brintos

brintos / linux-shallow public Read only

0
0
Text · 8.9 KiB · 2aa69a2 Raw
374 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Virtual I/O topology4 *5 * The Virtual I/O Translation Table (VIOT) describes the topology of6 * para-virtual IOMMUs and the endpoints they manage. The OS uses it to7 * initialize devices in the right order, preventing endpoints from issuing DMA8 * 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 the12 * 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 the14 * device's IOMMU fwspec, allowing the DMA infrastructure to invoke the IOMMU15 * ops when the device driver configures DMA mappings. If an IOMMU exists and16 * hasn't yet been initialized, VIOT returns -EPROBE_DEFER to postpone probing17 * the device until the IOMMU is available.18 */19#define pr_fmt(fmt) "ACPI: VIOT: " fmt20 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 a94		 * 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 other236		 * 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 ACS252 *253 * If the VIOT does exist, ACS must be enabled. This cannot be254 * done in acpi_viot_init() which is called after the bus scan255 */256void __init acpi_viot_early_init(void)257{258#ifdef CONFIG_PCI259	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#endif268}269 270/**271 * acpi_viot_init - Parse the VIOT table272 *273 * Parse the VIOT table, prepare the list of endpoints to be used during DMA274 * 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 VIOT361 * @dev: the endpoint362 *363 * Return: 0 on success, <0 on failure364 */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}374