Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers/base: Add MSI domain support for non-PCI devices

With the msi_list and the msi_domain properties now being at the
generic device level, it is starting to be relatively easy to offer
a generic way of providing non-PCI MSIs.

The two major hurdles with this idea are:

- Lack of global ID that identifies a device: this is worked around by
having a global ID allocator for each device that gets enrolled in
the platform MSI subsystem

- Lack of standard way to write the message in the generating device.
This is solved by mandating driver code to provide a write_msg
callback, so that everyone can have their own square wheel

Apart from that, the API is fairly straightforward:

- platform_msi_create_irq_domain creates an MSI domain that gets
tagged with DOMAIN_BUS_PLATFORM_MSI

- platform_msi_domain_alloc_irqs allocate MSIs for a given device,
populating the msi_list

- platform_msi_domain_free_irqs does what is written on the tin

[ tglx: Created a seperate struct platform_msi_desc and added
kerneldoc entries ]

Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Cc: <linux-arm-kernel@lists.infradead.org>
Cc: Yijing Wang <wangyijing@huawei.com>
Cc: Ma Jun <majun258@huawei.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Duc Dang <dhdang@apm.com>
Cc: Hanjun Guo <hanjun.guo@linaro.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Jiang Liu <jiang.liu@linux.intel.com>
Cc: Jason Cooper <jason@lakedaemon.net>
Link: http://lkml.kernel.org/r/1438091186-10244-10-git-send-email-marc.zyngier@arm.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>

authored by

Marc Zyngier and committed by
Thomas Gleixner
c09fcc4b c706c239

+305
+1
drivers/base/Makefile
··· 22 22 obj-$(CONFIG_SOC_BUS) += soc.o 23 23 obj-$(CONFIG_PINCTRL) += pinctrl.o 24 24 obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o 25 + obj-$(CONFIG_GENERIC_MSI_IRQ_DOMAIN) += platform-msi.o 25 26 26 27 ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG 27 28
+282
drivers/base/platform-msi.c
··· 1 + /* 2 + * MSI framework for platform devices 3 + * 4 + * Copyright (C) 2015 ARM Limited, All Rights Reserved. 5 + * Author: Marc Zyngier <marc.zyngier@arm.com> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License version 2 as 9 + * published by the Free Software Foundation. 10 + * 11 + * This program is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 + * GNU General Public License for more details. 15 + * 16 + * You should have received a copy of the GNU General Public License 17 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 + */ 19 + 20 + #include <linux/device.h> 21 + #include <linux/idr.h> 22 + #include <linux/irq.h> 23 + #include <linux/irqdomain.h> 24 + #include <linux/msi.h> 25 + #include <linux/slab.h> 26 + 27 + #define DEV_ID_SHIFT 24 28 + 29 + /* 30 + * Internal data structure containing a (made up, but unique) devid 31 + * and the callback to write the MSI message. 32 + */ 33 + struct platform_msi_priv_data { 34 + irq_write_msi_msg_t write_msg; 35 + int devid; 36 + }; 37 + 38 + /* The devid allocator */ 39 + static DEFINE_IDA(platform_msi_devid_ida); 40 + 41 + #ifdef GENERIC_MSI_DOMAIN_OPS 42 + /* 43 + * Convert an msi_desc to a globaly unique identifier (per-device 44 + * devid + msi_desc position in the msi_list). 45 + */ 46 + static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) 47 + { 48 + u32 devid; 49 + 50 + devid = desc->platform.msi_priv_data->devid; 51 + 52 + return (devid << (32 - DEV_ID_SHIFT)) | desc->platform.msi_index; 53 + } 54 + 55 + static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) 56 + { 57 + arg->desc = desc; 58 + arg->hwirq = platform_msi_calc_hwirq(desc); 59 + } 60 + 61 + static int platform_msi_init(struct irq_domain *domain, 62 + struct msi_domain_info *info, 63 + unsigned int virq, irq_hw_number_t hwirq, 64 + msi_alloc_info_t *arg) 65 + { 66 + struct irq_data *data; 67 + 68 + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 69 + info->chip, info->chip_data); 70 + 71 + /* 72 + * Save the MSI descriptor in handler_data so that the 73 + * irq_write_msi_msg callback can retrieve it (and the 74 + * associated device). 75 + */ 76 + data = irq_domain_get_irq_data(domain, virq); 77 + data->handler_data = arg->desc; 78 + 79 + return 0; 80 + } 81 + #else 82 + #define platform_msi_set_desc NULL 83 + #define platform_msi_init NULL 84 + #endif 85 + 86 + static void platform_msi_update_dom_ops(struct msi_domain_info *info) 87 + { 88 + struct msi_domain_ops *ops = info->ops; 89 + 90 + BUG_ON(!ops); 91 + 92 + if (ops->msi_init == NULL) 93 + ops->msi_init = platform_msi_init; 94 + if (ops->set_desc == NULL) 95 + ops->set_desc = platform_msi_set_desc; 96 + } 97 + 98 + static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) 99 + { 100 + struct msi_desc *desc = irq_data_get_irq_handler_data(data); 101 + struct platform_msi_priv_data *priv_data; 102 + 103 + priv_data = desc->platform.msi_priv_data; 104 + 105 + priv_data->write_msg(desc, msg); 106 + } 107 + 108 + static void platform_msi_update_chip_ops(struct msi_domain_info *info) 109 + { 110 + struct irq_chip *chip = info->chip; 111 + 112 + BUG_ON(!chip); 113 + if (!chip->irq_mask) 114 + chip->irq_mask = irq_chip_mask_parent; 115 + if (!chip->irq_unmask) 116 + chip->irq_unmask = irq_chip_unmask_parent; 117 + if (!chip->irq_eoi) 118 + chip->irq_eoi = irq_chip_eoi_parent; 119 + if (!chip->irq_set_affinity) 120 + chip->irq_set_affinity = msi_domain_set_affinity; 121 + if (!chip->irq_write_msi_msg) 122 + chip->irq_write_msi_msg = platform_msi_write_msg; 123 + } 124 + 125 + static void platform_msi_free_descs(struct device *dev) 126 + { 127 + struct msi_desc *desc, *tmp; 128 + 129 + list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { 130 + list_del(&desc->list); 131 + free_msi_entry(desc); 132 + } 133 + } 134 + 135 + static int platform_msi_alloc_descs(struct device *dev, int nvec, 136 + struct platform_msi_priv_data *data) 137 + 138 + { 139 + int i; 140 + 141 + for (i = 0; i < nvec; i++) { 142 + struct msi_desc *desc; 143 + 144 + desc = alloc_msi_entry(dev); 145 + if (!desc) 146 + break; 147 + 148 + desc->platform.msi_priv_data = data; 149 + desc->platform.msi_index = i; 150 + desc->nvec_used = 1; 151 + 152 + list_add_tail(&desc->list, dev_to_msi_list(dev)); 153 + } 154 + 155 + if (i != nvec) { 156 + /* Clean up the mess */ 157 + platform_msi_free_descs(dev); 158 + 159 + return -ENOMEM; 160 + } 161 + 162 + return 0; 163 + } 164 + 165 + /** 166 + * platform_msi_create_irq_domain - Create a platform MSI interrupt domain 167 + * @np: Optional device-tree node of the interrupt controller 168 + * @info: MSI domain info 169 + * @parent: Parent irq domain 170 + * 171 + * Updates the domain and chip ops and creates a platform MSI 172 + * interrupt domain. 173 + * 174 + * Returns: 175 + * A domain pointer or NULL in case of failure. 176 + */ 177 + struct irq_domain *platform_msi_create_irq_domain(struct device_node *np, 178 + struct msi_domain_info *info, 179 + struct irq_domain *parent) 180 + { 181 + struct irq_domain *domain; 182 + 183 + if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) 184 + platform_msi_update_dom_ops(info); 185 + if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 186 + platform_msi_update_chip_ops(info); 187 + 188 + domain = msi_create_irq_domain(np, info, parent); 189 + if (domain) 190 + domain->bus_token = DOMAIN_BUS_PLATFORM_MSI; 191 + 192 + return domain; 193 + } 194 + 195 + /** 196 + * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev 197 + * @dev: The device for which to allocate interrupts 198 + * @nvec: The number of interrupts to allocate 199 + * @write_msi_msg: Callback to write an interrupt message for @dev 200 + * 201 + * Returns: 202 + * Zero for success, or an error code in case of failure 203 + */ 204 + int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, 205 + irq_write_msi_msg_t write_msi_msg) 206 + { 207 + struct platform_msi_priv_data *priv_data; 208 + int err; 209 + 210 + /* 211 + * Limit the number of interrupts to 256 per device. Should we 212 + * need to bump this up, DEV_ID_SHIFT should be adjusted 213 + * accordingly (which would impact the max number of MSI 214 + * capable devices). 215 + */ 216 + if (!dev->msi_domain || !write_msi_msg || !nvec || 217 + nvec > (1 << (32 - DEV_ID_SHIFT))) 218 + return -EINVAL; 219 + 220 + if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { 221 + dev_err(dev, "Incompatible msi_domain, giving up\n"); 222 + return -EINVAL; 223 + } 224 + 225 + /* Already had a helping of MSI? Greed... */ 226 + if (!list_empty(dev_to_msi_list(dev))) 227 + return -EBUSY; 228 + 229 + priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); 230 + if (!priv_data) 231 + return -ENOMEM; 232 + 233 + priv_data->devid = ida_simple_get(&platform_msi_devid_ida, 234 + 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); 235 + if (priv_data->devid < 0) { 236 + err = priv_data->devid; 237 + goto out_free_data; 238 + } 239 + 240 + priv_data->write_msg = write_msi_msg; 241 + 242 + err = platform_msi_alloc_descs(dev, nvec, priv_data); 243 + if (err) 244 + goto out_free_id; 245 + 246 + err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec); 247 + if (err) 248 + goto out_free_desc; 249 + 250 + return 0; 251 + 252 + out_free_desc: 253 + platform_msi_free_descs(dev); 254 + out_free_id: 255 + ida_simple_remove(&platform_msi_devid_ida, priv_data->devid); 256 + out_free_data: 257 + kfree(priv_data); 258 + 259 + return err; 260 + } 261 + 262 + /** 263 + * platform_msi_domain_free_irqs - Free MSI interrupts for @dev 264 + * @dev: The device for which to free interrupts 265 + */ 266 + void platform_msi_domain_free_irqs(struct device *dev) 267 + { 268 + struct msi_desc *desc; 269 + 270 + desc = first_msi_entry(dev); 271 + if (desc) { 272 + struct platform_msi_priv_data *data; 273 + 274 + data = desc->platform.msi_priv_data; 275 + 276 + ida_simple_remove(&platform_msi_devid_ida, data->devid); 277 + kfree(data); 278 + } 279 + 280 + msi_domain_free_irqs(dev->msi_domain, dev); 281 + platform_msi_free_descs(dev); 282 + }
+22
include/linux/msi.h
··· 15 15 struct irq_data; 16 16 struct msi_desc; 17 17 struct pci_dev; 18 + struct platform_msi_priv_data; 18 19 void __get_cached_msi_msg(struct msi_desc *entry, struct msi_msg *msg); 19 20 void get_cached_msi_msg(unsigned int irq, struct msi_msg *msg); 21 + 22 + typedef void (*irq_write_msi_msg_t)(struct msi_desc *desc, 23 + struct msi_msg *msg); 24 + 25 + /** 26 + * platform_msi_desc - Platform device specific msi descriptor data 27 + * @msi_priv_data: Pointer to platform private data 28 + * @msi_index: The index of the MSI descriptor for multi MSI 29 + */ 30 + struct platform_msi_desc { 31 + struct platform_msi_priv_data *msi_priv_data; 32 + u16 msi_index; 33 + }; 20 34 21 35 /** 22 36 * struct msi_desc - Descriptor structure for MSI based interrupts ··· 50 36 * @default_irq:[PCI MSI/X] The default pre-assigned non-MSI irq 51 37 * @mask_pos: [PCI MSI] Mask register position 52 38 * @mask_base: [PCI MSI-X] Mask register base address 39 + * @platform: [platform] Platform device specific msi descriptor data 53 40 */ 54 41 struct msi_desc { 55 42 /* Shared device/bus type independent data */ ··· 86 71 * anonymous for now as it would require an immediate 87 72 * tree wide cleanup. 88 73 */ 74 + struct platform_msi_desc platform; 89 75 }; 90 76 }; 91 77 ··· 273 257 void msi_domain_free_irqs(struct irq_domain *domain, struct device *dev); 274 258 struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain); 275 259 260 + struct irq_domain *platform_msi_create_irq_domain(struct device_node *np, 261 + struct msi_domain_info *info, 262 + struct irq_domain *parent); 263 + int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, 264 + irq_write_msi_msg_t write_msi_msg); 265 + void platform_msi_domain_free_irqs(struct device *dev); 276 266 #endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */ 277 267 278 268 #ifdef CONFIG_PCI_MSI_IRQ_DOMAIN