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
);
149 desc
->platform
.msi_priv_data
= data
;
150 desc
->platform
.msi_index
= base
+ i
;
152 desc
->irq
= virq
? virq
+ i
: 0;
154 list_add_tail(&desc
->list
, dev_to_msi_list(dev
));
158 /* Clean up the mess */
159 platform_msi_free_descs(dev
, base
, nvec
);
167 static int platform_msi_alloc_descs(struct device
*dev
, int nvec
,
168 struct platform_msi_priv_data
*data
)
171 return platform_msi_alloc_descs_with_irq(dev
, 0, nvec
, data
);
175 * platform_msi_create_irq_domain - Create a platform MSI interrupt domain
176 * @fwnode: Optional fwnode of the interrupt controller
177 * @info: MSI domain info
178 * @parent: Parent irq domain
180 * Updates the domain and chip ops and creates a platform MSI
184 * A domain pointer or NULL in case of failure.
186 struct irq_domain
*platform_msi_create_irq_domain(struct fwnode_handle
*fwnode
,
187 struct msi_domain_info
*info
,
188 struct irq_domain
*parent
)
190 struct irq_domain
*domain
;
192 if (info
->flags
& MSI_FLAG_USE_DEF_DOM_OPS
)
193 platform_msi_update_dom_ops(info
);
194 if (info
->flags
& MSI_FLAG_USE_DEF_CHIP_OPS
)
195 platform_msi_update_chip_ops(info
);
197 domain
= msi_create_irq_domain(fwnode
, info
, parent
);
199 domain
->bus_token
= DOMAIN_BUS_PLATFORM_MSI
;
204 static struct platform_msi_priv_data
*
205 platform_msi_alloc_priv_data(struct device
*dev
, unsigned int nvec
,
206 irq_write_msi_msg_t write_msi_msg
)
208 struct platform_msi_priv_data
*datap
;
210 * Limit the number of interrupts to 256 per device. Should we
211 * need to bump this up, DEV_ID_SHIFT should be adjusted
212 * accordingly (which would impact the max number of MSI
215 if (!dev
->msi_domain
|| !write_msi_msg
|| !nvec
|| nvec
> MAX_DEV_MSIS
)
216 return ERR_PTR(-EINVAL
);
218 if (dev
->msi_domain
->bus_token
!= DOMAIN_BUS_PLATFORM_MSI
) {
219 dev_err(dev
, "Incompatible msi_domain, giving up\n");
220 return ERR_PTR(-EINVAL
);
223 /* Already had a helping of MSI? Greed... */
224 if (!list_empty(dev_to_msi_list(dev
)))
225 return ERR_PTR(-EBUSY
);
227 datap
= kzalloc(sizeof(*datap
), GFP_KERNEL
);
229 return ERR_PTR(-ENOMEM
);
231 datap
->devid
= ida_simple_get(&platform_msi_devid_ida
,
232 0, 1 << DEV_ID_SHIFT
, GFP_KERNEL
);
233 if (datap
->devid
< 0) {
234 int err
= datap
->devid
;
239 datap
->write_msg
= write_msi_msg
;
245 static void platform_msi_free_priv_data(struct platform_msi_priv_data
*data
)
247 ida_simple_remove(&platform_msi_devid_ida
, data
->devid
);
252 * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
253 * @dev: The device for which to allocate interrupts
254 * @nvec: The number of interrupts to allocate
255 * @write_msi_msg: Callback to write an interrupt message for @dev
258 * Zero for success, or an error code in case of failure
260 int platform_msi_domain_alloc_irqs(struct device
*dev
, unsigned int nvec
,
261 irq_write_msi_msg_t write_msi_msg
)
263 struct platform_msi_priv_data
*priv_data
;
266 priv_data
= platform_msi_alloc_priv_data(dev
, nvec
, write_msi_msg
);
267 if (IS_ERR(priv_data
))
268 return PTR_ERR(priv_data
);
270 err
= platform_msi_alloc_descs(dev
, nvec
, priv_data
);
272 goto out_free_priv_data
;
274 err
= msi_domain_alloc_irqs(dev
->msi_domain
, dev
, nvec
);
281 platform_msi_free_descs(dev
, 0, nvec
);
283 platform_msi_free_priv_data(priv_data
);
287 EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs
);
290 * platform_msi_domain_free_irqs - Free MSI interrupts for @dev
291 * @dev: The device for which to free interrupts
293 void platform_msi_domain_free_irqs(struct device
*dev
)
295 if (!list_empty(dev_to_msi_list(dev
))) {
296 struct msi_desc
*desc
;
298 desc
= first_msi_entry(dev
);
299 platform_msi_free_priv_data(desc
->platform
.msi_priv_data
);
302 msi_domain_free_irqs(dev
->msi_domain
, dev
);
303 platform_msi_free_descs(dev
, 0, MAX_DEV_MSIS
);
305 EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs
);
308 * platform_msi_get_host_data - Query the private data associated with
309 * a platform-msi domain
310 * @domain: The platform-msi domain
312 * Returns the private data provided when calling
313 * platform_msi_create_device_domain.
315 void *platform_msi_get_host_data(struct irq_domain
*domain
)
317 struct platform_msi_priv_data
*data
= domain
->host_data
;
318 return data
->host_data
;
322 * platform_msi_create_device_domain - Create a platform-msi domain
324 * @dev: The device generating the MSIs
325 * @nvec: The number of MSIs that need to be allocated
326 * @write_msi_msg: Callback to write an interrupt message for @dev
327 * @ops: The hierarchy domain operations to use
328 * @host_data: Private data associated to this domain
330 * Returns an irqdomain for @nvec interrupts
333 platform_msi_create_device_domain(struct device
*dev
,
335 irq_write_msi_msg_t write_msi_msg
,
336 const struct irq_domain_ops
*ops
,
339 struct platform_msi_priv_data
*data
;
340 struct irq_domain
*domain
;
343 data
= platform_msi_alloc_priv_data(dev
, nvec
, write_msi_msg
);
347 data
->host_data
= host_data
;
348 domain
= irq_domain_create_hierarchy(dev
->msi_domain
, 0, nvec
,
349 of_node_to_fwnode(dev
->of_node
),
354 err
= msi_domain_prepare_irqs(domain
->parent
, dev
, nvec
, &data
->arg
);
361 irq_domain_remove(domain
);
363 platform_msi_free_priv_data(data
);
368 * platform_msi_domain_free - Free interrupts associated with a platform-msi
371 * @domain: The platform-msi domain
372 * @virq: The base irq from which to perform the free operation
373 * @nvec: How many interrupts to free from @virq
375 void platform_msi_domain_free(struct irq_domain
*domain
, unsigned int virq
,
378 struct platform_msi_priv_data
*data
= domain
->host_data
;
379 struct msi_desc
*desc
;
380 for_each_msi_entry(desc
, data
->dev
) {
381 if (WARN_ON(!desc
->irq
|| desc
->nvec_used
!= 1))
383 if (!(desc
->irq
>= virq
&& desc
->irq
< (virq
+ nvec
)))
386 irq_domain_free_irqs_common(domain
, desc
->irq
, 1);
391 * platform_msi_domain_alloc - Allocate interrupts associated with
392 * a platform-msi domain
394 * @domain: The platform-msi domain
395 * @virq: The base irq from which to perform the allocate operation
396 * @nvec: How many interrupts to free from @virq
398 * Return 0 on success, or an error code on failure. Must be called
399 * with irq_domain_mutex held (which can only be done as part of a
400 * top-level interrupt allocation).
402 int platform_msi_domain_alloc(struct irq_domain
*domain
, unsigned int virq
,
403 unsigned int nr_irqs
)
405 struct platform_msi_priv_data
*data
= domain
->host_data
;
408 err
= platform_msi_alloc_descs_with_irq(data
->dev
, virq
, nr_irqs
, data
);
412 err
= msi_domain_populate_irqs(domain
->parent
, data
->dev
,
413 virq
, nr_irqs
, &data
->arg
);
415 platform_msi_domain_free(domain
, virq
, nr_irqs
);