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 | /* Begin of removal area. Once everything is converted over. Cleanup the includes too! */ |
17 | |
18 | #define DEV_ID_SHIFT 21 |
19 | #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) |
20 | |
21 | /* |
22 | * Internal data structure containing a (made up, but unique) devid |
23 | * and the callback to write the MSI message. |
24 | */ |
25 | struct platform_msi_priv_data { |
26 | struct device *dev; |
27 | void *host_data; |
28 | msi_alloc_info_t arg; |
29 | irq_write_msi_msg_t write_msg; |
30 | int devid; |
31 | }; |
32 | |
33 | /* The devid allocator */ |
34 | static DEFINE_IDA(platform_msi_devid_ida); |
35 | |
36 | #ifdef GENERIC_MSI_DOMAIN_OPS |
37 | /* |
38 | * Convert an msi_desc to a globaly unique identifier (per-device |
39 | * devid + msi_desc position in the msi_list). |
40 | */ |
41 | static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) |
42 | { |
43 | u32 devid = desc->dev->msi.data->platform_data->devid; |
44 | |
45 | return (devid << (32 - DEV_ID_SHIFT)) | desc->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 | |
63 | static void platform_msi_set_proxy_dev(msi_alloc_info_t *arg) |
64 | { |
65 | arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; |
66 | } |
67 | #else |
68 | #define platform_msi_set_desc NULL |
69 | #define platform_msi_init NULL |
70 | #define platform_msi_set_proxy_dev(x) do {} while(0) |
71 | #endif |
72 | |
73 | static void platform_msi_update_dom_ops(struct msi_domain_info *info) |
74 | { |
75 | struct msi_domain_ops *ops = info->ops; |
76 | |
77 | BUG_ON(!ops); |
78 | |
79 | if (ops->msi_init == NULL) |
80 | ops->msi_init = platform_msi_init; |
81 | if (ops->set_desc == NULL) |
82 | ops->set_desc = platform_msi_set_desc; |
83 | } |
84 | |
85 | static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) |
86 | { |
87 | struct msi_desc *desc = irq_data_get_msi_desc(d: data); |
88 | |
89 | desc->dev->msi.data->platform_data->write_msg(desc, msg); |
90 | } |
91 | |
92 | static void platform_msi_update_chip_ops(struct msi_domain_info *info) |
93 | { |
94 | struct irq_chip *chip = info->chip; |
95 | |
96 | BUG_ON(!chip); |
97 | if (!chip->irq_mask) |
98 | chip->irq_mask = irq_chip_mask_parent; |
99 | if (!chip->irq_unmask) |
100 | chip->irq_unmask = irq_chip_unmask_parent; |
101 | if (!chip->irq_eoi) |
102 | chip->irq_eoi = irq_chip_eoi_parent; |
103 | if (!chip->irq_set_affinity) |
104 | chip->irq_set_affinity = msi_domain_set_affinity; |
105 | if (!chip->irq_write_msi_msg) |
106 | chip->irq_write_msi_msg = platform_msi_write_msg; |
107 | if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && |
108 | !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) |
109 | info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; |
110 | } |
111 | |
112 | /** |
113 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain |
114 | * @fwnode: Optional fwnode of the interrupt controller |
115 | * @info: MSI domain info |
116 | * @parent: Parent irq domain |
117 | * |
118 | * Updates the domain and chip ops and creates a platform MSI |
119 | * interrupt domain. |
120 | * |
121 | * Returns: |
122 | * A domain pointer or NULL in case of failure. |
123 | */ |
124 | struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, |
125 | struct msi_domain_info *info, |
126 | struct irq_domain *parent) |
127 | { |
128 | struct irq_domain *domain; |
129 | |
130 | if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) |
131 | platform_msi_update_dom_ops(info); |
132 | if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) |
133 | platform_msi_update_chip_ops(info); |
134 | info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | |
135 | MSI_FLAG_FREE_MSI_DESCS; |
136 | |
137 | domain = msi_create_irq_domain(fwnode, info, parent); |
138 | if (domain) |
139 | irq_domain_update_bus_token(domain, bus_token: DOMAIN_BUS_PLATFORM_MSI); |
140 | |
141 | return domain; |
142 | } |
143 | EXPORT_SYMBOL_GPL(platform_msi_create_irq_domain); |
144 | |
145 | static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, |
146 | irq_write_msi_msg_t write_msi_msg) |
147 | { |
148 | struct platform_msi_priv_data *datap; |
149 | int err; |
150 | |
151 | /* |
152 | * Limit the number of interrupts to 2048 per device. Should we |
153 | * need to bump this up, DEV_ID_SHIFT should be adjusted |
154 | * accordingly (which would impact the max number of MSI |
155 | * capable devices). |
156 | */ |
157 | if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) |
158 | return -EINVAL; |
159 | |
160 | if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { |
161 | dev_err(dev, "Incompatible msi_domain, giving up\n"); |
162 | return -EINVAL; |
163 | } |
164 | |
165 | err = msi_setup_device_data(dev); |
166 | if (err) |
167 | return err; |
168 | |
169 | /* Already initialized? */ |
170 | if (dev->msi.data->platform_data) |
171 | return -EBUSY; |
172 | |
173 | datap = kzalloc(size: sizeof(*datap), GFP_KERNEL); |
174 | if (!datap) |
175 | return -ENOMEM; |
176 | |
177 | datap->devid = ida_alloc_max(ida: &platform_msi_devid_ida, |
178 | max: (1 << DEV_ID_SHIFT) - 1, GFP_KERNEL); |
179 | if (datap->devid < 0) { |
180 | err = datap->devid; |
181 | kfree(objp: datap); |
182 | return err; |
183 | } |
184 | |
185 | datap->write_msg = write_msi_msg; |
186 | datap->dev = dev; |
187 | dev->msi.data->platform_data = datap; |
188 | return 0; |
189 | } |
190 | |
191 | static void platform_msi_free_priv_data(struct device *dev) |
192 | { |
193 | struct platform_msi_priv_data *data = dev->msi.data->platform_data; |
194 | |
195 | dev->msi.data->platform_data = NULL; |
196 | ida_free(&platform_msi_devid_ida, id: data->devid); |
197 | kfree(objp: data); |
198 | } |
199 | |
200 | /** |
201 | * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev |
202 | * @dev: The device for which to allocate interrupts |
203 | * @nvec: The number of interrupts to allocate |
204 | * @write_msi_msg: Callback to write an interrupt message for @dev |
205 | * |
206 | * Returns: |
207 | * Zero for success, or an error code in case of failure |
208 | */ |
209 | static int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, |
210 | irq_write_msi_msg_t write_msi_msg) |
211 | { |
212 | int err; |
213 | |
214 | err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
215 | if (err) |
216 | return err; |
217 | |
218 | err = msi_domain_alloc_irqs_range(dev, domid: MSI_DEFAULT_DOMAIN, first: 0, last: nvec - 1); |
219 | if (err) |
220 | platform_msi_free_priv_data(dev); |
221 | |
222 | return err; |
223 | } |
224 | |
225 | /** |
226 | * platform_msi_get_host_data - Query the private data associated with |
227 | * a platform-msi domain |
228 | * @domain: The platform-msi domain |
229 | * |
230 | * Return: The private data provided when calling |
231 | * platform_msi_create_device_domain(). |
232 | */ |
233 | void *platform_msi_get_host_data(struct irq_domain *domain) |
234 | { |
235 | struct platform_msi_priv_data *data = domain->host_data; |
236 | |
237 | return data->host_data; |
238 | } |
239 | |
240 | static struct lock_class_key platform_device_msi_lock_class; |
241 | |
242 | /** |
243 | * __platform_msi_create_device_domain - Create a platform-msi device domain |
244 | * |
245 | * @dev: The device generating the MSIs |
246 | * @nvec: The number of MSIs that need to be allocated |
247 | * @is_tree: flag to indicate tree hierarchy |
248 | * @write_msi_msg: Callback to write an interrupt message for @dev |
249 | * @ops: The hierarchy domain operations to use |
250 | * @host_data: Private data associated to this domain |
251 | * |
252 | * Return: An irqdomain for @nvec interrupts on success, NULL in case of error. |
253 | * |
254 | * This is for interrupt domains which stack on a platform-msi domain |
255 | * created by platform_msi_create_irq_domain(). @dev->msi.domain points to |
256 | * that platform-msi domain which is the parent for the new domain. |
257 | */ |
258 | struct irq_domain * |
259 | __platform_msi_create_device_domain(struct device *dev, |
260 | unsigned int nvec, |
261 | bool is_tree, |
262 | irq_write_msi_msg_t write_msi_msg, |
263 | const struct irq_domain_ops *ops, |
264 | void *host_data) |
265 | { |
266 | struct platform_msi_priv_data *data; |
267 | struct irq_domain *domain; |
268 | int err; |
269 | |
270 | err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
271 | if (err) |
272 | return NULL; |
273 | |
274 | /* |
275 | * Use a separate lock class for the MSI descriptor mutex on |
276 | * platform MSI device domains because the descriptor mutex nests |
277 | * into the domain mutex. See alloc/free below. |
278 | */ |
279 | lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class); |
280 | |
281 | data = dev->msi.data->platform_data; |
282 | data->host_data = host_data; |
283 | domain = irq_domain_create_hierarchy(parent: dev->msi.domain, flags: 0, |
284 | size: is_tree ? 0 : nvec, |
285 | fwnode: dev->fwnode, ops, host_data: data); |
286 | if (!domain) |
287 | goto free_priv; |
288 | |
289 | platform_msi_set_proxy_dev(&data->arg); |
290 | err = msi_domain_prepare_irqs(domain: domain->parent, dev, nvec, args: &data->arg); |
291 | if (err) |
292 | goto free_domain; |
293 | |
294 | return domain; |
295 | |
296 | free_domain: |
297 | irq_domain_remove(host: domain); |
298 | free_priv: |
299 | platform_msi_free_priv_data(dev); |
300 | return NULL; |
301 | } |
302 | |
303 | /** |
304 | * platform_msi_device_domain_free - Free interrupts associated with a platform-msi |
305 | * device domain |
306 | * |
307 | * @domain: The platform-msi device domain |
308 | * @virq: The base irq from which to perform the free operation |
309 | * @nr_irqs: How many interrupts to free from @virq |
310 | */ |
311 | void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq, |
312 | unsigned int nr_irqs) |
313 | { |
314 | struct platform_msi_priv_data *data = domain->host_data; |
315 | |
316 | msi_lock_descs(dev: data->dev); |
317 | msi_domain_depopulate_descs(dev: data->dev, virq, nvec: nr_irqs); |
318 | irq_domain_free_irqs_common(domain, virq, nr_irqs); |
319 | msi_free_msi_descs_range(dev: data->dev, first: virq, last: virq + nr_irqs - 1); |
320 | msi_unlock_descs(dev: data->dev); |
321 | } |
322 | |
323 | /** |
324 | * platform_msi_device_domain_alloc - Allocate interrupts associated with |
325 | * a platform-msi device domain |
326 | * |
327 | * @domain: The platform-msi device domain |
328 | * @virq: The base irq from which to perform the allocate operation |
329 | * @nr_irqs: How many interrupts to allocate from @virq |
330 | * |
331 | * Return 0 on success, or an error code on failure. Must be called |
332 | * with irq_domain_mutex held (which can only be done as part of a |
333 | * top-level interrupt allocation). |
334 | */ |
335 | int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq, |
336 | unsigned int nr_irqs) |
337 | { |
338 | struct platform_msi_priv_data *data = domain->host_data; |
339 | struct device *dev = data->dev; |
340 | |
341 | return msi_domain_populate_irqs(domain: domain->parent, dev, virq, nvec: nr_irqs, args: &data->arg); |
342 | } |
343 | |
344 | /* End of removal area */ |
345 | |
346 | /* Real per device domain interfaces */ |
347 | |
348 | /* |
349 | * This indirection can go when platform_device_msi_init_and_alloc_irqs() |
350 | * is switched to a proper irq_chip::irq_write_msi_msg() callback. Keep it |
351 | * simple for now. |
352 | */ |
353 | static void platform_msi_write_msi_msg(struct irq_data *d, struct msi_msg *msg) |
354 | { |
355 | irq_write_msi_msg_t cb = d->chip_data; |
356 | |
357 | cb(irq_data_get_msi_desc(d), msg); |
358 | } |
359 | |
360 | static void platform_msi_set_desc_byindex(msi_alloc_info_t *arg, struct msi_desc *desc) |
361 | { |
362 | arg->desc = desc; |
363 | arg->hwirq = desc->msi_index; |
364 | } |
365 | |
366 | static const struct msi_domain_template platform_msi_template = { |
367 | .chip = { |
368 | .name = "pMSI", |
369 | .irq_mask = irq_chip_mask_parent, |
370 | .irq_unmask = irq_chip_unmask_parent, |
371 | .irq_write_msi_msg = platform_msi_write_msi_msg, |
372 | /* The rest is filled in by the platform MSI parent */ |
373 | }, |
374 | |
375 | .ops = { |
376 | .set_desc = platform_msi_set_desc_byindex, |
377 | }, |
378 | |
379 | .info = { |
380 | .bus_token = DOMAIN_BUS_DEVICE_MSI, |
381 | }, |
382 | }; |
383 | |
384 | /** |
385 | * platform_device_msi_init_and_alloc_irqs - Initialize platform device MSI |
386 | * and allocate interrupts for @dev |
387 | * @dev: The device for which to allocate interrupts |
388 | * @nvec: The number of interrupts to allocate |
389 | * @write_msi_msg: Callback to write an interrupt message for @dev |
390 | * |
391 | * Returns: |
392 | * Zero for success, or an error code in case of failure |
393 | * |
394 | * This creates a MSI domain on @dev which has @dev->msi.domain as |
395 | * parent. The parent domain sets up the new domain. The domain has |
396 | * a fixed size of @nvec. The domain is managed by devres and will |
397 | * be removed when the device is removed. |
398 | * |
399 | * Note: For migration purposes this falls back to the original platform_msi code |
400 | * up to the point where all platforms have been converted to the MSI |
401 | * parent model. |
402 | */ |
403 | int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec, |
404 | irq_write_msi_msg_t write_msi_msg) |
405 | { |
406 | struct irq_domain *domain = dev->msi.domain; |
407 | |
408 | if (!domain || !write_msi_msg) |
409 | return -EINVAL; |
410 | |
411 | /* Migration support. Will go away once everything is converted */ |
412 | if (!irq_domain_is_msi_parent(domain)) |
413 | return platform_msi_domain_alloc_irqs(dev, nvec, write_msi_msg); |
414 | |
415 | /* |
416 | * @write_msi_msg is stored in the resulting msi_domain_info::data. |
417 | * The underlying domain creation mechanism will assign that |
418 | * callback to the resulting irq chip. |
419 | */ |
420 | if (!msi_create_device_irq_domain(dev, domid: MSI_DEFAULT_DOMAIN, |
421 | template: &platform_msi_template, |
422 | hwsize: nvec, NULL, chip_data: write_msi_msg)) |
423 | return -ENODEV; |
424 | |
425 | return msi_domain_alloc_irqs_range(dev, domid: MSI_DEFAULT_DOMAIN, first: 0, last: nvec - 1); |
426 | } |
427 | EXPORT_SYMBOL_GPL(platform_device_msi_init_and_alloc_irqs); |
428 | |
429 | /** |
430 | * platform_device_msi_free_irqs_all - Free all interrupts for @dev |
431 | * @dev: The device for which to free interrupts |
432 | */ |
433 | void platform_device_msi_free_irqs_all(struct device *dev) |
434 | { |
435 | struct irq_domain *domain = dev->msi.domain; |
436 | |
437 | msi_domain_free_irqs_all(dev, domid: MSI_DEFAULT_DOMAIN); |
438 | |
439 | /* Migration support. Will go away once everything is converted */ |
440 | if (!irq_domain_is_msi_parent(domain)) |
441 | platform_msi_free_priv_data(dev); |
442 | } |
443 | EXPORT_SYMBOL_GPL(platform_device_msi_free_irqs_all); |
444 |
Definitions
- platform_msi_priv_data
- platform_msi_devid_ida
- platform_msi_update_dom_ops
- platform_msi_write_msg
- platform_msi_update_chip_ops
- platform_msi_create_irq_domain
- platform_msi_alloc_priv_data
- platform_msi_free_priv_data
- platform_msi_domain_alloc_irqs
- platform_msi_get_host_data
- platform_device_msi_lock_class
- __platform_msi_create_device_domain
- platform_msi_device_domain_free
- platform_msi_device_domain_alloc
- platform_msi_write_msi_msg
- platform_msi_set_desc_byindex
- platform_msi_template
- platform_device_msi_init_and_alloc_irqs
Improve your Profiling and Debugging skills
Find out more