projects/linux·blob master · refs

340 lines · 9.3 KiB

1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (C) 2013-2015 ARM Limited, All Rights Reserved.
3// Author: Marc Zyngier <[email protected]>
4// Copyright (C) 2022 Linutronix GmbH
5// Copyright (C) 2022 Intel
6
7#include <linux/acpi_iort.h>
8#include <linux/fsl/mc.h>
9#include <linux/of_address.h>
10#include <linux/pci.h>
11
12#include "irq-gic-its-msi-parent.h"
13#include <linux/irqchip/irq-msi-lib.h>
14
15#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
16 MSI_FLAG_USE_DEF_CHIP_OPS | \
17 MSI_FLAG_PCI_MSI_MASK_PARENT)
18
19#define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
20 MSI_FLAG_PCI_MSIX | \
21 MSI_FLAG_MULTI_PCI_MSI | \
22 MSI_FLAG_PCI_MSIX_ALLOC_DYN)
23
24static int its_translate_frame_address(struct fwnode_handle *msi_node, phys_addr_t *pa)
25{
26 struct resource res;
27 int ret;
28
29 if (is_of_node(msi_node)) {
30 struct device_node *msi_np = to_of_node(msi_node);
31
32 ret = of_property_match_string(msi_np, "reg-names", "ns-translate");
33 if (ret < 0)
34 return ret;
35
36 ret = of_address_to_resource(msi_np, ret, &res);
37 if (ret)
38 return ret;
39 } else {
40 ret = iort_its_translate_pa(msi_node, &res.start);
41 }
42
43 *pa = res.start;
44 return 0;
45}
46
47#ifdef CONFIG_PCI_MSI
48static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
49{
50 int msi, msix, *count = data;
51
52 msi = max(pci_msi_vec_count(pdev), 0);
53 msix = max(pci_msix_vec_count(pdev), 0);
54 *count += max(msi, msix);
55
56 return 0;
57}
58
59static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
60{
61 struct pci_dev **alias_dev = data;
62
63 *alias_dev = pdev;
64
65 return 0;
66}
67
68static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
69 int nvec, msi_alloc_info_t *info)
70{
71 struct pci_dev *pdev, *alias_dev;
72 struct msi_domain_info *msi_info;
73 int alias_count = 0, minnvec = 1;
74
75 if (!dev_is_pci(dev))
76 return -EINVAL;
77
78 pdev = to_pci_dev(dev);
79 /*
80 * If pdev is downstream of any aliasing bridges, take an upper
81 * bound of how many other vectors could map to the same DevID.
82 * Also tell the ITS that the signalling will come from a proxy
83 * device, and that special allocation rules apply.
84 */
85 pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
86 if (alias_dev != pdev) {
87 if (alias_dev->subordinate)
88 pci_walk_bus(alias_dev->subordinate,
89 its_pci_msi_vec_count, &alias_count);
90 info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
91 }
92
93 /* ITS specific DeviceID, as the core ITS ignores dev. */
94 info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain->parent, pdev);
95
96 /*
97 * Always allocate a power of 2, and special case device 0 for
98 * broken systems where the DevID is not wired (and all devices
99 * appear as DevID 0). For that reason, we generously allocate a
100 * minimum of 32 MSIs for DevID 0. If you want more because all
101 * your devices are aliasing to DevID 0, consider fixing your HW.
102 */
103 nvec = max(nvec, alias_count);
104 if (!info->scratchpad[0].ul)
105 minnvec = 32;
106 nvec = max_t(int, minnvec, roundup_pow_of_two(nvec));
107
108 msi_info = msi_get_domain_info(domain->parent);
109 return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
110}
111
112static int its_v5_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
113 int nvec, msi_alloc_info_t *info)
114{
115 struct fwnode_handle *msi_node = NULL;
116 struct msi_domain_info *msi_info;
117 struct pci_dev *pdev;
118 phys_addr_t pa;
119 u32 rid;
120 int ret;
121
122 if (!dev_is_pci(dev))
123 return -EINVAL;
124
125 pdev = to_pci_dev(dev);
126
127 rid = pci_msi_map_rid_ctlr_node(domain->parent, pdev, &msi_node);
128 if (!msi_node)
129 return -ENODEV;
130
131 ret = its_translate_frame_address(msi_node, &pa);
132 if (ret)
133 return -ENODEV;
134
135 fwnode_handle_put(msi_node);
136
137 /* ITS specific DeviceID */
138 info->scratchpad[0].ul = rid;
139 /* ITS translate frame physical address */
140 info->scratchpad[1].ul = pa;
141
142 /* Always allocate power of two vectors */
143 nvec = roundup_pow_of_two(nvec);
144
145 msi_info = msi_get_domain_info(domain->parent);
146 return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
147}
148#else /* CONFIG_PCI_MSI */
149#define its_pci_msi_prepare NULL
150#define its_v5_pci_msi_prepare NULL
151#endif /* !CONFIG_PCI_MSI */
152
153static int of_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev, u32 *dev_id,
154 phys_addr_t *pa)
155{
156 struct device_node *msi_ctrl = NULL;
157 struct of_phandle_args msi_spec = {};
158 struct of_phandle_iterator it;
159 int ret;
160
161 /* Suck the DeviceID out of the msi-parent property */
162 of_for_each_phandle(&it, ret, dev->of_node, "msi-parent", "#msi-cells", -1) {
163 /* GICv5 ITS domain matches the MSI controller node parent */
164 struct device_node *np __free(device_node) = pa ? of_get_parent(it.node)
165 : of_node_get(it.node);
166
167 if (np == irq_domain_get_of_node(domain)) {
168 u32 args;
169
170 if (WARN_ON(of_phandle_iterator_args(&it, &args, 1) != 1))
171 ret = -EINVAL;
172
173 if (!ret && pa)
174 ret = its_translate_frame_address(of_fwnode_handle(it.node), pa);
175
176 if (!ret)
177 *dev_id = args;
178
179 of_node_put(it.node);
180 return ret;
181 }
182 }
183
184 ret = of_map_msi_id(dev->of_node, dev->id, &msi_ctrl, &msi_spec);
185 if (!ret)
186 *dev_id = msi_spec.args[0];
187 of_node_put(msi_spec.np);
188 return ret;
189}
190
191static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
192 int nvec, msi_alloc_info_t *info)
193{
194 struct msi_domain_info *msi_info;
195 u32 dev_id;
196 int ret = 0;
197
198 if (dev_is_fsl_mc(dev))
199 dev_id = fsl_mc_get_msi_id(dev);
200 else if (dev->of_node)
201 ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, NULL);
202 else
203 ret = iort_pmsi_get_msi_info(dev, &dev_id, NULL);
204 if (ret)
205 return ret;
206
207 /* ITS specific DeviceID, as the core ITS ignores dev. */
208 info->scratchpad[0].ul = dev_id;
209
210 /* Allocate at least 32 MSIs, and always as a power of 2 */
211 nvec = max_t(int, 32, roundup_pow_of_two(nvec));
212
213 msi_info = msi_get_domain_info(domain->parent);
214 return msi_info->ops->msi_prepare(domain->parent,
215 dev, nvec, info);
216}
217
218static int its_v5_pmsi_prepare(struct irq_domain *domain, struct device *dev,
219 int nvec, msi_alloc_info_t *info)
220{
221 struct msi_domain_info *msi_info;
222 phys_addr_t pa;
223 u32 dev_id;
224 int ret;
225
226 if (dev->of_node)
227 ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, &pa);
228 else
229 ret = iort_pmsi_get_msi_info(dev, &dev_id, &pa);
230 if (ret)
231 return ret;
232
233 /* ITS specific DeviceID */
234 info->scratchpad[0].ul = dev_id;
235 /* ITS translate frame physical address */
236 info->scratchpad[1].ul = pa;
237
238 /* Allocate always as a power of 2 */
239 nvec = roundup_pow_of_two(nvec);
240
241 msi_info = msi_get_domain_info(domain->parent);
242 return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
243}
244
245static void its_msi_teardown(struct irq_domain *domain, msi_alloc_info_t *info)
246{
247 struct msi_domain_info *msi_info;
248
249 msi_info = msi_get_domain_info(domain->parent);
250 msi_info->ops->msi_teardown(domain->parent, info);
251}
252
253static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
254 struct irq_domain *real_parent, struct msi_domain_info *info)
255{
256 if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
257 return false;
258
259 switch(info->bus_token) {
260 case DOMAIN_BUS_PCI_DEVICE_MSI:
261 case DOMAIN_BUS_PCI_DEVICE_MSIX:
262 /*
263 * FIXME: This probably should be done after a (not yet
264 * existing) post domain creation callback once to make
265 * support for dynamic post-enable MSI-X allocations
266 * work without having to reevaluate the domain size
267 * over and over. It is known already at allocation
268 * time via info->hwsize.
269 *
270 * That should work perfectly fine for MSI/MSI-X but needs
271 * some thoughts for purely software managed MSI domains
272 * where the index space is only limited artificially via
273 * %MSI_MAX_INDEX.
274 */
275 info->ops->msi_prepare = its_pci_msi_prepare;
276 info->ops->msi_teardown = its_msi_teardown;
277 break;
278 case DOMAIN_BUS_DEVICE_MSI:
279 case DOMAIN_BUS_WIRED_TO_MSI:
280 /*
281 * FIXME: See the above PCI prepare comment. The domain
282 * size is also known at domain creation time.
283 */
284 info->ops->msi_prepare = its_pmsi_prepare;
285 info->ops->msi_teardown = its_msi_teardown;
286 break;
287 default:
288 /* Confused. How did the lib return true? */
289 WARN_ON_ONCE(1);
290 return false;
291 }
292
293 return true;
294}
295
296static bool its_v5_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
297 struct irq_domain *real_parent, struct msi_domain_info *info)
298{
299 if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
300 return false;
301
302 switch (info->bus_token) {
303 case DOMAIN_BUS_PCI_DEVICE_MSI:
304 case DOMAIN_BUS_PCI_DEVICE_MSIX:
305 info->ops->msi_prepare = its_v5_pci_msi_prepare;
306 info->ops->msi_teardown = its_msi_teardown;
307 break;
308 case DOMAIN_BUS_DEVICE_MSI:
309 case DOMAIN_BUS_WIRED_TO_MSI:
310 info->ops->msi_prepare = its_v5_pmsi_prepare;
311 info->ops->msi_teardown = its_msi_teardown;
312 break;
313 default:
314 /* Confused. How did the lib return true? */
315 WARN_ON_ONCE(1);
316 return false;
317 }
318
319 return true;
320}
321
322const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
323 .supported_flags = ITS_MSI_FLAGS_SUPPORTED,
324 .required_flags = ITS_MSI_FLAGS_REQUIRED,
325 .chip_flags = MSI_CHIP_FLAG_SET_EOI,
326 .bus_select_token = DOMAIN_BUS_NEXUS,
327 .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
328 .prefix = "ITS-",
329 .init_dev_msi_info = its_init_dev_msi_info,
330};
331
332const struct msi_parent_ops gic_v5_its_msi_parent_ops = {
333 .supported_flags = ITS_MSI_FLAGS_SUPPORTED,
334 .required_flags = ITS_MSI_FLAGS_REQUIRED,
335 .chip_flags = MSI_CHIP_FLAG_SET_EOI,
336 .bus_select_token = DOMAIN_BUS_NEXUS,
337 .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
338 .prefix = "ITS-v5-",
339 .init_dev_msi_info = its_v5_init_dev_msi_info,
340};