2 * MSI framework for platform devices
4 * Copyright (C) 2015 ARM Limited, All Rights Reserved.
5 * Author: Marc Zyngier <marc.zyngier@arm.com>
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.
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.
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/>.
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>
27 #define DEV_ID_SHIFT 21
28 #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT))
31 * Internal data structure containing a (made up, but unique) devid
32 * and the callback to write the MSI message.
34 struct platform_msi_priv_data
{
38 irq_write_msi_msg_t write_msg
;
42 /* The devid allocator */
43 static DEFINE_IDA(platform_msi_devid_ida
);
45 #ifdef GENERIC_MSI_DOMAIN_OPS
47 * Convert an msi_desc to a globaly unique identifier (per-device
48 * devid + msi_desc position in the msi_list).
50 static irq_hw_number_t
platform_msi_calc_hwirq(struct msi_desc
*desc
)
54 devid
= desc
->platform
.msi_priv_data
->devid
;
56 return (devid
<< (32 - DEV_ID_SHIFT
)) | desc
->platform
.msi_index
;
59 static void platform_msi_set_desc(msi_alloc_info_t
*arg
, struct msi_desc
*desc
)
62 arg
->hwirq
= platform_msi_calc_hwirq(desc
);
65 static int platform_msi_init(struct irq_domain
*domain
,
66 struct msi_domain_info
*info
,
67 unsigned int virq
, irq_hw_number_t hwirq
,
68 msi_alloc_info_t
*arg
)
70 return irq_domain_set_hwirq_and_chip(domain
, virq
, hwirq
,
71 info
->chip
, info
->chip_data
);
74 #define platform_msi_set_desc NULL
75 #define platform_msi_init NULL
78 static void platform_msi_update_dom_ops(struct msi_domain_info
*info
)
80 struct msi_domain_ops
*ops
= info
->ops
;
84 if (ops
->msi_init
== NULL
)
85 ops
->msi_init
= platform_msi_init
;
86 if (ops
->set_desc
== NULL
)
87 ops
->set_desc
= platform_msi_set_desc
;
90 static void platform_msi_write_msg(struct irq_data
*data
, struct msi_msg
*msg
)
92 struct msi_desc
*desc
= irq_data_get_msi_desc(data
);
93 struct platform_msi_priv_data
*priv_data
;
95 priv_data
= desc
->platform
.msi_priv_data
;
97 priv_data
->write_msg(desc
, msg
);
100 static void platform_msi_update_chip_ops(struct msi_domain_info
*info
)
102 struct irq_chip
*chip
= info
->chip
;
106 chip
->irq_mask
= irq_chip_mask_parent
;
107 if (!chip
->irq_unmask
)
108 chip
->irq_unmask
= irq_chip_unmask_parent
;
110 chip
->irq_eoi
= irq_chip_eoi_parent
;
111 if (!chip
->irq_set_affinity
)
112 chip
->irq_set_affinity
= msi_domain_set_affinity
;
113 if (!chip
->irq_write_msi_msg
)
114 chip
->irq_write_msi_msg
= platform_msi_write_msg
;
117 static void platform_msi_free_descs(struct device
*dev
, int base
, int nvec
)
119 struct msi_desc
*desc
, *tmp
;
121 list_for_each_entry_safe(desc
, tmp
, dev_to_msi_list(dev
), list
) {
122 if (desc
->platform
.msi_index
>= base
&&
123 desc
->platform
.msi_index
< (base
+ nvec
)) {
124 list_del(&desc
->list
);
125 free_msi_entry(desc
);
130 static int platform_msi_alloc_descs_with_irq(struct device
*dev
, int virq
,
132 struct platform_msi_priv_data
*data
)
135 struct msi_desc
*desc
;
138 if (!list_empty(dev_to_msi_list(dev
))) {
139 desc
= list_last_entry(dev_to_msi_list(dev
),
140 struct msi_desc
, list
);
141 base
= desc
->platform
.msi_index
+ 1;
144 for (i
= 0; i
< nvec
; i
++) {
145 desc
= alloc_msi_entry(dev
, 1, NULL
);
149 desc
->platform
.msi_priv_data
= data
;
150 desc
->platform
.msi_index
= base
+ i
;
151 desc
->irq
= virq
? virq
+ i
: 0;
153 list_add_tail(&desc
->list
, dev_to_msi_list(dev
));
157 /* Clean up the mess */
158 platform_msi_free_descs(dev
, base
, nvec
);
166 static int platform_msi_alloc_descs(struct device
*dev
, int nvec
,
167 struct platform_msi_priv_data
*data
)
170 return platform_msi_alloc_descs_with_irq(dev
, 0, nvec
, data
);
174 * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
175 * @fwnode: Optional fwnode of the interrupt controller
176 * @info: MSI domain info
177 * @parent: Parent irq domain
179 * Updates the domain and chip ops and creates a platform MSI
183 * A domain pointer or NULL in case of failure.
185 struct irq_domain
*platform_msi_create_irq_domain(struct fwnode_handle
*fwnode
,
186 struct msi_domain_info
*info
,
187 struct irq_domain
*parent
)
189 struct irq_domain
*domain
;
191 if (info
->flags
& MSI_FLAG_USE_DEF_DOM_OPS
)
192 platform_msi_update_dom_ops(info
);
193 if (info
->flags
& MSI_FLAG_USE_DEF_CHIP_OPS
)
194 platform_msi_update_chip_ops(info
);
196 domain
= msi_create_irq_domain(fwnode
, info
, parent
);
198 domain
->bus_token
= DOMAIN_BUS_PLATFORM_MSI
;
203 static struct platform_msi_priv_data
*
204 platform_msi_alloc_priv_data(struct device
*dev
, unsigned int nvec
,
205 irq_write_msi_msg_t write_msi_msg
)
207 struct platform_msi_priv_data
*datap
;
209 * Limit the number of interrupts to 2048 per device. Should we
210 * need to bump this up, DEV_ID_SHIFT should be adjusted
211 * accordingly (which would impact the max number of MSI
214 if (!dev
->msi_domain
|| !write_msi_msg
|| !nvec
|| nvec
> MAX_DEV_MSIS
)
215 return ERR_PTR(-EINVAL
);
217 if (dev
->msi_domain
->bus_token
!= DOMAIN_BUS_PLATFORM_MSI
) {
218 dev_err(dev
, "Incompatible msi_domain, giving up\n");
219 return ERR_PTR(-EINVAL
);
222 /* Already had a helping of MSI? Greed... */
223 if (!list_empty(dev_to_msi_list(dev
)))
224 return ERR_PTR(-EBUSY
);
226 datap
= kzalloc(sizeof(*datap
), GFP_KERNEL
);
228 return ERR_PTR(-ENOMEM
);
230 datap
->devid
= ida_simple_get(&platform_msi_devid_ida
,
231 0, 1 << DEV_ID_SHIFT
, GFP_KERNEL
);
232 if (datap
->devid
< 0) {
233 int err
= datap
->devid
;
238 datap
->write_msg
= write_msi_msg
;
244 static void platform_msi_free_priv_data(struct platform_msi_priv_data
*data
)
246 ida_simple_remove(&platform_msi_devid_ida
, data
->devid
);
251 * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
252 * @dev: The device for which to allocate interrupts
253 * @nvec: The number of interrupts to allocate
254 * @write_msi_msg: Callback to write an interrupt message for @dev
257 * Zero for success, or an error code in case of failure
259 int platform_msi_domain_alloc_irqs(struct device
*dev
, unsigned int nvec
,
260 irq_write_msi_msg_t write_msi_msg
)
262 struct platform_msi_priv_data
*priv_data
;
265 priv_data
= platform_msi_alloc_priv_data(dev
, nvec
, write_msi_msg
);
266 if (IS_ERR(priv_data
))
267 return PTR_ERR(priv_data
);
269 err
= platform_msi_alloc_descs(dev
, nvec
, priv_data
);
271 goto out_free_priv_data
;
273 err
= msi_domain_alloc_irqs(dev
->msi_domain
, dev
, nvec
);
280 platform_msi_free_descs(dev
, 0, nvec
);
282 platform_msi_free_priv_data(priv_data
);
286 EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs
);
289 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
290 * @dev: The device for which to free interrupts
292 void platform_msi_domain_free_irqs(struct device
*dev
)
294 if (!list_empty(dev_to_msi_list(dev
))) {
295 struct msi_desc
*desc
;
297 desc
= first_msi_entry(dev
);
298 platform_msi_free_priv_data(desc
->platform
.msi_priv_data
);
301 msi_domain_free_irqs(dev
->msi_domain
, dev
);
302 platform_msi_free_descs(dev
, 0, MAX_DEV_MSIS
);
304 EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs
);
307 * platform_msi_get_host_data - Query the private data associated with
308 * a platform-msi domain
309 * @domain: The platform-msi domain
311 * Returns the private data provided when calling
312 * platform_msi_create_device_domain.
314 void *platform_msi_get_host_data(struct irq_domain
*domain
)
316 struct platform_msi_priv_data
*data
= domain
->host_data
;
317 return data
->host_data
;
321 * platform_msi_create_device_domain - Create a platform-msi domain
323 * @dev: The device generating the MSIs
324 * @nvec: The number of MSIs that need to be allocated
325 * @write_msi_msg: Callback to write an interrupt message for @dev
326 * @ops: The hierarchy domain operations to use
327 * @host_data: Private data associated to this domain
329 * Returns an irqdomain for @nvec interrupts
332 platform_msi_create_device_domain(struct device
*dev
,
334 irq_write_msi_msg_t write_msi_msg
,
335 const struct irq_domain_ops
*ops
,
338 struct platform_msi_priv_data
*data
;
339 struct irq_domain
*domain
;
342 data
= platform_msi_alloc_priv_data(dev
, nvec
, write_msi_msg
);
346 data
->host_data
= host_data
;
347 domain
= irq_domain_create_hierarchy(dev
->msi_domain
, 0, nvec
,
348 of_node_to_fwnode(dev
->of_node
),
353 err
= msi_domain_prepare_irqs(domain
->parent
, dev
, nvec
, &data
->arg
);
360 irq_domain_remove(domain
);
362 platform_msi_free_priv_data(data
);
367 * platform_msi_domain_free - Free interrupts associated with a platform-msi
370 * @domain: The platform-msi domain
371 * @virq: The base irq from which to perform the free operation
372 * @nvec: How many interrupts to free from @virq
374 void platform_msi_domain_free(struct irq_domain
*domain
, unsigned int virq
,
377 struct platform_msi_priv_data
*data
= domain
->host_data
;
378 struct msi_desc
*desc
;
379 for_each_msi_entry(desc
, data
->dev
) {
380 if (WARN_ON(!desc
->irq
|| desc
->nvec_used
!= 1))
382 if (!(desc
->irq
>= virq
&& desc
->irq
< (virq
+ nvec
)))
385 irq_domain_free_irqs_common(domain
, desc
->irq
, 1);
390 * platform_msi_domain_alloc - Allocate interrupts associated with
391 * a platform-msi domain
393 * @domain: The platform-msi domain
394 * @virq: The base irq from which to perform the allocate operation
395 * @nvec: How many interrupts to free from @virq
397 * Return 0 on success, or an error code on failure. Must be called
398 * with irq_domain_mutex held (which can only be done as part of a
399 * top-level interrupt allocation).
401 int platform_msi_domain_alloc(struct irq_domain
*domain
, unsigned int virq
,
402 unsigned int nr_irqs
)
404 struct platform_msi_priv_data
*data
= domain
->host_data
;
407 err
= platform_msi_alloc_descs_with_irq(data
->dev
, virq
, nr_irqs
, data
);
411 err
= msi_domain_populate_irqs(domain
->parent
, data
->dev
,
412 virq
, nr_irqs
, &data
->arg
);
414 platform_msi_domain_free(domain
, virq
, nr_irqs
);