1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MSI framework for platform devices |
4 | * |
5 | * Copyright (C) 2015 ARM Limited, All Rights Reserved. |
6 | * Author: Marc Zyngier <marc.zyngier@arm.com> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/idr.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/irqdomain.h> |
13 | #include <linux/msi.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #define DEV_ID_SHIFT 21 |
17 | #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) |
18 | |
19 | /* |
20 | * Internal data structure containing a (made up, but unique) devid |
21 | * and the callback to write the MSI message. |
22 | */ |
23 | struct platform_msi_priv_data { |
24 | struct device *dev; |
25 | void *host_data; |
26 | msi_alloc_info_t arg; |
27 | irq_write_msi_msg_t write_msg; |
28 | int devid; |
29 | }; |
30 | |
31 | /* The devid allocator */ |
32 | static DEFINE_IDA(platform_msi_devid_ida); |
33 | |
34 | #ifdef GENERIC_MSI_DOMAIN_OPS |
35 | /* |
36 | * Convert an msi_desc to a globaly unique identifier (per-device |
37 | * devid + msi_desc position in the msi_list). |
38 | */ |
39 | static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) |
40 | { |
41 | u32 devid; |
42 | |
43 | devid = desc->platform.msi_priv_data->devid; |
44 | |
45 | return (devid << (32 - DEV_ID_SHIFT)) | desc->platform.msi_index; |
46 | } |
47 | |
48 | static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) |
49 | { |
50 | arg->desc = desc; |
51 | arg->hwirq = platform_msi_calc_hwirq(desc); |
52 | } |
53 | |
54 | static int platform_msi_init(struct irq_domain *domain, |
55 | struct msi_domain_info *info, |
56 | unsigned int virq, irq_hw_number_t hwirq, |
57 | msi_alloc_info_t *arg) |
58 | { |
59 | return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
60 | info->chip, info->chip_data); |
61 | } |
62 | #else |
63 | #define platform_msi_set_desc NULL |
64 | #define platform_msi_init NULL |
65 | #endif |
66 | |
67 | static void platform_msi_update_dom_ops(struct msi_domain_info *info) |
68 | { |
69 | struct msi_domain_ops *ops = info->ops; |
70 | |
71 | BUG_ON(!ops); |
72 | |
73 | if (ops->msi_init == NULL) |
74 | ops->msi_init = platform_msi_init; |
75 | if (ops->set_desc == NULL) |
76 | ops->set_desc = platform_msi_set_desc; |
77 | } |
78 | |
79 | static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) |
80 | { |
81 | struct msi_desc *desc = irq_data_get_msi_desc(data); |
82 | struct platform_msi_priv_data *priv_data; |
83 | |
84 | priv_data = desc->platform.msi_priv_data; |
85 | |
86 | priv_data->write_msg(desc, msg); |
87 | } |
88 | |
89 | static void platform_msi_update_chip_ops(struct msi_domain_info *info) |
90 | { |
91 | struct irq_chip *chip = info->chip; |
92 | |
93 | BUG_ON(!chip); |
94 | if (!chip->irq_mask) |
95 | chip->irq_mask = irq_chip_mask_parent; |
96 | if (!chip->irq_unmask) |
97 | chip->irq_unmask = irq_chip_unmask_parent; |
98 | if (!chip->irq_eoi) |
99 | chip->irq_eoi = irq_chip_eoi_parent; |
100 | if (!chip->irq_set_affinity) |
101 | chip->irq_set_affinity = msi_domain_set_affinity; |
102 | if (!chip->irq_write_msi_msg) |
103 | chip->irq_write_msi_msg = platform_msi_write_msg; |
104 | if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && |
105 | !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) |
106 | info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; |
107 | } |
108 | |
109 | static void platform_msi_free_descs(struct device *dev, int base, int nvec) |
110 | { |
111 | struct msi_desc *desc, *tmp; |
112 | |
113 | list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { |
114 | if (desc->platform.msi_index >= base && |
115 | desc->platform.msi_index < (base + nvec)) { |
116 | list_del(&desc->list); |
117 | free_msi_entry(desc); |
118 | } |
119 | } |
120 | } |
121 | |
122 | static int platform_msi_alloc_descs_with_irq(struct device *dev, int virq, |
123 | int nvec, |
124 | struct platform_msi_priv_data *data) |
125 | |
126 | { |
127 | struct msi_desc *desc; |
128 | int i, base = 0; |
129 | |
130 | if (!list_empty(dev_to_msi_list(dev))) { |
131 | desc = list_last_entry(dev_to_msi_list(dev), |
132 | struct msi_desc, list); |
133 | base = desc->platform.msi_index + 1; |
134 | } |
135 | |
136 | for (i = 0; i < nvec; i++) { |
137 | desc = alloc_msi_entry(dev, 1, NULL); |
138 | if (!desc) |
139 | break; |
140 | |
141 | desc->platform.msi_priv_data = data; |
142 | desc->platform.msi_index = base + i; |
143 | desc->irq = virq ? virq + i : 0; |
144 | |
145 | list_add_tail(&desc->list, dev_to_msi_list(dev)); |
146 | } |
147 | |
148 | if (i != nvec) { |
149 | /* Clean up the mess */ |
150 | platform_msi_free_descs(dev, base, nvec); |
151 | |
152 | return -ENOMEM; |
153 | } |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static int platform_msi_alloc_descs(struct device *dev, int nvec, |
159 | struct platform_msi_priv_data *data) |
160 | |
161 | { |
162 | return platform_msi_alloc_descs_with_irq(dev, 0, nvec, data); |
163 | } |
164 | |
165 | /** |
166 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain |
167 | * @fwnode: Optional fwnode 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 fwnode_handle *fwnode, |
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(fwnode, info, parent); |
189 | if (domain) |
190 | irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI); |
191 | |
192 | return domain; |
193 | } |
194 | |
195 | static struct platform_msi_priv_data * |
196 | platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, |
197 | irq_write_msi_msg_t write_msi_msg) |
198 | { |
199 | struct platform_msi_priv_data *datap; |
200 | /* |
201 | * Limit the number of interrupts to 2048 per device. Should we |
202 | * need to bump this up, DEV_ID_SHIFT should be adjusted |
203 | * accordingly (which would impact the max number of MSI |
204 | * capable devices). |
205 | */ |
206 | if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) |
207 | return ERR_PTR(-EINVAL); |
208 | |
209 | if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { |
210 | dev_err(dev, "Incompatible msi_domain, giving up\n" ); |
211 | return ERR_PTR(-EINVAL); |
212 | } |
213 | |
214 | /* Already had a helping of MSI? Greed... */ |
215 | if (!list_empty(dev_to_msi_list(dev))) |
216 | return ERR_PTR(-EBUSY); |
217 | |
218 | datap = kzalloc(sizeof(*datap), GFP_KERNEL); |
219 | if (!datap) |
220 | return ERR_PTR(-ENOMEM); |
221 | |
222 | datap->devid = ida_simple_get(&platform_msi_devid_ida, |
223 | 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); |
224 | if (datap->devid < 0) { |
225 | int err = datap->devid; |
226 | kfree(datap); |
227 | return ERR_PTR(err); |
228 | } |
229 | |
230 | datap->write_msg = write_msi_msg; |
231 | datap->dev = dev; |
232 | |
233 | return datap; |
234 | } |
235 | |
236 | static void platform_msi_free_priv_data(struct platform_msi_priv_data *data) |
237 | { |
238 | ida_simple_remove(&platform_msi_devid_ida, data->devid); |
239 | kfree(data); |
240 | } |
241 | |
242 | /** |
243 | * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev |
244 | * @dev: The device for which to allocate interrupts |
245 | * @nvec: The number of interrupts to allocate |
246 | * @write_msi_msg: Callback to write an interrupt message for @dev |
247 | * |
248 | * Returns: |
249 | * Zero for success, or an error code in case of failure |
250 | */ |
251 | int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, |
252 | irq_write_msi_msg_t write_msi_msg) |
253 | { |
254 | struct platform_msi_priv_data *priv_data; |
255 | int err; |
256 | |
257 | priv_data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
258 | if (IS_ERR(priv_data)) |
259 | return PTR_ERR(priv_data); |
260 | |
261 | err = platform_msi_alloc_descs(dev, nvec, priv_data); |
262 | if (err) |
263 | goto out_free_priv_data; |
264 | |
265 | err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec); |
266 | if (err) |
267 | goto out_free_desc; |
268 | |
269 | return 0; |
270 | |
271 | out_free_desc: |
272 | platform_msi_free_descs(dev, 0, nvec); |
273 | out_free_priv_data: |
274 | platform_msi_free_priv_data(priv_data); |
275 | |
276 | return err; |
277 | } |
278 | EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs); |
279 | |
280 | /** |
281 | * platform_msi_domain_free_irqs - Free MSI interrupts for @dev |
282 | * @dev: The device for which to free interrupts |
283 | */ |
284 | void platform_msi_domain_free_irqs(struct device *dev) |
285 | { |
286 | if (!list_empty(dev_to_msi_list(dev))) { |
287 | struct msi_desc *desc; |
288 | |
289 | desc = first_msi_entry(dev); |
290 | platform_msi_free_priv_data(desc->platform.msi_priv_data); |
291 | } |
292 | |
293 | msi_domain_free_irqs(dev->msi_domain, dev); |
294 | platform_msi_free_descs(dev, 0, MAX_DEV_MSIS); |
295 | } |
296 | EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs); |
297 | |
298 | /** |
299 | * platform_msi_get_host_data - Query the private data associated with |
300 | * a platform-msi domain |
301 | * @domain: The platform-msi domain |
302 | * |
303 | * Returns the private data provided when calling |
304 | * platform_msi_create_device_domain. |
305 | */ |
306 | void *platform_msi_get_host_data(struct irq_domain *domain) |
307 | { |
308 | struct platform_msi_priv_data *data = domain->host_data; |
309 | return data->host_data; |
310 | } |
311 | |
312 | /** |
313 | * platform_msi_create_device_domain - Create a platform-msi domain |
314 | * |
315 | * @dev: The device generating the MSIs |
316 | * @nvec: The number of MSIs that need to be allocated |
317 | * @write_msi_msg: Callback to write an interrupt message for @dev |
318 | * @ops: The hierarchy domain operations to use |
319 | * @host_data: Private data associated to this domain |
320 | * |
321 | * Returns an irqdomain for @nvec interrupts |
322 | */ |
323 | struct irq_domain * |
324 | __platform_msi_create_device_domain(struct device *dev, |
325 | unsigned int nvec, |
326 | bool is_tree, |
327 | irq_write_msi_msg_t write_msi_msg, |
328 | const struct irq_domain_ops *ops, |
329 | void *host_data) |
330 | { |
331 | struct platform_msi_priv_data *data; |
332 | struct irq_domain *domain; |
333 | int err; |
334 | |
335 | data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
336 | if (IS_ERR(data)) |
337 | return NULL; |
338 | |
339 | data->host_data = host_data; |
340 | domain = irq_domain_create_hierarchy(dev->msi_domain, 0, |
341 | is_tree ? 0 : nvec, |
342 | dev->fwnode, ops, data); |
343 | if (!domain) |
344 | goto free_priv; |
345 | |
346 | err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg); |
347 | if (err) |
348 | goto free_domain; |
349 | |
350 | return domain; |
351 | |
352 | free_domain: |
353 | irq_domain_remove(domain); |
354 | free_priv: |
355 | platform_msi_free_priv_data(data); |
356 | return NULL; |
357 | } |
358 | |
359 | /** |
360 | * platform_msi_domain_free - Free interrupts associated with a platform-msi |
361 | * domain |
362 | * |
363 | * @domain: The platform-msi domain |
364 | * @virq: The base irq from which to perform the free operation |
365 | * @nvec: How many interrupts to free from @virq |
366 | */ |
367 | void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq, |
368 | unsigned int nvec) |
369 | { |
370 | struct platform_msi_priv_data *data = domain->host_data; |
371 | struct msi_desc *desc, *tmp; |
372 | for_each_msi_entry_safe(desc, tmp, data->dev) { |
373 | if (WARN_ON(!desc->irq || desc->nvec_used != 1)) |
374 | return; |
375 | if (!(desc->irq >= virq && desc->irq < (virq + nvec))) |
376 | continue; |
377 | |
378 | irq_domain_free_irqs_common(domain, desc->irq, 1); |
379 | list_del(&desc->list); |
380 | free_msi_entry(desc); |
381 | } |
382 | } |
383 | |
384 | /** |
385 | * platform_msi_domain_alloc - Allocate interrupts associated with |
386 | * a platform-msi domain |
387 | * |
388 | * @domain: The platform-msi domain |
389 | * @virq: The base irq from which to perform the allocate operation |
390 | * @nvec: How many interrupts to free from @virq |
391 | * |
392 | * Return 0 on success, or an error code on failure. Must be called |
393 | * with irq_domain_mutex held (which can only be done as part of a |
394 | * top-level interrupt allocation). |
395 | */ |
396 | int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, |
397 | unsigned int nr_irqs) |
398 | { |
399 | struct platform_msi_priv_data *data = domain->host_data; |
400 | int err; |
401 | |
402 | err = platform_msi_alloc_descs_with_irq(data->dev, virq, nr_irqs, data); |
403 | if (err) |
404 | return err; |
405 | |
406 | err = msi_domain_populate_irqs(domain->parent, data->dev, |
407 | virq, nr_irqs, &data->arg); |
408 | if (err) |
409 | platform_msi_domain_free(domain, virq, nr_irqs); |
410 | |
411 | return err; |
412 | } |
413 | |