cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

viot.c (8861B)


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