Commit | Line | Data |
---|---|---|
989d42e8 | 1 | // SPDX-License-Identifier: GPL-2.0 |
c09fcc4b MZ |
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> | |
c09fcc4b MZ |
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 | ||
aff5e06b | 16 | #define DEV_ID_SHIFT 21 |
ab6484ee | 17 | #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) |
c09fcc4b MZ |
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 { | |
552c494a MZ |
24 | struct device *dev; |
25 | void *host_data; | |
26 | msi_alloc_info_t arg; | |
c09fcc4b MZ |
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 | { | |
e4084a16 MZ |
59 | return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
60 | info->chip, info->chip_data); | |
c09fcc4b MZ |
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 | { | |
e4084a16 | 81 | struct msi_desc *desc = irq_data_get_msi_desc(data); |
c09fcc4b MZ |
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; | |
6988e0e0 MZ |
104 | if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && |
105 | !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) | |
106 | info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; | |
c09fcc4b MZ |
107 | } |
108 | ||
ab6484ee | 109 | static void platform_msi_free_descs(struct device *dev, int base, int nvec) |
c09fcc4b MZ |
110 | { |
111 | struct msi_desc *desc, *tmp; | |
112 | ||
113 | list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { | |
ab6484ee MZ |
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 | } | |
c09fcc4b MZ |
119 | } |
120 | } | |
121 | ||
552c494a MZ |
122 | static int platform_msi_alloc_descs_with_irq(struct device *dev, int virq, |
123 | int nvec, | |
124 | struct platform_msi_priv_data *data) | |
c09fcc4b MZ |
125 | |
126 | { | |
ab6484ee MZ |
127 | struct msi_desc *desc; |
128 | int i, base = 0; | |
c09fcc4b | 129 | |
ab6484ee MZ |
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 | } | |
c09fcc4b | 135 | |
ab6484ee | 136 | for (i = 0; i < nvec; i++) { |
28f4b041 | 137 | desc = alloc_msi_entry(dev, 1, NULL); |
c09fcc4b MZ |
138 | if (!desc) |
139 | break; | |
140 | ||
141 | desc->platform.msi_priv_data = data; | |
ab6484ee | 142 | desc->platform.msi_index = base + i; |
552c494a | 143 | desc->irq = virq ? virq + i : 0; |
c09fcc4b MZ |
144 | |
145 | list_add_tail(&desc->list, dev_to_msi_list(dev)); | |
146 | } | |
147 | ||
148 | if (i != nvec) { | |
149 | /* Clean up the mess */ | |
ab6484ee | 150 | platform_msi_free_descs(dev, base, nvec); |
c09fcc4b MZ |
151 | |
152 | return -ENOMEM; | |
153 | } | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
552c494a MZ |
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 | ||
c09fcc4b MZ |
165 | /** |
166 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain | |
be5436c8 | 167 | * @fwnode: Optional fwnode of the interrupt controller |
c09fcc4b MZ |
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 | */ | |
be5436c8 | 177 | struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, |
c09fcc4b MZ |
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 | ||
be5436c8 | 188 | domain = msi_create_irq_domain(fwnode, info, parent); |
c09fcc4b | 189 | if (domain) |
96f0d93a | 190 | irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI); |
c09fcc4b MZ |
191 | |
192 | return domain; | |
193 | } | |
194 | ||
72f57f2f MZ |
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) | |
c09fcc4b | 198 | { |
72f57f2f | 199 | struct platform_msi_priv_data *datap; |
c09fcc4b | 200 | /* |
788d2393 | 201 | * Limit the number of interrupts to 2048 per device. Should we |
c09fcc4b MZ |
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 | */ | |
ab6484ee | 206 | if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) |
72f57f2f | 207 | return ERR_PTR(-EINVAL); |
c09fcc4b MZ |
208 | |
209 | if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { | |
210 | dev_err(dev, "Incompatible msi_domain, giving up\n"); | |
72f57f2f | 211 | return ERR_PTR(-EINVAL); |
c09fcc4b MZ |
212 | } |
213 | ||
214 | /* Already had a helping of MSI? Greed... */ | |
215 | if (!list_empty(dev_to_msi_list(dev))) | |
72f57f2f MZ |
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 | } | |
c09fcc4b | 229 | |
72f57f2f | 230 | datap->write_msg = write_msi_msg; |
552c494a | 231 | datap->dev = dev; |
c09fcc4b | 232 | |
72f57f2f MZ |
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; | |
c09fcc4b | 256 | |
72f57f2f MZ |
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); | |
c09fcc4b MZ |
260 | |
261 | err = platform_msi_alloc_descs(dev, nvec, priv_data); | |
262 | if (err) | |
72f57f2f | 263 | goto out_free_priv_data; |
c09fcc4b MZ |
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: | |
ab6484ee | 272 | platform_msi_free_descs(dev, 0, nvec); |
72f57f2f MZ |
273 | out_free_priv_data: |
274 | platform_msi_free_priv_data(priv_data); | |
c09fcc4b MZ |
275 | |
276 | return err; | |
277 | } | |
bb1a7931 | 278 | EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs); |
c09fcc4b MZ |
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 | { | |
72f57f2f MZ |
286 | if (!list_empty(dev_to_msi_list(dev))) { |
287 | struct msi_desc *desc; | |
c09fcc4b | 288 | |
72f57f2f MZ |
289 | desc = first_msi_entry(dev); |
290 | platform_msi_free_priv_data(desc->platform.msi_priv_data); | |
c09fcc4b MZ |
291 | } |
292 | ||
293 | msi_domain_free_irqs(dev->msi_domain, dev); | |
ab6484ee | 294 | platform_msi_free_descs(dev, 0, MAX_DEV_MSIS); |
c09fcc4b | 295 | } |
bb1a7931 | 296 | EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs); |
552c494a MZ |
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 * | |
1f83515b MZ |
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) | |
552c494a MZ |
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; | |
1f83515b MZ |
340 | domain = irq_domain_create_hierarchy(dev->msi_domain, 0, |
341 | is_tree ? 0 : nvec, | |
6943acf6 | 342 | dev->fwnode, ops, data); |
552c494a MZ |
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; | |
81b1e6e6 MR |
371 | struct msi_desc *desc, *tmp; |
372 | for_each_msi_entry_safe(desc, tmp, data->dev) { | |
552c494a MZ |
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); | |
81b1e6e6 MR |
379 | list_del(&desc->list); |
380 | free_msi_entry(desc); | |
552c494a MZ |
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 | } |