]>
Commit | Line | Data |
---|---|---|
c09fcc4b MZ |
1 | /* |
2 | * MSI framework for platform devices | |
3 | * | |
4 | * Copyright (C) 2015 ARM Limited, All Rights Reserved. | |
5 | * Author: Marc Zyngier <marc.zyngier@arm.com> | |
6 | * | |
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. | |
10 | * | |
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. | |
15 | * | |
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/>. | |
18 | */ | |
19 | ||
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> | |
26 | ||
27 | #define DEV_ID_SHIFT 24 | |
ab6484ee | 28 | #define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) |
c09fcc4b MZ |
29 | |
30 | /* | |
31 | * Internal data structure containing a (made up, but unique) devid | |
32 | * and the callback to write the MSI message. | |
33 | */ | |
34 | struct platform_msi_priv_data { | |
35 | irq_write_msi_msg_t write_msg; | |
36 | int devid; | |
37 | }; | |
38 | ||
39 | /* The devid allocator */ | |
40 | static DEFINE_IDA(platform_msi_devid_ida); | |
41 | ||
42 | #ifdef GENERIC_MSI_DOMAIN_OPS | |
43 | /* | |
44 | * Convert an msi_desc to a globaly unique identifier (per-device | |
45 | * devid + msi_desc position in the msi_list). | |
46 | */ | |
47 | static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) | |
48 | { | |
49 | u32 devid; | |
50 | ||
51 | devid = desc->platform.msi_priv_data->devid; | |
52 | ||
53 | return (devid << (32 - DEV_ID_SHIFT)) | desc->platform.msi_index; | |
54 | } | |
55 | ||
56 | static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) | |
57 | { | |
58 | arg->desc = desc; | |
59 | arg->hwirq = platform_msi_calc_hwirq(desc); | |
60 | } | |
61 | ||
62 | static int platform_msi_init(struct irq_domain *domain, | |
63 | struct msi_domain_info *info, | |
64 | unsigned int virq, irq_hw_number_t hwirq, | |
65 | msi_alloc_info_t *arg) | |
66 | { | |
e4084a16 MZ |
67 | return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
68 | info->chip, info->chip_data); | |
c09fcc4b MZ |
69 | } |
70 | #else | |
71 | #define platform_msi_set_desc NULL | |
72 | #define platform_msi_init NULL | |
73 | #endif | |
74 | ||
75 | static void platform_msi_update_dom_ops(struct msi_domain_info *info) | |
76 | { | |
77 | struct msi_domain_ops *ops = info->ops; | |
78 | ||
79 | BUG_ON(!ops); | |
80 | ||
81 | if (ops->msi_init == NULL) | |
82 | ops->msi_init = platform_msi_init; | |
83 | if (ops->set_desc == NULL) | |
84 | ops->set_desc = platform_msi_set_desc; | |
85 | } | |
86 | ||
87 | static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) | |
88 | { | |
e4084a16 | 89 | struct msi_desc *desc = irq_data_get_msi_desc(data); |
c09fcc4b MZ |
90 | struct platform_msi_priv_data *priv_data; |
91 | ||
92 | priv_data = desc->platform.msi_priv_data; | |
93 | ||
94 | priv_data->write_msg(desc, msg); | |
95 | } | |
96 | ||
97 | static void platform_msi_update_chip_ops(struct msi_domain_info *info) | |
98 | { | |
99 | struct irq_chip *chip = info->chip; | |
100 | ||
101 | BUG_ON(!chip); | |
102 | if (!chip->irq_mask) | |
103 | chip->irq_mask = irq_chip_mask_parent; | |
104 | if (!chip->irq_unmask) | |
105 | chip->irq_unmask = irq_chip_unmask_parent; | |
106 | if (!chip->irq_eoi) | |
107 | chip->irq_eoi = irq_chip_eoi_parent; | |
108 | if (!chip->irq_set_affinity) | |
109 | chip->irq_set_affinity = msi_domain_set_affinity; | |
110 | if (!chip->irq_write_msi_msg) | |
111 | chip->irq_write_msi_msg = platform_msi_write_msg; | |
112 | } | |
113 | ||
ab6484ee | 114 | static void platform_msi_free_descs(struct device *dev, int base, int nvec) |
c09fcc4b MZ |
115 | { |
116 | struct msi_desc *desc, *tmp; | |
117 | ||
118 | list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { | |
ab6484ee MZ |
119 | if (desc->platform.msi_index >= base && |
120 | desc->platform.msi_index < (base + nvec)) { | |
121 | list_del(&desc->list); | |
122 | free_msi_entry(desc); | |
123 | } | |
c09fcc4b MZ |
124 | } |
125 | } | |
126 | ||
127 | static int platform_msi_alloc_descs(struct device *dev, int nvec, | |
128 | struct platform_msi_priv_data *data) | |
129 | ||
130 | { | |
ab6484ee MZ |
131 | struct msi_desc *desc; |
132 | int i, base = 0; | |
c09fcc4b | 133 | |
ab6484ee MZ |
134 | if (!list_empty(dev_to_msi_list(dev))) { |
135 | desc = list_last_entry(dev_to_msi_list(dev), | |
136 | struct msi_desc, list); | |
137 | base = desc->platform.msi_index + 1; | |
138 | } | |
c09fcc4b | 139 | |
ab6484ee | 140 | for (i = 0; i < nvec; i++) { |
c09fcc4b MZ |
141 | desc = alloc_msi_entry(dev); |
142 | if (!desc) | |
143 | break; | |
144 | ||
145 | desc->platform.msi_priv_data = data; | |
ab6484ee | 146 | desc->platform.msi_index = base + i; |
c09fcc4b MZ |
147 | desc->nvec_used = 1; |
148 | ||
149 | list_add_tail(&desc->list, dev_to_msi_list(dev)); | |
150 | } | |
151 | ||
152 | if (i != nvec) { | |
153 | /* Clean up the mess */ | |
ab6484ee | 154 | platform_msi_free_descs(dev, base, nvec); |
c09fcc4b MZ |
155 | |
156 | return -ENOMEM; | |
157 | } | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | /** | |
163 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain | |
be5436c8 | 164 | * @fwnode: Optional fwnode of the interrupt controller |
c09fcc4b MZ |
165 | * @info: MSI domain info |
166 | * @parent: Parent irq domain | |
167 | * | |
168 | * Updates the domain and chip ops and creates a platform MSI | |
169 | * interrupt domain. | |
170 | * | |
171 | * Returns: | |
172 | * A domain pointer or NULL in case of failure. | |
173 | */ | |
be5436c8 | 174 | struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, |
c09fcc4b MZ |
175 | struct msi_domain_info *info, |
176 | struct irq_domain *parent) | |
177 | { | |
178 | struct irq_domain *domain; | |
179 | ||
180 | if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) | |
181 | platform_msi_update_dom_ops(info); | |
182 | if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) | |
183 | platform_msi_update_chip_ops(info); | |
184 | ||
be5436c8 | 185 | domain = msi_create_irq_domain(fwnode, info, parent); |
c09fcc4b MZ |
186 | if (domain) |
187 | domain->bus_token = DOMAIN_BUS_PLATFORM_MSI; | |
188 | ||
189 | return domain; | |
190 | } | |
191 | ||
72f57f2f MZ |
192 | static struct platform_msi_priv_data * |
193 | platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, | |
194 | irq_write_msi_msg_t write_msi_msg) | |
c09fcc4b | 195 | { |
72f57f2f | 196 | struct platform_msi_priv_data *datap; |
c09fcc4b MZ |
197 | /* |
198 | * Limit the number of interrupts to 256 per device. Should we | |
199 | * need to bump this up, DEV_ID_SHIFT should be adjusted | |
200 | * accordingly (which would impact the max number of MSI | |
201 | * capable devices). | |
202 | */ | |
ab6484ee | 203 | if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) |
72f57f2f | 204 | return ERR_PTR(-EINVAL); |
c09fcc4b MZ |
205 | |
206 | if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { | |
207 | dev_err(dev, "Incompatible msi_domain, giving up\n"); | |
72f57f2f | 208 | return ERR_PTR(-EINVAL); |
c09fcc4b MZ |
209 | } |
210 | ||
211 | /* Already had a helping of MSI? Greed... */ | |
212 | if (!list_empty(dev_to_msi_list(dev))) | |
72f57f2f MZ |
213 | return ERR_PTR(-EBUSY); |
214 | ||
215 | datap = kzalloc(sizeof(*datap), GFP_KERNEL); | |
216 | if (!datap) | |
217 | return ERR_PTR(-ENOMEM); | |
218 | ||
219 | datap->devid = ida_simple_get(&platform_msi_devid_ida, | |
220 | 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); | |
221 | if (datap->devid < 0) { | |
222 | int err = datap->devid; | |
223 | kfree(datap); | |
224 | return ERR_PTR(err); | |
225 | } | |
c09fcc4b | 226 | |
72f57f2f | 227 | datap->write_msg = write_msi_msg; |
c09fcc4b | 228 | |
72f57f2f MZ |
229 | return datap; |
230 | } | |
231 | ||
232 | static void platform_msi_free_priv_data(struct platform_msi_priv_data *data) | |
233 | { | |
234 | ida_simple_remove(&platform_msi_devid_ida, data->devid); | |
235 | kfree(data); | |
236 | } | |
237 | ||
238 | /** | |
239 | * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev | |
240 | * @dev: The device for which to allocate interrupts | |
241 | * @nvec: The number of interrupts to allocate | |
242 | * @write_msi_msg: Callback to write an interrupt message for @dev | |
243 | * | |
244 | * Returns: | |
245 | * Zero for success, or an error code in case of failure | |
246 | */ | |
247 | int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, | |
248 | irq_write_msi_msg_t write_msi_msg) | |
249 | { | |
250 | struct platform_msi_priv_data *priv_data; | |
251 | int err; | |
c09fcc4b | 252 | |
72f57f2f MZ |
253 | priv_data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); |
254 | if (IS_ERR(priv_data)) | |
255 | return PTR_ERR(priv_data); | |
c09fcc4b MZ |
256 | |
257 | err = platform_msi_alloc_descs(dev, nvec, priv_data); | |
258 | if (err) | |
72f57f2f | 259 | goto out_free_priv_data; |
c09fcc4b MZ |
260 | |
261 | err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec); | |
262 | if (err) | |
263 | goto out_free_desc; | |
264 | ||
265 | return 0; | |
266 | ||
267 | out_free_desc: | |
ab6484ee | 268 | platform_msi_free_descs(dev, 0, nvec); |
72f57f2f MZ |
269 | out_free_priv_data: |
270 | platform_msi_free_priv_data(priv_data); | |
c09fcc4b MZ |
271 | |
272 | return err; | |
273 | } | |
274 | ||
275 | /** | |
276 | * platform_msi_domain_free_irqs - Free MSI interrupts for @dev | |
277 | * @dev: The device for which to free interrupts | |
278 | */ | |
279 | void platform_msi_domain_free_irqs(struct device *dev) | |
280 | { | |
72f57f2f MZ |
281 | if (!list_empty(dev_to_msi_list(dev))) { |
282 | struct msi_desc *desc; | |
c09fcc4b | 283 | |
72f57f2f MZ |
284 | desc = first_msi_entry(dev); |
285 | platform_msi_free_priv_data(desc->platform.msi_priv_data); | |
c09fcc4b MZ |
286 | } |
287 | ||
288 | msi_domain_free_irqs(dev->msi_domain, dev); | |
ab6484ee | 289 | platform_msi_free_descs(dev, 0, MAX_DEV_MSIS); |
c09fcc4b | 290 | } |