1 // SPDX-License-Identifier: GPL-2.0
3 * MSI framework for platform devices
5 * Copyright (C) 2015 ARM Limited, All Rights Reserved.
6 * Author: Marc Zyngier <marc.zyngier@arm.com>
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>
16 #define DEV_ID_SHIFT 21
17 #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT))
20 * Internal data structure containing a (made up, but unique) devid
21 * and the callback to write the MSI message.
23 struct platform_msi_priv_data
{
27 irq_write_msi_msg_t write_msg
;
31 /* The devid allocator */
32 static DEFINE_IDA(platform_msi_devid_ida
);
34 #ifdef GENERIC_MSI_DOMAIN_OPS
36 * Convert an msi_desc to a globaly unique identifier (per-device
37 * devid + msi_desc position in the msi_list).
39 static irq_hw_number_t
platform_msi_calc_hwirq(struct msi_desc
*desc
)
43 devid
= desc
->platform
.msi_priv_data
->devid
;
45 return (devid
<< (32 - DEV_ID_SHIFT
)) | desc
->platform
.msi_index
;
48 static void platform_msi_set_desc(msi_alloc_info_t
*arg
, struct msi_desc
*desc
)
51 arg
->hwirq
= platform_msi_calc_hwirq(desc
);
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
)
59 return irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
60 info
->chip
, info
->chip_data
);
63 #define platform_msi_set_desc NULL
64 #define platform_msi_init NULL
67 static void platform_msi_update_dom_ops(struct msi_domain_info
*info
)
69 struct msi_domain_ops
*ops
= info
->ops
;
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
;
79 static void platform_msi_write_msg(struct irq_data
*data
, struct msi_msg
*msg
)
81 struct msi_desc
*desc
= irq_data_get_msi_desc(data
);
82 struct platform_msi_priv_data
*priv_data
;
84 priv_data
= desc
->platform
.msi_priv_data
;
86 priv_data
->write_msg(desc
, msg
);
89 static void platform_msi_update_chip_ops(struct msi_domain_info
*info
)
91 struct irq_chip
*chip
= info
->chip
;
95 chip
->irq_mask
= irq_chip_mask_parent
;
96 if (!chip
->irq_unmask
)
97 chip
->irq_unmask
= irq_chip_unmask_parent
;
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
;
109 static void platform_msi_free_descs(struct device
*dev
, int base
, int nvec
)
111 struct msi_desc
*desc
, *tmp
;
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
);
122 static int platform_msi_alloc_descs_with_irq(struct device
*dev
, int virq
,
124 struct platform_msi_priv_data
*data
)
127 struct msi_desc
*desc
;
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;
136 for (i
= 0; i
< nvec
; i
++) {
137 desc
= alloc_msi_entry(dev
, 1, NULL
);
141 desc
->platform
.msi_priv_data
= data
;
142 desc
->platform
.msi_index
= base
+ i
;
143 desc
->irq
= virq
? virq
+ i
: 0;
145 list_add_tail(&desc
->list
, dev_to_msi_list(dev
));
149 /* Clean up the mess */
150 platform_msi_free_descs(dev
, base
, nvec
);
158 static int platform_msi_alloc_descs(struct device
*dev
, int nvec
,
159 struct platform_msi_priv_data
*data
)
162 return platform_msi_alloc_descs_with_irq(dev
, 0, nvec
, data
);
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
171 * Updates the domain and chip ops and creates a platform MSI
175 * A domain pointer or NULL in case of failure.
177 struct irq_domain
*platform_msi_create_irq_domain(struct fwnode_handle
*fwnode
,
178 struct msi_domain_info
*info
,
179 struct irq_domain
*parent
)
181 struct irq_domain
*domain
;
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
);
188 domain
= msi_create_irq_domain(fwnode
, info
, parent
);
190 irq_domain_update_bus_token(domain
, DOMAIN_BUS_PLATFORM_MSI
);
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
)
199 struct platform_msi_priv_data
*datap
;
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
206 if (!dev
->msi_domain
|| !write_msi_msg
|| !nvec
|| nvec
> MAX_DEV_MSIS
)
207 return ERR_PTR(-EINVAL
);
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
);
214 /* Already had a helping of MSI? Greed... */
215 if (!list_empty(dev_to_msi_list(dev
)))
216 return ERR_PTR(-EBUSY
);
218 datap
= kzalloc(sizeof(*datap
), GFP_KERNEL
);
220 return ERR_PTR(-ENOMEM
);
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
;
230 datap
->write_msg
= write_msi_msg
;
236 static void platform_msi_free_priv_data(struct platform_msi_priv_data
*data
)
238 ida_simple_remove(&platform_msi_devid_ida
, data
->devid
);
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
249 * Zero for success, or an error code in case of failure
251 int platform_msi_domain_alloc_irqs(struct device
*dev
, unsigned int nvec
,
252 irq_write_msi_msg_t write_msi_msg
)
254 struct platform_msi_priv_data
*priv_data
;
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
);
261 err
= platform_msi_alloc_descs(dev
, nvec
, priv_data
);
263 goto out_free_priv_data
;
265 err
= msi_domain_alloc_irqs(dev
->msi_domain
, dev
, nvec
);
272 platform_msi_free_descs(dev
, 0, nvec
);
274 platform_msi_free_priv_data(priv_data
);
278 EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs
);
281 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
282 * @dev: The device for which to free interrupts
284 void platform_msi_domain_free_irqs(struct device
*dev
)
286 if (!list_empty(dev_to_msi_list(dev
))) {
287 struct msi_desc
*desc
;
289 desc
= first_msi_entry(dev
);
290 platform_msi_free_priv_data(desc
->platform
.msi_priv_data
);
293 msi_domain_free_irqs(dev
->msi_domain
, dev
);
294 platform_msi_free_descs(dev
, 0, MAX_DEV_MSIS
);
296 EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs
);
299 * platform_msi_get_host_data - Query the private data associated with
300 * a platform-msi domain
301 * @domain: The platform-msi domain
303 * Returns the private data provided when calling
304 * platform_msi_create_device_domain.
306 void *platform_msi_get_host_data(struct irq_domain
*domain
)
308 struct platform_msi_priv_data
*data
= domain
->host_data
;
309 return data
->host_data
;
313 * platform_msi_create_device_domain - Create a platform-msi domain
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
321 * Returns an irqdomain for @nvec interrupts
324 __platform_msi_create_device_domain(struct device
*dev
,
327 irq_write_msi_msg_t write_msi_msg
,
328 const struct irq_domain_ops
*ops
,
331 struct platform_msi_priv_data
*data
;
332 struct irq_domain
*domain
;
335 data
= platform_msi_alloc_priv_data(dev
, nvec
, write_msi_msg
);
339 data
->host_data
= host_data
;
340 domain
= irq_domain_create_hierarchy(dev
->msi_domain
, 0,
342 dev
->fwnode
, ops
, data
);
346 err
= msi_domain_prepare_irqs(domain
->parent
, dev
, nvec
, &data
->arg
);
353 irq_domain_remove(domain
);
355 platform_msi_free_priv_data(data
);
360 * platform_msi_domain_free - Free interrupts associated with a platform-msi
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
367 void platform_msi_domain_free(struct irq_domain
*domain
, unsigned int virq
,
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))
375 if (!(desc
->irq
>= virq
&& desc
->irq
< (virq
+ nvec
)))
378 irq_domain_free_irqs_common(domain
, desc
->irq
, 1);
379 list_del(&desc
->list
);
380 free_msi_entry(desc
);
385 * platform_msi_domain_alloc - Allocate interrupts associated with
386 * a platform-msi domain
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
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).
396 int platform_msi_domain_alloc(struct irq_domain
*domain
, unsigned int virq
,
397 unsigned int nr_irqs
)
399 struct platform_msi_priv_data
*data
= domain
->host_data
;
402 err
= platform_msi_alloc_descs_with_irq(data
->dev
, virq
, nr_irqs
, data
);
406 err
= msi_domain_populate_irqs(domain
->parent
, data
->dev
,
407 virq
, nr_irqs
, &data
->arg
);
409 platform_msi_domain_free(domain
, virq
, nr_irqs
);