Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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
 28struct viot_iommu {
 29	/* Node offset within the table */
 30	unsigned int			offset;
 31	struct fwnode_handle		*fwnode;
 32	struct list_head		list;
 33};
 34
 35struct 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
 52static struct acpi_table_viot *viot;
 53static LIST_HEAD(viot_iommus);
 54static LIST_HEAD(viot_pci_ranges);
 55static LIST_HEAD(viot_mmio_endpoints);
 56
 57static 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
 77static 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
 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(pdev);
 99			return -ENOMEM;
100		}
101		set_primary_fwnode(&pdev->dev, fwnode);
102	}
103	viommu->fwnode = dev_fwnode(&pdev->dev);
104	pci_dev_put(pdev);
105	return 0;
106}
107
108static 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
127static 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
174err_free:
175	kfree(viommu);
176	return NULL;
177}
178
179static 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
245err_free:
246	kfree(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 */
256void __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
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 */
276void __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
307static int viot_dev_iommu_init(struct device *dev, struct viot_iommu *viommu,
308			       u32 epid)
309{
310	if (!viommu || !IS_ENABLED(CONFIG_VIRTIO_IOMMU))
311		return -ENODEV;
312
313	/* We're not translating ourself */
314	if (device_match_fwnode(dev, viommu->fwnode))
315		return -EINVAL;
316
317	return acpi_iommu_fwspec_init(dev, epid, viommu->fwnode);
318}
319
320static int viot_pci_dev_iommu_init(struct pci_dev *pdev, u16 dev_id, void *data)
321{
322	u32 epid;
323	struct viot_endpoint *ep;
324	struct device *aliased_dev = data;
325	u32 domain_nr = pci_domain_nr(pdev->bus);
326
327	list_for_each_entry(ep, &viot_pci_ranges, list) {
328		if (domain_nr >= ep->segment_start &&
329		    domain_nr <= ep->segment_end &&
330		    dev_id >= ep->bdf_start &&
331		    dev_id <= ep->bdf_end) {
332			epid = ((domain_nr - ep->segment_start) << 16) +
333				dev_id - ep->bdf_start + ep->endpoint_id;
334
335			return viot_dev_iommu_init(aliased_dev, ep->viommu,
336						   epid);
337		}
338	}
339	return -ENODEV;
340}
341
342static int viot_mmio_dev_iommu_init(struct platform_device *pdev)
343{
344	struct resource *mem;
345	struct viot_endpoint *ep;
346
347	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
348	if (!mem)
349		return -ENODEV;
350
351	list_for_each_entry(ep, &viot_mmio_endpoints, list) {
352		if (ep->address == mem->start)
353			return viot_dev_iommu_init(&pdev->dev, ep->viommu,
354						   ep->endpoint_id);
355	}
356	return -ENODEV;
357}
358
359/**
360 * viot_iommu_configure - Setup IOMMU ops for an endpoint described by VIOT
361 * @dev: the endpoint
362 *
363 * Return: 0 on success, <0 on failure
364 */
365int viot_iommu_configure(struct device *dev)
366{
367	if (dev_is_pci(dev))
368		return pci_for_each_dma_alias(to_pci_dev(dev),
369					      viot_pci_dev_iommu_init, dev);
370	else if (dev_is_platform(dev))
371		return viot_mmio_dev_iommu_init(to_platform_device(dev));
372	return -ENODEV;
373}