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 | { | |
e4084a16 MZ |
66 | return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
67 | info->chip, info->chip_data); | |
c09fcc4b MZ |
68 | } |
69 | #else | |
70 | #define platform_msi_set_desc NULL | |
71 | #define platform_msi_init NULL | |
72 | #endif | |
73 | ||
74 | static void platform_msi_update_dom_ops(struct msi_domain_info *info) | |
75 | { | |
76 | struct msi_domain_ops *ops = info->ops; | |
77 | ||
78 | BUG_ON(!ops); | |
79 | ||
80 | if (ops->msi_init == NULL) | |
81 | ops->msi_init = platform_msi_init; | |
82 | if (ops->set_desc == NULL) | |
83 | ops->set_desc = platform_msi_set_desc; | |
84 | } | |
85 | ||
86 | static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) | |
87 | { | |
e4084a16 | 88 | struct msi_desc *desc = irq_data_get_msi_desc(data); |
c09fcc4b MZ |
89 | struct platform_msi_priv_data *priv_data; |
90 | ||
91 | priv_data = desc->platform.msi_priv_data; | |
92 | ||
93 | priv_data->write_msg(desc, msg); | |
94 | } | |
95 | ||
96 | static void platform_msi_update_chip_ops(struct msi_domain_info *info) | |
97 | { | |
98 | struct irq_chip *chip = info->chip; | |
99 | ||
100 | BUG_ON(!chip); | |
101 | if (!chip->irq_mask) | |
102 | chip->irq_mask = irq_chip_mask_parent; | |
103 | if (!chip->irq_unmask) | |
104 | chip->irq_unmask = irq_chip_unmask_parent; | |
105 | if (!chip->irq_eoi) | |
106 | chip->irq_eoi = irq_chip_eoi_parent; | |
107 | if (!chip->irq_set_affinity) | |
108 | chip->irq_set_affinity = msi_domain_set_affinity; | |
109 | if (!chip->irq_write_msi_msg) | |
110 | chip->irq_write_msi_msg = platform_msi_write_msg; | |
111 | } | |
112 | ||
113 | static void platform_msi_free_descs(struct device *dev) | |
114 | { | |
115 | struct msi_desc *desc, *tmp; | |
116 | ||
117 | list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { | |
118 | list_del(&desc->list); | |
119 | free_msi_entry(desc); | |
120 | } | |
121 | } | |
122 | ||
123 | static int platform_msi_alloc_descs(struct device *dev, int nvec, | |
124 | struct platform_msi_priv_data *data) | |
125 | ||
126 | { | |
127 | int i; | |
128 | ||
129 | for (i = 0; i < nvec; i++) { | |
130 | struct msi_desc *desc; | |
131 | ||
132 | desc = alloc_msi_entry(dev); | |
133 | if (!desc) | |
134 | break; | |
135 | ||
136 | desc->platform.msi_priv_data = data; | |
137 | desc->platform.msi_index = i; | |
138 | desc->nvec_used = 1; | |
139 | ||
140 | list_add_tail(&desc->list, dev_to_msi_list(dev)); | |
141 | } | |
142 | ||
143 | if (i != nvec) { | |
144 | /* Clean up the mess */ | |
145 | platform_msi_free_descs(dev); | |
146 | ||
147 | return -ENOMEM; | |
148 | } | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | /** | |
154 | * platform_msi_create_irq_domain - Create a platform MSI interrupt domain | |
be5436c8 | 155 | * @fwnode: Optional fwnode of the interrupt controller |
c09fcc4b MZ |
156 | * @info: MSI domain info |
157 | * @parent: Parent irq domain | |
158 | * | |
159 | * Updates the domain and chip ops and creates a platform MSI | |
160 | * interrupt domain. | |
161 | * | |
162 | * Returns: | |
163 | * A domain pointer or NULL in case of failure. | |
164 | */ | |
be5436c8 | 165 | struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, |
c09fcc4b MZ |
166 | struct msi_domain_info *info, |
167 | struct irq_domain *parent) | |
168 | { | |
169 | struct irq_domain *domain; | |
170 | ||
171 | if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) | |
172 | platform_msi_update_dom_ops(info); | |
173 | if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) | |
174 | platform_msi_update_chip_ops(info); | |
175 | ||
be5436c8 | 176 | domain = msi_create_irq_domain(fwnode, info, parent); |
c09fcc4b MZ |
177 | if (domain) |
178 | domain->bus_token = DOMAIN_BUS_PLATFORM_MSI; | |
179 | ||
180 | return domain; | |
181 | } | |
182 | ||
183 | /** | |
184 | * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev | |
185 | * @dev: The device for which to allocate interrupts | |
186 | * @nvec: The number of interrupts to allocate | |
187 | * @write_msi_msg: Callback to write an interrupt message for @dev | |
188 | * | |
189 | * Returns: | |
190 | * Zero for success, or an error code in case of failure | |
191 | */ | |
192 | int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, | |
193 | irq_write_msi_msg_t write_msi_msg) | |
194 | { | |
195 | struct platform_msi_priv_data *priv_data; | |
196 | int err; | |
197 | ||
198 | /* | |
199 | * Limit the number of interrupts to 256 per device. Should we | |
200 | * need to bump this up, DEV_ID_SHIFT should be adjusted | |
201 | * accordingly (which would impact the max number of MSI | |
202 | * capable devices). | |
203 | */ | |
204 | if (!dev->msi_domain || !write_msi_msg || !nvec || | |
205 | nvec > (1 << (32 - DEV_ID_SHIFT))) | |
206 | return -EINVAL; | |
207 | ||
208 | if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { | |
209 | dev_err(dev, "Incompatible msi_domain, giving up\n"); | |
210 | return -EINVAL; | |
211 | } | |
212 | ||
213 | /* Already had a helping of MSI? Greed... */ | |
214 | if (!list_empty(dev_to_msi_list(dev))) | |
215 | return -EBUSY; | |
216 | ||
217 | priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); | |
218 | if (!priv_data) | |
219 | return -ENOMEM; | |
220 | ||
221 | priv_data->devid = ida_simple_get(&platform_msi_devid_ida, | |
222 | 0, 1 << DEV_ID_SHIFT, GFP_KERNEL); | |
223 | if (priv_data->devid < 0) { | |
224 | err = priv_data->devid; | |
225 | goto out_free_data; | |
226 | } | |
227 | ||
228 | priv_data->write_msg = write_msi_msg; | |
229 | ||
230 | err = platform_msi_alloc_descs(dev, nvec, priv_data); | |
231 | if (err) | |
232 | goto out_free_id; | |
233 | ||
234 | err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec); | |
235 | if (err) | |
236 | goto out_free_desc; | |
237 | ||
238 | return 0; | |
239 | ||
240 | out_free_desc: | |
241 | platform_msi_free_descs(dev); | |
242 | out_free_id: | |
243 | ida_simple_remove(&platform_msi_devid_ida, priv_data->devid); | |
244 | out_free_data: | |
245 | kfree(priv_data); | |
246 | ||
247 | return err; | |
248 | } | |
249 | ||
250 | /** | |
251 | * platform_msi_domain_free_irqs - Free MSI interrupts for @dev | |
252 | * @dev: The device for which to free interrupts | |
253 | */ | |
254 | void platform_msi_domain_free_irqs(struct device *dev) | |
255 | { | |
256 | struct msi_desc *desc; | |
257 | ||
258 | desc = first_msi_entry(dev); | |
259 | if (desc) { | |
260 | struct platform_msi_priv_data *data; | |
261 | ||
262 | data = desc->platform.msi_priv_data; | |
263 | ||
264 | ida_simple_remove(&platform_msi_devid_ida, data->devid); | |
265 | kfree(data); | |
266 | } | |
267 | ||
268 | msi_domain_free_irqs(dev->msi_domain, dev); | |
269 | platform_msi_free_descs(dev); | |
270 | } |