Commit | Line | Data |
---|---|---|
3cf48554 JPB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Virtual I/O topology | |
4 | * | |
5 | * The Virtual I/O Translation Table (VIOT) describes the topology of | |
6 | * para-virtual IOMMUs and the endpoints they manage. The OS uses it to | |
7 | * initialize devices in the right order, preventing endpoints from issuing DMA | |
8 | * before their IOMMU is ready. | |
9 | * | |
10 | * When binding a driver to a device, before calling the device driver's probe() | |
11 | * method, the driver infrastructure calls dma_configure(). At that point the | |
12 | * VIOT driver looks for an IOMMU associated to the device in the VIOT table. | |
13 | * If an IOMMU exists and has been initialized, the VIOT driver initializes the | |
14 | * device's IOMMU fwspec, allowing the DMA infrastructure to invoke the IOMMU | |
15 | * ops when the device driver configures DMA mappings. If an IOMMU exists and | |
16 | * hasn't yet been initialized, VIOT returns -EPROBE_DEFER to postpone probing | |
17 | * the device until the IOMMU is available. | |
18 | */ | |
19 | #define pr_fmt(fmt) "ACPI: VIOT: " fmt | |
20 | ||
21 | #include <linux/acpi_viot.h> | |
3cf48554 JPB |
22 | #include <linux/fwnode.h> |
23 | #include <linux/iommu.h> | |
24 | #include <linux/list.h> | |
25 | #include <linux/pci.h> | |
26 | #include <linux/platform_device.h> | |
27 | ||
28 | struct viot_iommu { | |
29 | /* Node offset within the table */ | |
30 | unsigned int offset; | |
31 | struct fwnode_handle *fwnode; | |
32 | struct list_head list; | |
33 | }; | |
34 | ||
35 | struct viot_endpoint { | |
36 | union { | |
37 | /* PCI range */ | |
38 | struct { | |
39 | u16 segment_start; | |
40 | u16 segment_end; | |
41 | u16 bdf_start; | |
42 | u16 bdf_end; | |
43 | }; | |
44 | /* MMIO */ | |
45 | u64 address; | |
46 | }; | |
47 | u32 endpoint_id; | |
48 | struct viot_iommu *viommu; | |
49 | struct list_head list; | |
50 | }; | |
51 | ||
52 | static struct acpi_table_viot *viot; | |
53 | static LIST_HEAD(viot_iommus); | |
54 | static LIST_HEAD(viot_pci_ranges); | |
55 | static LIST_HEAD(viot_mmio_endpoints); | |
56 | ||
57 | static int __init viot_check_bounds(const struct acpi_viot_header *hdr) | |
58 | { | |
59 | struct acpi_viot_header *start, *end, *hdr_end; | |
60 | ||
61 | start = ACPI_ADD_PTR(struct acpi_viot_header, viot, | |
62 | max_t(size_t, sizeof(*viot), viot->node_offset)); | |
63 | end = ACPI_ADD_PTR(struct acpi_viot_header, viot, viot->header.length); | |
64 | hdr_end = ACPI_ADD_PTR(struct acpi_viot_header, hdr, sizeof(*hdr)); | |
65 | ||
66 | if (hdr < start || hdr_end > end) { | |
67 | pr_err(FW_BUG "Node pointer overflows\n"); | |
68 | return -EOVERFLOW; | |
69 | } | |
70 | if (hdr->length < sizeof(*hdr)) { | |
71 | pr_err(FW_BUG "Empty node\n"); | |
72 | return -EINVAL; | |
73 | } | |
74 | return 0; | |
75 | } | |
76 | ||
77 | static int __init viot_get_pci_iommu_fwnode(struct viot_iommu *viommu, | |
78 | u16 segment, u16 bdf) | |
79 | { | |
80 | struct pci_dev *pdev; | |
81 | struct fwnode_handle *fwnode; | |
82 | ||
83 | pdev = pci_get_domain_bus_and_slot(segment, PCI_BUS_NUM(bdf), | |
84 | bdf & 0xff); | |
85 | if (!pdev) { | |
86 | pr_err("Could not find PCI IOMMU\n"); | |
87 | return -ENODEV; | |
88 | } | |
89 | ||
b0f2fe5a | 90 | fwnode = dev_fwnode(&pdev->dev); |
3cf48554 JPB |
91 | if (!fwnode) { |
92 | /* | |
93 | * PCI devices aren't necessarily described by ACPI. Create a | |
94 | * fwnode so the IOMMU subsystem can identify this device. | |
95 | */ | |
96 | fwnode = acpi_alloc_fwnode_static(); | |
97 | if (!fwnode) { | |
98 | pci_dev_put(pdev); | |
99 | return -ENOMEM; | |
100 | } | |
101 | set_primary_fwnode(&pdev->dev, fwnode); | |
102 | } | |
b0f2fe5a | 103 | viommu->fwnode = dev_fwnode(&pdev->dev); |
3cf48554 JPB |
104 | pci_dev_put(pdev); |
105 | return 0; | |
106 | } | |
107 | ||
108 | static int __init viot_get_mmio_iommu_fwnode(struct viot_iommu *viommu, | |
109 | u64 address) | |
110 | { | |
111 | struct acpi_device *adev; | |
112 | struct resource res = { | |
113 | .start = address, | |
114 | .end = address, | |
115 | .flags = IORESOURCE_MEM, | |
116 | }; | |
117 | ||
118 | adev = acpi_resource_consumer(&res); | |
119 | if (!adev) { | |
120 | pr_err("Could not find MMIO IOMMU\n"); | |
121 | return -EINVAL; | |
122 | } | |
123 | viommu->fwnode = &adev->fwnode; | |
124 | return 0; | |
125 | } | |
126 | ||
127 | static struct viot_iommu * __init viot_get_iommu(unsigned int offset) | |
128 | { | |
129 | int ret; | |
130 | struct viot_iommu *viommu; | |
131 | struct acpi_viot_header *hdr = ACPI_ADD_PTR(struct acpi_viot_header, | |
132 | viot, offset); | |
133 | union { | |
134 | struct acpi_viot_virtio_iommu_pci pci; | |
135 | struct acpi_viot_virtio_iommu_mmio mmio; | |
136 | } *node = (void *)hdr; | |
137 | ||
138 | list_for_each_entry(viommu, &viot_iommus, list) | |
139 | if (viommu->offset == offset) | |
140 | return viommu; | |
141 | ||
142 | if (viot_check_bounds(hdr)) | |
143 | return NULL; | |
144 | ||
145 | viommu = kzalloc(sizeof(*viommu), GFP_KERNEL); | |
146 | if (!viommu) | |
147 | return NULL; | |
148 | ||
149 | viommu->offset = offset; | |
150 | switch (hdr->type) { | |
151 | case ACPI_VIOT_NODE_VIRTIO_IOMMU_PCI: | |
152 | if (hdr->length < sizeof(node->pci)) | |
153 | goto err_free; | |
154 | ||
155 | ret = viot_get_pci_iommu_fwnode(viommu, node->pci.segment, | |
156 | node->pci.bdf); | |
157 | break; | |
158 | case ACPI_VIOT_NODE_VIRTIO_IOMMU_MMIO: | |
159 | if (hdr->length < sizeof(node->mmio)) | |
160 | goto err_free; | |
161 | ||
162 | ret = viot_get_mmio_iommu_fwnode(viommu, | |
163 | node->mmio.base_address); | |
164 | break; | |
165 | default: | |
166 | ret = -EINVAL; | |
167 | } | |
168 | if (ret) | |
169 | goto err_free; | |
170 | ||
171 | list_add(&viommu->list, &viot_iommus); | |
172 | return viommu; | |
173 | ||
174 | err_free: | |
175 | kfree(viommu); | |
176 | return NULL; | |
177 | } | |
178 | ||
179 | static int __init viot_parse_node(const struct acpi_viot_header *hdr) | |
180 | { | |
181 | int ret = -EINVAL; | |
182 | struct list_head *list; | |
183 | struct viot_endpoint *ep; | |
184 | union { | |
185 | struct acpi_viot_mmio mmio; | |
186 | struct acpi_viot_pci_range pci; | |
187 | } *node = (void *)hdr; | |
188 | ||
189 | if (viot_check_bounds(hdr)) | |
190 | return -EINVAL; | |
191 | ||
192 | if (hdr->type == ACPI_VIOT_NODE_VIRTIO_IOMMU_PCI || | |
193 | hdr->type == ACPI_VIOT_NODE_VIRTIO_IOMMU_MMIO) | |
194 | return 0; | |
195 | ||
196 | ep = kzalloc(sizeof(*ep), GFP_KERNEL); | |
197 | if (!ep) | |
198 | return -ENOMEM; | |
199 | ||
200 | switch (hdr->type) { | |
201 | case ACPI_VIOT_NODE_PCI_RANGE: | |
202 | if (hdr->length < sizeof(node->pci)) { | |
203 | pr_err(FW_BUG "Invalid PCI node size\n"); | |
204 | goto err_free; | |
205 | } | |
206 | ||
207 | ep->segment_start = node->pci.segment_start; | |
208 | ep->segment_end = node->pci.segment_end; | |
209 | ep->bdf_start = node->pci.bdf_start; | |
210 | ep->bdf_end = node->pci.bdf_end; | |
211 | ep->endpoint_id = node->pci.endpoint_start; | |
212 | ep->viommu = viot_get_iommu(node->pci.output_node); | |
213 | list = &viot_pci_ranges; | |
214 | break; | |
215 | case ACPI_VIOT_NODE_MMIO: | |
216 | if (hdr->length < sizeof(node->mmio)) { | |
217 | pr_err(FW_BUG "Invalid MMIO node size\n"); | |
218 | goto err_free; | |
219 | } | |
220 | ||
221 | ep->address = node->mmio.base_address; | |
222 | ep->endpoint_id = node->mmio.endpoint; | |
223 | ep->viommu = viot_get_iommu(node->mmio.output_node); | |
224 | list = &viot_mmio_endpoints; | |
225 | break; | |
226 | default: | |
227 | pr_warn("Unsupported node %x\n", hdr->type); | |
228 | ret = 0; | |
229 | goto err_free; | |
230 | } | |
231 | ||
232 | if (!ep->viommu) { | |
233 | pr_warn("No IOMMU node found\n"); | |
234 | /* | |
235 | * A future version of the table may use the node for other | |
236 | * purposes. Keep parsing. | |
237 | */ | |
238 | ret = 0; | |
239 | goto err_free; | |
240 | } | |
241 | ||
242 | list_add(&ep->list, list); | |
243 | return 0; | |
244 | ||
245 | err_free: | |
246 | kfree(ep); | |
247 | return ret; | |
248 | } | |
249 | ||
3dcb861d EA |
250 | /** |
251 | * acpi_viot_early_init - Test the presence of VIOT and enable ACS | |
252 | * | |
253 | * If the VIOT does exist, ACS must be enabled. This cannot be | |
254 | * done in acpi_viot_init() which is called after the bus scan | |
255 | */ | |
256 | void __init acpi_viot_early_init(void) | |
257 | { | |
258 | #ifdef CONFIG_PCI | |
259 | acpi_status status; | |
260 | struct acpi_table_header *hdr; | |
261 | ||
262 | status = acpi_get_table(ACPI_SIG_VIOT, 0, &hdr); | |
263 | if (ACPI_FAILURE(status)) | |
264 | return; | |
265 | pci_request_acs(); | |
266 | acpi_put_table(hdr); | |
267 | #endif | |
268 | } | |
269 | ||
3cf48554 JPB |
270 | /** |
271 | * acpi_viot_init - Parse the VIOT table | |
272 | * | |
273 | * Parse the VIOT table, prepare the list of endpoints to be used during DMA | |
274 | * setup of devices. | |
275 | */ | |
276 | void __init acpi_viot_init(void) | |
277 | { | |
278 | int i; | |
279 | acpi_status status; | |
280 | struct acpi_table_header *hdr; | |
281 | struct acpi_viot_header *node; | |
282 | ||
283 | status = acpi_get_table(ACPI_SIG_VIOT, 0, &hdr); | |
284 | if (ACPI_FAILURE(status)) { | |
285 | if (status != AE_NOT_FOUND) { | |
286 | const char *msg = acpi_format_exception(status); | |
287 | ||
288 | pr_err("Failed to get table, %s\n", msg); | |
289 | } | |
290 | return; | |
291 | } | |
292 | ||
293 | viot = (void *)hdr; | |
294 | ||
295 | node = ACPI_ADD_PTR(struct acpi_viot_header, viot, viot->node_offset); | |
296 | for (i = 0; i < viot->node_count; i++) { | |
297 | if (viot_parse_node(node)) | |
298 | return; | |
299 | ||
300 | node = ACPI_ADD_PTR(struct acpi_viot_header, node, | |
301 | node->length); | |
302 | } | |
303 | ||
304 | acpi_put_table(hdr); | |
305 | } | |
306 | ||
307 | static int viot_dev_iommu_init(struct device *dev, struct viot_iommu *viommu, | |
308 | u32 epid) | |
309 | { | |
310 | const struct iommu_ops *ops; | |
311 | ||
312 | if (!viommu) | |
313 | return -ENODEV; | |
314 | ||
315 | /* We're not translating ourself */ | |
b0f2fe5a | 316 | if (device_match_fwnode(dev, viommu->fwnode)) |
3cf48554 JPB |
317 | return -EINVAL; |
318 | ||
319 | ops = iommu_ops_from_fwnode(viommu->fwnode); | |
320 | if (!ops) | |
321 | return IS_ENABLED(CONFIG_VIRTIO_IOMMU) ? | |
322 | -EPROBE_DEFER : -ENODEV; | |
323 | ||
324 | return acpi_iommu_fwspec_init(dev, epid, viommu->fwnode, ops); | |
325 | } | |
326 | ||
327 | static int viot_pci_dev_iommu_init(struct pci_dev *pdev, u16 dev_id, void *data) | |
328 | { | |
329 | u32 epid; | |
330 | struct viot_endpoint *ep; | |
47d26684 | 331 | struct device *aliased_dev = data; |
3cf48554 JPB |
332 | u32 domain_nr = pci_domain_nr(pdev->bus); |
333 | ||
334 | list_for_each_entry(ep, &viot_pci_ranges, list) { | |
335 | if (domain_nr >= ep->segment_start && | |
336 | domain_nr <= ep->segment_end && | |
337 | dev_id >= ep->bdf_start && | |
338 | dev_id <= ep->bdf_end) { | |
339 | epid = ((domain_nr - ep->segment_start) << 16) + | |
340 | dev_id - ep->bdf_start + ep->endpoint_id; | |
341 | ||
47d26684 | 342 | return viot_dev_iommu_init(aliased_dev, ep->viommu, |
3cf48554 JPB |
343 | epid); |
344 | } | |
345 | } | |
346 | return -ENODEV; | |
347 | } | |
348 | ||
349 | static int viot_mmio_dev_iommu_init(struct platform_device *pdev) | |
350 | { | |
351 | struct resource *mem; | |
352 | struct viot_endpoint *ep; | |
353 | ||
354 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
355 | if (!mem) | |
356 | return -ENODEV; | |
357 | ||
358 | list_for_each_entry(ep, &viot_mmio_endpoints, list) { | |
359 | if (ep->address == mem->start) | |
360 | return viot_dev_iommu_init(&pdev->dev, ep->viommu, | |
361 | ep->endpoint_id); | |
362 | } | |
363 | return -ENODEV; | |
364 | } | |
365 | ||
366 | /** | |
367 | * viot_iommu_configure - Setup IOMMU ops for an endpoint described by VIOT | |
368 | * @dev: the endpoint | |
369 | * | |
370 | * Return: 0 on success, <0 on failure | |
371 | */ | |
372 | int viot_iommu_configure(struct device *dev) | |
373 | { | |
374 | if (dev_is_pci(dev)) | |
375 | return pci_for_each_dma_alias(to_pci_dev(dev), | |
47d26684 | 376 | viot_pci_dev_iommu_init, dev); |
3cf48554 JPB |
377 | else if (dev_is_platform(dev)) |
378 | return viot_mmio_dev_iommu_init(to_platform_device(dev)); | |
379 | return -ENODEV; | |
380 | } |