diff options
Diffstat (limited to 'drivers/iommu/iommu.c')
-rw-r--r-- | drivers/iommu/iommu.c | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index e5555fcfe703..d061c8677a81 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -29,6 +29,7 @@ #include <linux/idr.h> #include <linux/notifier.h> #include <linux/err.h> +#include <linux/pci.h> #include <trace/events/iommu.h> static struct kset *iommu_group_kset; @@ -514,6 +515,187 @@ int iommu_group_id(struct iommu_group *group) } EXPORT_SYMBOL_GPL(iommu_group_id); +/* + * To consider a PCI device isolated, we require ACS to support Source + * Validation, Request Redirection, Completer Redirection, and Upstream + * Forwarding. This effectively means that devices cannot spoof their + * requester ID, requests and completions cannot be redirected, and all + * transactions are forwarded upstream, even as it passes through a + * bridge where the target device is downstream. + */ +#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF) + +struct group_for_pci_data { + struct pci_dev *pdev; + struct iommu_group *group; +}; + +/* + * DMA alias iterator callback, return the last seen device. Stop and return + * the IOMMU group if we find one along the way. + */ +static int get_pci_alias_or_group(struct pci_dev *pdev, u16 alias, void *opaque) +{ + struct group_for_pci_data *data = opaque; + + data->pdev = pdev; + data->group = iommu_group_get(&pdev->dev); + + return data->group != NULL; +} + +/* + * Use standard PCI bus topology, isolation features, and DMA alias quirks + * to find or create an IOMMU group for a device. + */ +static struct iommu_group *iommu_group_get_for_pci_dev(struct pci_dev *pdev) +{ + struct group_for_pci_data data; + struct pci_bus *bus; + struct iommu_group *group = NULL; + struct pci_dev *tmp; + + /* + * Find the upstream DMA alias for the device. A device must not + * be aliased due to topology in order to have its own IOMMU group. + * If we find an alias along the way that already belongs to a + * group, use it. + */ + if (pci_for_each_dma_alias(pdev, get_pci_alias_or_group, &data)) + return data.group; + + pdev = data.pdev; + + /* + * Continue upstream from the point of minimum IOMMU granularity + * due to aliases to the point where devices are protected from + * peer-to-peer DMA by PCI ACS. Again, if we find an existing + * group, use it. + */ + for (bus = pdev->bus; !pci_is_root_bus(bus); bus = bus->parent) { + if (!bus->self) + continue; + + if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS)) + break; + + pdev = bus->self; + + group = iommu_group_get(&pdev->dev); + if (group) + return group; + } + + /* + * Next we need to consider DMA alias quirks. If one device aliases + * to another, they should be grouped together. It's theoretically + * possible that aliases could create chains of devices where each + * device aliases another device. If we then factor in multifunction + * ACS grouping requirements, each alias could incorporate a new slot + * with multiple functions, each with aliases. This is all extremely + * unlikely as DMA alias quirks are typically only used for PCIe + * devices where we usually have a single slot per bus. Furthermore, + * the alias quirk is usually to another function within the slot + * (and ACS multifunction is not supported) or to a different slot + * that doesn't physically exist. The likely scenario is therefore + * that everything on the bus gets grouped together. To reduce the + * problem space, share the IOMMU group for all devices on the bus + * if a DMA alias quirk is present on the bus. + */ + tmp = NULL; + for_each_pci_dev(tmp) { + if (tmp->bus != pdev->bus || + !(tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN)) + continue; + + pci_dev_put(tmp); + tmp = NULL; + + /* We have an alias quirk, search for an existing group */ + for_each_pci_dev(tmp) { + struct iommu_group *group_tmp; + + if (tmp->bus != pdev->bus) + continue; + + group_tmp = iommu_group_get(&tmp->dev); + if (!group) { + group = group_tmp; + continue; + } + + if (group_tmp) { + WARN_ON(group != group_tmp); + iommu_group_put(group_tmp); + } + } + + return group ? group : iommu_group_alloc(); + } + + /* + * Non-multifunction devices or multifunction devices supporting + * ACS get their own group. + */ + if (!pdev->multifunction || pci_acs_enabled(pdev, REQ_ACS_FLAGS)) + return iommu_group_alloc(); + + /* + * Multifunction devices not supporting ACS share a group with other + * similar devices in the same slot. + */ + tmp = NULL; + for_each_pci_dev(tmp) { + if (tmp == pdev || tmp->bus != pdev->bus || + PCI_SLOT(tmp->devfn) != PCI_SLOT(pdev->devfn) || + pci_acs_enabled(tmp, REQ_ACS_FLAGS)) + continue; + + group = iommu_group_get(&tmp->dev); + if (group) { + pci_dev_put(tmp); + return group; + } + } + + /* No shared group found, allocate new */ + return iommu_group_alloc(); +} + +/** + * iommu_group_get_for_dev - Find or create the IOMMU group for a device + * @dev: target device + * + * This function is intended to be called by IOMMU drivers and extended to + * support common, bus-defined algorithms when determining or creating the + * IOMMU group for a device. On success, the caller will hold a reference + * to the returned IOMMU group, which will already include the provided + * device. The reference should be released with iommu_group_put(). + */ +struct iommu_group *iommu_group_get_for_dev(struct device *dev) +{ + struct iommu_group *group = ERR_PTR(-EIO); + int ret; + + group = iommu_group_get(dev); + if (group) + return group; + + if (dev_is_pci(dev)) + group = iommu_group_get_for_pci_dev(to_pci_dev(dev)); + + if (IS_ERR(group)) + return group; + + ret = iommu_group_add_device(group, dev); + if (ret) { + iommu_group_put(group); + return ERR_PTR(ret); + } + + return group; +} + static int add_iommu_group(struct device *dev, void *data) { struct iommu_ops *ops = data; |