summaryrefslogtreecommitdiffstats
path: root/drivers/iommu/iommu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iommu/iommu.c')
-rw-r--r--drivers/iommu/iommu.c208
1 files changed, 121 insertions, 87 deletions
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 0639b92..ed8b048 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -30,6 +30,7 @@
#include <linux/notifier.h>
#include <linux/err.h>
#include <linux/pci.h>
+#include <linux/bitops.h>
#include <trace/events/iommu.h>
static struct kset *iommu_group_kset;
@@ -519,6 +520,9 @@ int iommu_group_id(struct iommu_group *group)
}
EXPORT_SYMBOL_GPL(iommu_group_id);
+static struct iommu_group *get_pci_alias_group(struct pci_dev *pdev,
+ unsigned long *devfns);
+
/*
* To consider a PCI device isolated, we require ACS to support Source
* Validation, Request Redirection, Completer Redirection, and Upstream
@@ -529,6 +533,86 @@ EXPORT_SYMBOL_GPL(iommu_group_id);
*/
#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
+/*
+ * For multifunction devices which are not isolated from each other, find
+ * all the other non-isolated functions and look for existing groups. For
+ * each function, we also need to look for aliases to or from other devices
+ * that may already have a group.
+ */
+static struct iommu_group *get_pci_function_alias_group(struct pci_dev *pdev,
+ unsigned long *devfns)
+{
+ struct pci_dev *tmp = NULL;
+ struct iommu_group *group;
+
+ if (!pdev->multifunction || pci_acs_enabled(pdev, REQ_ACS_FLAGS))
+ return 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 = get_pci_alias_group(tmp, devfns);
+ if (group) {
+ pci_dev_put(tmp);
+ return group;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Look for aliases to or from the given device for exisiting groups. The
+ * dma_alias_devfn only supports aliases on the same bus, therefore the search
+ * space is quite small (especially since we're really only looking at pcie
+ * device, and therefore only expect multiple slots on the root complex or
+ * downstream switch ports). It's conceivable though that a pair of
+ * multifunction devices could have aliases between them that would cause a
+ * loop. To prevent this, we use a bitmap to track where we've been.
+ */
+static struct iommu_group *get_pci_alias_group(struct pci_dev *pdev,
+ unsigned long *devfns)
+{
+ struct pci_dev *tmp = NULL;
+ struct iommu_group *group;
+
+ if (test_and_set_bit(pdev->devfn & 0xff, devfns))
+ return NULL;
+
+ group = iommu_group_get(&pdev->dev);
+ if (group)
+ return group;
+
+ for_each_pci_dev(tmp) {
+ if (tmp == pdev || tmp->bus != pdev->bus)
+ continue;
+
+ /* We alias them or they alias us */
+ if (((pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) &&
+ pdev->dma_alias_devfn == tmp->devfn) ||
+ ((tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) &&
+ tmp->dma_alias_devfn == pdev->devfn)) {
+
+ group = get_pci_alias_group(tmp, devfns);
+ if (group) {
+ pci_dev_put(tmp);
+ return group;
+ }
+
+ group = get_pci_function_alias_group(tmp, devfns);
+ if (group) {
+ pci_dev_put(tmp);
+ return group;
+ }
+ }
+ }
+
+ return NULL;
+}
+
struct group_for_pci_data {
struct pci_dev *pdev;
struct iommu_group *group;
@@ -557,7 +641,7 @@ 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;
+ u64 devfns[4] = { 0 };
/*
* Find the upstream DMA alias for the device. A device must not
@@ -591,76 +675,21 @@ static struct iommu_group *iommu_group_get_for_pci_dev(struct pci_dev *pdev)
}
/*
- * 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.
+ * Look for existing groups on device aliases. If we alias another
+ * device or another device aliases us, use the same group.
*/
- 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();
+ group = get_pci_alias_group(pdev, (unsigned long *)devfns);
+ if (group)
+ return group;
/*
- * Multifunction devices not supporting ACS share a group with other
- * similar devices in the same slot.
+ * Look for existing groups on non-isolated functions on the same
+ * slot and aliases of those funcions, if any. No need to clear
+ * the search bitmap, the tested devfns are still valid.
*/
- 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;
- }
- }
+ group = get_pci_function_alias_group(pdev, (unsigned long *)devfns);
+ if (group)
+ return group;
/* No shared group found, allocate new */
return iommu_group_alloc();
@@ -770,18 +799,26 @@ static int iommu_bus_notifier(struct notifier_block *nb,
return 0;
}
-static struct notifier_block iommu_bus_nb = {
- .notifier_call = iommu_bus_notifier,
-};
-
-static void iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
+static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
{
+ int err;
+ struct notifier_block *nb;
struct iommu_callback_data cb = {
.ops = ops,
};
- bus_register_notifier(bus, &iommu_bus_nb);
- bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
+ nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
+ if (!nb)
+ return -ENOMEM;
+
+ nb->notifier_call = iommu_bus_notifier;
+
+ err = bus_register_notifier(bus, nb);
+ if (err) {
+ kfree(nb);
+ return err;
+ }
+ return bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
}
/**
@@ -805,9 +842,7 @@ int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops)
bus->iommu_ops = ops;
/* Do IOMMU specific setup for this bus-type */
- iommu_bus_init(bus, ops);
-
- return 0;
+ return iommu_bus_init(bus, ops);
}
EXPORT_SYMBOL_GPL(bus_set_iommu);
@@ -817,6 +852,15 @@ bool iommu_present(struct bus_type *bus)
}
EXPORT_SYMBOL_GPL(iommu_present);
+bool iommu_capable(struct bus_type *bus, enum iommu_cap cap)
+{
+ if (!bus->iommu_ops || !bus->iommu_ops->capable)
+ return false;
+
+ return bus->iommu_ops->capable(cap);
+}
+EXPORT_SYMBOL_GPL(iommu_capable);
+
/**
* iommu_set_fault_handler() - set a fault handler for an iommu domain
* @domain: iommu domain
@@ -947,16 +991,6 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
}
EXPORT_SYMBOL_GPL(iommu_iova_to_phys);
-int iommu_domain_has_cap(struct iommu_domain *domain,
- unsigned long cap)
-{
- if (unlikely(domain->ops->domain_has_cap == NULL))
- return 0;
-
- return domain->ops->domain_has_cap(domain, cap);
-}
-EXPORT_SYMBOL_GPL(iommu_domain_has_cap);
-
static size_t iommu_pgsize(struct iommu_domain *domain,
unsigned long addr_merge, size_t size)
{
OpenPOWER on IntegriCloud