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