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 { | |
00ed1401 BS |
24 | struct device *dev; |
25 | void *host_data; | |
00ed1401 BS |
26 | msi_alloc_info_t arg; |
27 | irq_write_msi_msg_t write_msg; | |
28 | int devid; | |
c09fcc4b MZ |
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 | { | |
fc22e7db | 41 | u32 devid = desc->dev->msi.data->platform_data->devid; |
c09fcc4b | 42 | |
dba27c7f | 43 | return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index; |
c09fcc4b MZ |
44 | } |
45 | ||
46 | static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) | |
47 | { | |
48 | arg->desc = desc; | |
49 | arg->hwirq = platform_msi_calc_hwirq(desc); | |
50 | } | |
51 | ||
52 | static int platform_msi_init(struct irq_domain *domain, | |
53 | struct msi_domain_info *info, | |
54 | unsigned int virq, irq_hw_number_t hwirq, | |
55 | msi_alloc_info_t *arg) | |
56 | { | |
e4084a16 MZ |
57 | return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
58 | info->chip, info->chip_data); | |
c09fcc4b | 59 | } |
91f90daa MZ |
60 | |
61 | static void platform_msi_set_proxy_dev(msi_alloc_info_t *arg) | |
62 | { | |
63 | arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; | |
64 | } | |
c09fcc4b MZ |
65 | #else |
66 | #define platform_msi_set_desc NULL | |
67 | #define platform_msi_init NULL | |
91f90daa | 68 | #define platform_msi_set_proxy_dev(x) do {} while(0) |
c09fcc4b MZ |
69 | #endif |
70 | ||
71 | static void platform_msi_update_dom_ops(struct msi_domain_info *info) | |
72 | { | |
73 | struct msi_domain_ops *ops = info->ops; | |
74 | ||
75 | BUG_ON(!ops); | |
76 | ||
77 | if (ops->msi_init == NULL) | |
78 | ops->msi_init = platform_msi_init; | |
79 | if (ops->set_desc == NULL) | |
80 | ops->set_desc = platform_msi_set_desc; | |
81 | } | |
82 | ||
83 | static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) | |
84 | { | |
e4084a16 | 85 | struct msi_desc *desc = irq_data_get_msi_desc(data); |
c09fcc4b | 86 | |
fc22e7db | 87 | desc->dev->msi.data->platform_data->write_msg(desc, msg); |
c09fcc4b MZ |
88 | } |
89 | ||
90 | static void platform_msi_update_chip_ops(struct msi_domain_info *info) | |
91 | { | |
92 | struct irq_chip *chip = info->chip; | |
93 | ||
94 | BUG_ON(!chip); | |
95 | if (!chip->irq_mask) | |
96 | chip->irq_mask = irq_chip_mask_parent; | |
97 | if (!chip->irq_unmask) | |
98 | chip->irq_unmask = irq_chip_unmask_parent; | |
99 | if (!chip->irq_eoi) | |
100 | chip->irq_eoi = irq_chip_eoi_parent; | |
101 | if (!chip->irq_set_affinity) | |
102 | chip->irq_set_affinity = msi_domain_set_affinity; | |
103 | if (!chip->irq_write_msi_msg) | |
104 | chip->irq_write_msi_msg = platform_msi_write_msg; | |
6988e0e0 MZ |
105 | if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && |
106 | !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) | |
107 | info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; | |
c09fcc4b MZ |
108 | } |
109 | ||
c09fcc4b MZ |
110 | /** |
111 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain | |
be5436c8 | 112 | * @fwnode: Optional fwnode of the interrupt controller |
c09fcc4b MZ |
113 | * @info: MSI domain info |
114 | * @parent: Parent irq domain | |
115 | * | |
116 | * Updates the domain and chip ops and creates a platform MSI | |
117 | * interrupt domain. | |
118 | * | |
119 | * Returns: | |
120 | * A domain pointer or NULL in case of failure. | |
121 | */ | |
be5436c8 | 122 | struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, |
c09fcc4b MZ |
123 | struct msi_domain_info *info, |
124 | struct irq_domain *parent) | |
125 | { | |
126 | struct irq_domain *domain; | |
127 | ||
128 | if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) | |
129 | platform_msi_update_dom_ops(info); | |
130 | if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) | |
131 | platform_msi_update_chip_ops(info); | |
653b50c5 TG |
132 | info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | |
133 | MSI_FLAG_FREE_MSI_DESCS; | |
c09fcc4b | 134 | |
be5436c8 | 135 | domain = msi_create_irq_domain(fwnode, info, parent); |
c09fcc4b | 136 | if (domain) |
96f0d93a | 137 | irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI); |
c09fcc4b MZ |
138 | |
139 | return domain; | |
140 | } | |
aecd1de3 | 141 | EXPORT_SYMBOL_GPL(platform_msi_create_irq_domain); |
c09fcc4b | 142 | |
fc22e7db TG |
143 | static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, |
144 | irq_write_msi_msg_t write_msi_msg) | |
c09fcc4b | 145 | { |
72f57f2f | 146 | struct platform_msi_priv_data *datap; |
077aeadb TG |
147 | int err; |
148 | ||
c09fcc4b | 149 | /* |
788d2393 | 150 | * Limit the number of interrupts to 2048 per device. Should we |
c09fcc4b MZ |
151 | * need to bump this up, DEV_ID_SHIFT should be adjusted |
152 | * accordingly (which would impact the max number of MSI | |
153 | * capable devices). | |
154 | */ | |
34fff628 | 155 | if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) |
fc22e7db | 156 | return -EINVAL; |
c09fcc4b | 157 | |
34fff628 | 158 | if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { |
c09fcc4b | 159 | dev_err(dev, "Incompatible msi_domain, giving up\n"); |
fc22e7db | 160 | return -EINVAL; |
c09fcc4b MZ |
161 | } |
162 | ||
077aeadb TG |
163 | err = msi_setup_device_data(dev); |
164 | if (err) | |
fc22e7db | 165 | return err; |
077aeadb | 166 | |
fc22e7db TG |
167 | /* Already initialized? */ |
168 | if (dev->msi.data->platform_data) | |
169 | return -EBUSY; | |
72f57f2f MZ |
170 | |
171 | datap = kzalloc(sizeof(*datap), GFP_KERNEL); | |
172 | if (!datap) | |
fc22e7db | 173 | return -ENOMEM; |
72f57f2f MZ |
174 | |
175 | datap->devid = ida_simple_get(&platform_msi_devid_ida, | |
176 | 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); | |
177 | if (datap->devid < 0) { | |
077aeadb | 178 | err = datap->devid; |
72f57f2f | 179 | kfree(datap); |
fc22e7db | 180 | return err; |
72f57f2f | 181 | } |
c09fcc4b | 182 | |
72f57f2f | 183 | datap->write_msg = write_msi_msg; |
552c494a | 184 | datap->dev = dev; |
fc22e7db TG |
185 | dev->msi.data->platform_data = datap; |
186 | return 0; | |
72f57f2f MZ |
187 | } |
188 | ||
fc22e7db | 189 | static void platform_msi_free_priv_data(struct device *dev) |
72f57f2f | 190 | { |
fc22e7db TG |
191 | struct platform_msi_priv_data *data = dev->msi.data->platform_data; |
192 | ||
193 | dev->msi.data->platform_data = NULL; | |
72f57f2f MZ |
194 | ida_simple_remove(&platform_msi_devid_ida, data->devid); |
195 | kfree(data); | |
196 | } | |
197 | ||
198 | /** | |
199 | * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev | |
200 | * @dev: The device for which to allocate interrupts | |
201 | * @nvec: The number of interrupts to allocate | |
202 | * @write_msi_msg: Callback to write an interrupt message for @dev | |
203 | * | |
204 | * Returns: | |
205 | * Zero for success, or an error code in case of failure | |
206 | */ | |
207 | int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, | |
208 | irq_write_msi_msg_t write_msi_msg) | |
209 | { | |
72f57f2f | 210 | int err; |
c09fcc4b | 211 | |
fc22e7db TG |
212 | err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
213 | if (err) | |
214 | return err; | |
c09fcc4b | 215 | |
b330ff9f | 216 | err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1); |
c09fcc4b | 217 | if (err) |
653b50c5 | 218 | platform_msi_free_priv_data(dev); |
c09fcc4b | 219 | |
c09fcc4b MZ |
220 | return err; |
221 | } | |
bb1a7931 | 222 | EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs); |
c09fcc4b MZ |
223 | |
224 | /** | |
225 | * platform_msi_domain_free_irqs - Free MSI interrupts for @dev | |
226 | * @dev: The device for which to free interrupts | |
227 | */ | |
228 | void platform_msi_domain_free_irqs(struct device *dev) | |
229 | { | |
b330ff9f | 230 | msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); |
fc22e7db | 231 | platform_msi_free_priv_data(dev); |
c09fcc4b | 232 | } |
bb1a7931 | 233 | EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs); |
552c494a MZ |
234 | |
235 | /** | |
236 | * platform_msi_get_host_data - Query the private data associated with | |
237 | * a platform-msi domain | |
238 | * @domain: The platform-msi domain | |
239 | * | |
9835cec6 TG |
240 | * Return: The private data provided when calling |
241 | * platform_msi_create_device_domain(). | |
552c494a MZ |
242 | */ |
243 | void *platform_msi_get_host_data(struct irq_domain *domain) | |
244 | { | |
245 | struct platform_msi_priv_data *data = domain->host_data; | |
9835cec6 | 246 | |
552c494a MZ |
247 | return data->host_data; |
248 | } | |
249 | ||
a80713fe TG |
250 | static struct lock_class_key platform_device_msi_lock_class; |
251 | ||
552c494a | 252 | /** |
9835cec6 | 253 | * __platform_msi_create_device_domain - Create a platform-msi device domain |
552c494a MZ |
254 | * |
255 | * @dev: The device generating the MSIs | |
256 | * @nvec: The number of MSIs that need to be allocated | |
3c652132 | 257 | * @is_tree: flag to indicate tree hierarchy |
552c494a MZ |
258 | * @write_msi_msg: Callback to write an interrupt message for @dev |
259 | * @ops: The hierarchy domain operations to use | |
260 | * @host_data: Private data associated to this domain | |
261 | * | |
9835cec6 TG |
262 | * Return: An irqdomain for @nvec interrupts on success, NULL in case of error. |
263 | * | |
264 | * This is for interrupt domains which stack on a platform-msi domain | |
265 | * created by platform_msi_create_irq_domain(). @dev->msi.domain points to | |
266 | * that platform-msi domain which is the parent for the new domain. | |
552c494a MZ |
267 | */ |
268 | struct irq_domain * | |
1f83515b MZ |
269 | __platform_msi_create_device_domain(struct device *dev, |
270 | unsigned int nvec, | |
271 | bool is_tree, | |
272 | irq_write_msi_msg_t write_msi_msg, | |
273 | const struct irq_domain_ops *ops, | |
274 | void *host_data) | |
552c494a MZ |
275 | { |
276 | struct platform_msi_priv_data *data; | |
277 | struct irq_domain *domain; | |
278 | int err; | |
279 | ||
fc22e7db TG |
280 | err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
281 | if (err) | |
552c494a MZ |
282 | return NULL; |
283 | ||
a80713fe TG |
284 | /* |
285 | * Use a separate lock class for the MSI descriptor mutex on | |
286 | * platform MSI device domains because the descriptor mutex nests | |
287 | * into the domain mutex. See alloc/free below. | |
288 | */ | |
289 | lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class); | |
290 | ||
fc22e7db | 291 | data = dev->msi.data->platform_data; |
552c494a | 292 | data->host_data = host_data; |
34fff628 | 293 | domain = irq_domain_create_hierarchy(dev->msi.domain, 0, |
1f83515b | 294 | is_tree ? 0 : nvec, |
6943acf6 | 295 | dev->fwnode, ops, data); |
552c494a MZ |
296 | if (!domain) |
297 | goto free_priv; | |
298 | ||
91f90daa | 299 | platform_msi_set_proxy_dev(&data->arg); |
552c494a MZ |
300 | err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg); |
301 | if (err) | |
302 | goto free_domain; | |
303 | ||
304 | return domain; | |
305 | ||
306 | free_domain: | |
307 | irq_domain_remove(domain); | |
308 | free_priv: | |
fc22e7db | 309 | platform_msi_free_priv_data(dev); |
552c494a MZ |
310 | return NULL; |
311 | } | |
312 | ||
313 | /** | |
9835cec6 TG |
314 | * platform_msi_device_domain_free - Free interrupts associated with a platform-msi |
315 | * device domain | |
552c494a | 316 | * |
9835cec6 | 317 | * @domain: The platform-msi device domain |
552c494a | 318 | * @virq: The base irq from which to perform the free operation |
a80713fe | 319 | * @nr_irqs: How many interrupts to free from @virq |
552c494a | 320 | */ |
9835cec6 | 321 | void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq, |
a80713fe | 322 | unsigned int nr_irqs) |
552c494a MZ |
323 | { |
324 | struct platform_msi_priv_data *data = domain->host_data; | |
9835cec6 | 325 | |
a80713fe TG |
326 | msi_lock_descs(data->dev); |
327 | irq_domain_free_irqs_common(domain, virq, nr_irqs); | |
2f2940d1 | 328 | msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1); |
a80713fe | 329 | msi_unlock_descs(data->dev); |
552c494a MZ |
330 | } |
331 | ||
332 | /** | |
9835cec6 TG |
333 | * platform_msi_device_domain_alloc - Allocate interrupts associated with |
334 | * a platform-msi device domain | |
552c494a | 335 | * |
9835cec6 | 336 | * @domain: The platform-msi device domain |
552c494a | 337 | * @virq: The base irq from which to perform the allocate operation |
a80713fe | 338 | * @nr_irqs: How many interrupts to allocate from @virq |
552c494a MZ |
339 | * |
340 | * Return 0 on success, or an error code on failure. Must be called | |
341 | * with irq_domain_mutex held (which can only be done as part of a | |
342 | * top-level interrupt allocation). | |
343 | */ | |
9835cec6 TG |
344 | int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq, |
345 | unsigned int nr_irqs) | |
552c494a MZ |
346 | { |
347 | struct platform_msi_priv_data *data = domain->host_data; | |
a80713fe | 348 | struct device *dev = data->dev; |
552c494a | 349 | |
a80713fe | 350 | return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg); |
552c494a | 351 | } |