iommu/dma: Avoid PCI host bridge windows
authorRobin Murphy <robin.murphy@arm.com>
Mon, 12 Sep 2016 16:14:00 +0000 (17:14 +0100)
committerWill Deacon <will.deacon@arm.com>
Fri, 16 Sep 2016 08:34:22 +0000 (09:34 +0100)
With our DMA ops enabled for PCI devices, we should avoid allocating
IOVAs which a host bridge might misinterpret as peer-to-peer DMA and
lead to faults, corruption or other badness. To be safe, punch out holes
for all of the relevant host bridge's windows when initialising a DMA
domain for a PCI device.

CC: Marek Szyprowski <m.szyprowski@samsung.com>
CC: Inki Dae <inki.dae@samsung.com>
Reported-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
arch/arm64/mm/dma-mapping.c
drivers/gpu/drm/exynos/exynos_drm_iommu.h
drivers/iommu/dma-iommu.c
include/linux/dma-iommu.h

index c4284c432ae8c4d6ca0e83491aa4331af6b07d21..610d8e53011ed84e52927d421d1d90d708627183 100644 (file)
@@ -827,7 +827,7 @@ static bool do_iommu_attach(struct device *dev, const struct iommu_ops *ops,
         * then the IOMMU core will have already configured a group for this
         * device, and allocated the default domain for that group.
         */
-       if (!domain || iommu_dma_init_domain(domain, dma_base, size)) {
+       if (!domain || iommu_dma_init_domain(domain, dma_base, size, dev)) {
                pr_warn("Failed to set up IOMMU for device %s; retaining platform DMA ops\n",
                        dev_name(dev));
                return false;
index c8de4913fdbe80a3962ebca2744236f523e768b0..87f6b5672e1193a1df76bf3c09eb69abfd966e55 100644 (file)
@@ -66,7 +66,7 @@ static inline int __exynos_iommu_create_mapping(struct exynos_drm_private *priv,
        if (ret)
                goto free_domain;
 
-       ret = iommu_dma_init_domain(domain, start, size);
+       ret = iommu_dma_init_domain(domain, start, size, NULL);
        if (ret)
                goto put_cookie;
 
index 4329d18080cf325f505a9cebbad31f4a6adaf38d..c5ab8667e6f2e6e4c8390e298cc1a27be78e6bfb 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/iova.h>
 #include <linux/irq.h>
 #include <linux/mm.h>
+#include <linux/pci.h>
 #include <linux/scatterlist.h>
 #include <linux/vmalloc.h>
 
@@ -103,18 +104,38 @@ void iommu_put_dma_cookie(struct iommu_domain *domain)
 }
 EXPORT_SYMBOL(iommu_put_dma_cookie);
 
+static void iova_reserve_pci_windows(struct pci_dev *dev,
+               struct iova_domain *iovad)
+{
+       struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+       struct resource_entry *window;
+       unsigned long lo, hi;
+
+       resource_list_for_each_entry(window, &bridge->windows) {
+               if (resource_type(window->res) != IORESOURCE_MEM &&
+                   resource_type(window->res) != IORESOURCE_IO)
+                       continue;
+
+               lo = iova_pfn(iovad, window->res->start - window->offset);
+               hi = iova_pfn(iovad, window->res->end - window->offset);
+               reserve_iova(iovad, lo, hi);
+       }
+}
+
 /**
  * iommu_dma_init_domain - Initialise a DMA mapping domain
  * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
  * @base: IOVA at which the mappable address space starts
  * @size: Size of IOVA space
+ * @dev: Device the domain is being initialised for
  *
  * @base and @size should be exact multiples of IOMMU page granularity to
  * avoid rounding surprises. If necessary, we reserve the page at address 0
  * to ensure it is an invalid IOVA. It is safe to reinitialise a domain, but
  * any change which could make prior IOVAs invalid will fail.
  */
-int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, u64 size)
+int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
+               u64 size, struct device *dev)
 {
        struct iova_domain *iovad = cookie_iovad(domain);
        unsigned long order, base_pfn, end_pfn;
@@ -152,6 +173,8 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, u64 size
                iovad->dma_32bit_pfn = end_pfn;
        } else {
                init_iova_domain(iovad, 1UL << order, base_pfn, end_pfn);
+               if (dev && dev_is_pci(dev))
+                       iova_reserve_pci_windows(to_pci_dev(dev), iovad);
        }
        return 0;
 }
index 5ee806e41b5c556396aab1ee75478905a0ecb8ca..32c589062bd9e871c6d11af22ac2f41b953211cc 100644 (file)
@@ -30,7 +30,8 @@ int iommu_get_dma_cookie(struct iommu_domain *domain);
 void iommu_put_dma_cookie(struct iommu_domain *domain);
 
 /* Setup call for arch DMA mapping code */
-int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, u64 size);
+int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
+               u64 size, struct device *dev);
 
 /* General helpers for DMA-API <-> IOMMU-API interaction */
 int dma_direction_to_prot(enum dma_data_direction dir, bool coherent);