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> |
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(domain: segment, PCI_BUS_NUM(bdf), |
84 | devfn: bdf & 0xff); |
85 | if (!pdev) { |
86 | pr_err("Could not find PCI IOMMU\n" ); |
87 | return -ENODEV; |
88 | } |
89 | |
90 | fwnode = dev_fwnode(&pdev->dev); |
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(dev: pdev); |
99 | return -ENOMEM; |
100 | } |
101 | set_primary_fwnode(dev: &pdev->dev, fwnode); |
102 | } |
103 | viommu->fwnode = dev_fwnode(&pdev->dev); |
104 | pci_dev_put(dev: 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: &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(size: 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, segment: node->pci.segment, |
156 | bdf: 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 | address: node->mmio.base_address); |
164 | break; |
165 | default: |
166 | ret = -EINVAL; |
167 | } |
168 | if (ret) |
169 | goto err_free; |
170 | |
171 | list_add(new: &viommu->list, head: &viot_iommus); |
172 | return viommu; |
173 | |
174 | err_free: |
175 | kfree(objp: 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(size: 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(offset: 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(offset: 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(new: &ep->list, head: list); |
243 | return 0; |
244 | |
245 | err_free: |
246 | kfree(objp: ep); |
247 | return ret; |
248 | } |
249 | |
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, instance: 0, out_table: &hdr); |
263 | if (ACPI_FAILURE(status)) |
264 | return; |
265 | pci_request_acs(); |
266 | acpi_put_table(table: hdr); |
267 | #endif |
268 | } |
269 | |
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, instance: 0, out_table: &hdr); |
284 | if (ACPI_FAILURE(status)) { |
285 | if (status != AE_NOT_FOUND) { |
286 | const char *msg = acpi_format_exception(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(hdr: node)) |
298 | return; |
299 | |
300 | node = ACPI_ADD_PTR(struct acpi_viot_header, node, |
301 | node->length); |
302 | } |
303 | |
304 | acpi_put_table(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 */ |
316 | if (device_match_fwnode(dev, fwnode: viommu->fwnode)) |
317 | return -EINVAL; |
318 | |
319 | ops = iommu_ops_from_fwnode(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, id: epid, fwnode: 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; |
331 | struct device *aliased_dev = data; |
332 | u32 domain_nr = pci_domain_nr(bus: 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 | |
342 | return viot_dev_iommu_init(dev: aliased_dev, viommu: ep->viommu, |
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(dev: &pdev->dev, viommu: ep->viommu, |
361 | epid: 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), |
376 | fn: viot_pci_dev_iommu_init, data: dev); |
377 | else if (dev_is_platform(dev)) |
378 | return viot_mmio_dev_iommu_init(to_platform_device(dev)); |
379 | return -ENODEV; |
380 | } |
381 | |