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 */ 33struct platform_msi_priv_data { 34 irq_write_msi_msg_t write_msg; 35 int devid; 36}; 37 38/* The devid allocator */ 39static 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 */ 46static 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 55static 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 61static 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 return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 67 info->chip, info->chip_data); 68} 69#else 70#define platform_msi_set_desc NULL 71#define platform_msi_init NULL 72#endif 73 74static void platform_msi_update_dom_ops(struct msi_domain_info *info) 75{ 76 struct msi_domain_ops *ops = info->ops; 77 78 BUG_ON(!ops); 79 80 if (ops->msi_init == NULL) 81 ops->msi_init = platform_msi_init; 82 if (ops->set_desc == NULL) 83 ops->set_desc = platform_msi_set_desc; 84} 85 86static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) 87{ 88 struct msi_desc *desc = irq_data_get_msi_desc(data); 89 struct platform_msi_priv_data *priv_data; 90 91 priv_data = desc->platform.msi_priv_data; 92 93 priv_data->write_msg(desc, msg); 94} 95 96static void platform_msi_update_chip_ops(struct msi_domain_info *info) 97{ 98 struct irq_chip *chip = info->chip; 99 100 BUG_ON(!chip); 101 if (!chip->irq_mask) 102 chip->irq_mask = irq_chip_mask_parent; 103 if (!chip->irq_unmask) 104 chip->irq_unmask = irq_chip_unmask_parent; 105 if (!chip->irq_eoi) 106 chip->irq_eoi = irq_chip_eoi_parent; 107 if (!chip->irq_set_affinity) 108 chip->irq_set_affinity = msi_domain_set_affinity; 109 if (!chip->irq_write_msi_msg) 110 chip->irq_write_msi_msg = platform_msi_write_msg; 111} 112 113static void platform_msi_free_descs(struct device *dev) 114{ 115 struct msi_desc *desc, *tmp; 116 117 list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { 118 list_del(&desc->list); 119 free_msi_entry(desc); 120 } 121} 122 123static int platform_msi_alloc_descs(struct device *dev, int nvec, 124 struct platform_msi_priv_data *data) 125 126{ 127 int i; 128 129 for (i = 0; i < nvec; i++) { 130 struct msi_desc *desc; 131 132 desc = alloc_msi_entry(dev); 133 if (!desc) 134 break; 135 136 desc->platform.msi_priv_data = data; 137 desc->platform.msi_index = i; 138 desc->nvec_used = 1; 139 140 list_add_tail(&desc->list, dev_to_msi_list(dev)); 141 } 142 143 if (i != nvec) { 144 /* Clean up the mess */ 145 platform_msi_free_descs(dev); 146 147 return -ENOMEM; 148 } 149 150 return 0; 151} 152 153/** 154 * platform_msi_create_irq_domain - Create a platform MSI interrupt domain 155 * @fwnode: Optional fwnode of the interrupt controller 156 * @info: MSI domain info 157 * @parent: Parent irq domain 158 * 159 * Updates the domain and chip ops and creates a platform MSI 160 * interrupt domain. 161 * 162 * Returns: 163 * A domain pointer or NULL in case of failure. 164 */ 165struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, 166 struct msi_domain_info *info, 167 struct irq_domain *parent) 168{ 169 struct irq_domain *domain; 170 171 if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) 172 platform_msi_update_dom_ops(info); 173 if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 174 platform_msi_update_chip_ops(info); 175 176 domain = msi_create_irq_domain(fwnode, info, parent); 177 if (domain) 178 domain->bus_token = DOMAIN_BUS_PLATFORM_MSI; 179 180 return domain; 181} 182 183/** 184 * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev 185 * @dev: The device for which to allocate interrupts 186 * @nvec: The number of interrupts to allocate 187 * @write_msi_msg: Callback to write an interrupt message for @dev 188 * 189 * Returns: 190 * Zero for success, or an error code in case of failure 191 */ 192int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, 193 irq_write_msi_msg_t write_msi_msg) 194{ 195 struct platform_msi_priv_data *priv_data; 196 int err; 197 198 /* 199 * Limit the number of interrupts to 256 per device. Should we 200 * need to bump this up, DEV_ID_SHIFT should be adjusted 201 * accordingly (which would impact the max number of MSI 202 * capable devices). 203 */ 204 if (!dev->msi_domain || !write_msi_msg || !nvec || 205 nvec > (1 << (32 - DEV_ID_SHIFT))) 206 return -EINVAL; 207 208 if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { 209 dev_err(dev, "Incompatible msi_domain, giving up\n"); 210 return -EINVAL; 211 } 212 213 /* Already had a helping of MSI? Greed... */ 214 if (!list_empty(dev_to_msi_list(dev))) 215 return -EBUSY; 216 217 priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); 218 if (!priv_data) 219 return -ENOMEM; 220 221 priv_data->devid = ida_simple_get(&platform_msi_devid_ida, 222 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); 223 if (priv_data->devid < 0) { 224 err = priv_data->devid; 225 goto out_free_data; 226 } 227 228 priv_data->write_msg = write_msi_msg; 229 230 err = platform_msi_alloc_descs(dev, nvec, priv_data); 231 if (err) 232 goto out_free_id; 233 234 err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec); 235 if (err) 236 goto out_free_desc; 237 238 return 0; 239 240out_free_desc: 241 platform_msi_free_descs(dev); 242out_free_id: 243 ida_simple_remove(&platform_msi_devid_ida, priv_data->devid); 244out_free_data: 245 kfree(priv_data); 246 247 return err; 248} 249 250/** 251 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev 252 * @dev: The device for which to free interrupts 253 */ 254void platform_msi_domain_free_irqs(struct device *dev) 255{ 256 struct msi_desc *desc; 257 258 desc = first_msi_entry(dev); 259 if (desc) { 260 struct platform_msi_priv_data *data; 261 262 data = desc->platform.msi_priv_data; 263 264 ida_simple_remove(&platform_msi_devid_ida, data->devid); 265 kfree(data); 266 } 267 268 msi_domain_free_irqs(dev->msi_domain, dev); 269 platform_msi_free_descs(dev); 270} 271