]> Git Repo - linux.git/blobdiff - drivers/iommu/dma-iommu.c
iommu/dma: Convert to address-based allocation
[linux.git] / drivers / iommu / dma-iommu.c
index 48d36ce59efbfd6305f8e85e9ae85875f71215f8..8e0b684da1ba4f9bd8f7b773f805f87eafbd385b 100644 (file)
@@ -167,22 +167,99 @@ 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)
+/**
+ * iommu_dma_get_resv_regions - Reserved region driver helper
+ * @dev: Device from iommu_get_resv_regions()
+ * @list: Reserved region list from iommu_get_resv_regions()
+ *
+ * IOMMU drivers can use this to implement their .get_resv_regions callback
+ * for general non-IOMMU-specific reservations. Currently, this covers host
+ * bridge windows for PCI devices.
+ */
+void iommu_dma_get_resv_regions(struct device *dev, struct list_head *list)
 {
-       struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+       struct pci_host_bridge *bridge;
        struct resource_entry *window;
-       unsigned long lo, hi;
 
+       if (!dev_is_pci(dev))
+               return;
+
+       bridge = pci_find_host_bridge(to_pci_dev(dev)->bus);
        resource_list_for_each_entry(window, &bridge->windows) {
-               if (resource_type(window->res) != IORESOURCE_MEM &&
-                   resource_type(window->res) != IORESOURCE_IO)
+               struct iommu_resv_region *region;
+               phys_addr_t start;
+               size_t length;
+
+               if (resource_type(window->res) != IORESOURCE_MEM)
                        continue;
 
-               lo = iova_pfn(iovad, window->res->start - window->offset);
-               hi = iova_pfn(iovad, window->res->end - window->offset);
+               start = window->res->start - window->offset;
+               length = window->res->end - window->res->start + 1;
+               region = iommu_alloc_resv_region(start, length, 0,
+                               IOMMU_RESV_RESERVED);
+               if (!region)
+                       return;
+
+               list_add_tail(&region->list, list);
+       }
+}
+EXPORT_SYMBOL(iommu_dma_get_resv_regions);
+
+static int cookie_init_hw_msi_region(struct iommu_dma_cookie *cookie,
+               phys_addr_t start, phys_addr_t end)
+{
+       struct iova_domain *iovad = &cookie->iovad;
+       struct iommu_dma_msi_page *msi_page;
+       int i, num_pages;
+
+       start -= iova_offset(iovad, start);
+       num_pages = iova_align(iovad, end - start) >> iova_shift(iovad);
+
+       msi_page = kcalloc(num_pages, sizeof(*msi_page), GFP_KERNEL);
+       if (!msi_page)
+               return -ENOMEM;
+
+       for (i = 0; i < num_pages; i++) {
+               msi_page[i].phys = start;
+               msi_page[i].iova = start;
+               INIT_LIST_HEAD(&msi_page[i].list);
+               list_add(&msi_page[i].list, &cookie->msi_page_list);
+               start += iovad->granule;
+       }
+
+       return 0;
+}
+
+static int iova_reserve_iommu_regions(struct device *dev,
+               struct iommu_domain *domain)
+{
+       struct iommu_dma_cookie *cookie = domain->iova_cookie;
+       struct iova_domain *iovad = &cookie->iovad;
+       struct iommu_resv_region *region;
+       LIST_HEAD(resv_regions);
+       int ret = 0;
+
+       iommu_get_resv_regions(dev, &resv_regions);
+       list_for_each_entry(region, &resv_regions, list) {
+               unsigned long lo, hi;
+
+               /* We ARE the software that manages these! */
+               if (region->type == IOMMU_RESV_SW_MSI)
+                       continue;
+
+               lo = iova_pfn(iovad, region->start);
+               hi = iova_pfn(iovad, region->start + region->length - 1);
                reserve_iova(iovad, lo, hi);
+
+               if (region->type == IOMMU_RESV_MSI)
+                       ret = cookie_init_hw_msi_region(cookie, region->start,
+                                       region->start + region->length);
+               if (ret)
+                       break;
        }
+       iommu_put_resv_regions(dev, &resv_regions);
+
+       return ret;
 }
 
 /**
@@ -203,7 +280,6 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
        struct iommu_dma_cookie *cookie = domain->iova_cookie;
        struct iova_domain *iovad = &cookie->iovad;
        unsigned long order, base_pfn, end_pfn;
-       bool pci = dev && dev_is_pci(dev);
 
        if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE)
                return -EINVAL;
@@ -233,7 +309,7 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
         * leave the cache limit at the top of their range to save an rb_last()
         * traversal on every allocation.
         */
-       if (pci)
+       if (dev && dev_is_pci(dev))
                end_pfn &= DMA_BIT_MASK(32) >> order;
 
        /* start_pfn is always nonzero for an already-initialised domain */
@@ -248,12 +324,15 @@ int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
                 * area cache limit down for the benefit of the smaller one.
                 */
                iovad->dma_32bit_pfn = min(end_pfn, iovad->dma_32bit_pfn);
-       } else {
-               init_iova_domain(iovad, 1UL << order, base_pfn, end_pfn);
-               if (pci)
-                       iova_reserve_pci_windows(to_pci_dev(dev), iovad);
+
+               return 0;
        }
-       return 0;
+
+       init_iova_domain(iovad, 1UL << order, base_pfn, end_pfn);
+       if (!dev)
+               return 0;
+
+       return iova_reserve_iommu_regions(dev, domain);
 }
 EXPORT_SYMBOL(iommu_dma_init_domain);
 
@@ -286,12 +365,12 @@ int dma_info_to_prot(enum dma_data_direction dir, bool coherent,
        }
 }
 
-static struct iova *__alloc_iova(struct iommu_domain *domain, size_t size,
-               dma_addr_t dma_limit, struct device *dev)
+static dma_addr_t iommu_dma_alloc_iova(struct iommu_domain *domain,
+               size_t size, dma_addr_t dma_limit, struct device *dev)
 {
        struct iova_domain *iovad = cookie_iovad(domain);
        unsigned long shift = iova_shift(iovad);
-       unsigned long length = iova_align(iovad, size) >> shift;
+       unsigned long iova_len = size >> shift;
        struct iova *iova = NULL;
 
        if (domain->geometry.force_aperture)
@@ -299,35 +378,42 @@ static struct iova *__alloc_iova(struct iommu_domain *domain, size_t size,
 
        /* Try to get PCI devices a SAC address */
        if (dma_limit > DMA_BIT_MASK(32) && dev_is_pci(dev))
-               iova = alloc_iova(iovad, length, DMA_BIT_MASK(32) >> shift,
+               iova = alloc_iova(iovad, iova_len, DMA_BIT_MASK(32) >> shift,
                                  true);
        /*
         * Enforce size-alignment to be safe - there could perhaps be an
         * attribute to control this per-device, or at least per-domain...
         */
        if (!iova)
-               iova = alloc_iova(iovad, length, dma_limit >> shift, true);
+               iova = alloc_iova(iovad, iova_len, dma_limit >> shift, true);
 
-       return iova;
+       return (dma_addr_t)iova->pfn_lo << shift;
 }
 
-/* The IOVA allocator knows what we mapped, so just unmap whatever that was */
-static void __iommu_dma_unmap(struct iommu_domain *domain, dma_addr_t dma_addr)
+static void iommu_dma_free_iova(struct iommu_dma_cookie *cookie,
+               dma_addr_t iova, size_t size)
 {
-       struct iova_domain *iovad = cookie_iovad(domain);
-       unsigned long shift = iova_shift(iovad);
-       unsigned long pfn = dma_addr >> shift;
-       struct iova *iova = find_iova(iovad, pfn);
-       size_t size;
+       struct iova_domain *iovad = &cookie->iovad;
+       struct iova *iova_rbnode;
 
-       if (WARN_ON(!iova))
+       iova_rbnode = find_iova(iovad, iova_pfn(iovad, iova));
+       if (WARN_ON(!iova_rbnode))
                return;
 
-       size = iova_size(iova) << shift;
-       size -= iommu_unmap(domain, pfn << shift, size);
-       /* ...and if we can't, then something is horribly, horribly wrong */
-       WARN_ON(size > 0);
-       __free_iova(iovad, iova);
+       __free_iova(iovad, iova_rbnode);
+}
+
+static void __iommu_dma_unmap(struct iommu_domain *domain, dma_addr_t dma_addr,
+               size_t size)
+{
+       struct iova_domain *iovad = cookie_iovad(domain);
+       size_t iova_off = iova_offset(iovad, dma_addr);
+
+       dma_addr -= iova_off;
+       size = iova_align(iovad, size + iova_off);
+
+       WARN_ON(iommu_unmap(domain, dma_addr, size) != size);
+       iommu_dma_free_iova(domain->iova_cookie, dma_addr, size);
 }
 
 static void __iommu_dma_free_pages(struct page **pages, int count)
@@ -409,7 +495,7 @@ static struct page **__iommu_dma_alloc_pages(unsigned int count,
 void iommu_dma_free(struct device *dev, struct page **pages, size_t size,
                dma_addr_t *handle)
 {
-       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), *handle);
+       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), *handle, size);
        __iommu_dma_free_pages(pages, PAGE_ALIGN(size) >> PAGE_SHIFT);
        *handle = DMA_ERROR_CODE;
 }
@@ -437,11 +523,11 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
                void (*flush_page)(struct device *, const void *, phys_addr_t))
 {
        struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
-       struct iova_domain *iovad = cookie_iovad(domain);
-       struct iova *iova;
+       struct iommu_dma_cookie *cookie = domain->iova_cookie;
+       struct iova_domain *iovad = &cookie->iovad;
        struct page **pages;
        struct sg_table sgt;
-       dma_addr_t dma_addr;
+       dma_addr_t iova;
        unsigned int count, min_size, alloc_sizes = domain->pgsize_bitmap;
 
        *handle = DMA_ERROR_CODE;
@@ -461,11 +547,11 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
        if (!pages)
                return NULL;
 
-       iova = __alloc_iova(domain, size, dev->coherent_dma_mask, dev);
+       size = iova_align(iovad, size);
+       iova = iommu_dma_alloc_iova(domain, size, dev->coherent_dma_mask, dev);
        if (!iova)
                goto out_free_pages;
 
-       size = iova_align(iovad, size);
        if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL))
                goto out_free_iova;
 
@@ -481,19 +567,18 @@ struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
                sg_miter_stop(&miter);
        }
 
-       dma_addr = iova_dma_addr(iovad, iova);
-       if (iommu_map_sg(domain, dma_addr, sgt.sgl, sgt.orig_nents, prot)
+       if (iommu_map_sg(domain, iova, sgt.sgl, sgt.orig_nents, prot)
                        < size)
                goto out_free_sg;
 
-       *handle = dma_addr;
+       *handle = iova;
        sg_free_table(&sgt);
        return pages;
 
 out_free_sg:
        sg_free_table(&sgt);
 out_free_iova:
-       __free_iova(iovad, iova);
+       iommu_dma_free_iova(cookie, iova, size);
 out_free_pages:
        __iommu_dma_free_pages(pages, count);
        return NULL;
@@ -527,22 +612,22 @@ int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
 static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
                size_t size, int prot)
 {
-       dma_addr_t dma_addr;
        struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
-       struct iova_domain *iovad = cookie_iovad(domain);
+       struct iommu_dma_cookie *cookie = domain->iova_cookie;
+       struct iova_domain *iovad = &cookie->iovad;
        size_t iova_off = iova_offset(iovad, phys);
-       size_t len = iova_align(iovad, size + iova_off);
-       struct iova *iova = __alloc_iova(domain, len, dma_get_mask(dev), dev);
+       dma_addr_t iova;
 
+       size = iova_align(iovad, size + iova_off);
+       iova = iommu_dma_alloc_iova(domain, size, dma_get_mask(dev), dev);
        if (!iova)
                return DMA_ERROR_CODE;
 
-       dma_addr = iova_dma_addr(iovad, iova);
-       if (iommu_map(domain, dma_addr, phys - iova_off, len, prot)) {
-               __free_iova(iovad, iova);
+       if (iommu_map(domain, iova, phys - iova_off, size, prot)) {
+               iommu_dma_free_iova(cookie, iova, size);
                return DMA_ERROR_CODE;
        }
-       return dma_addr + iova_off;
+       return iova + iova_off;
 }
 
 dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
@@ -554,7 +639,7 @@ dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
 void iommu_dma_unmap_page(struct device *dev, dma_addr_t handle, size_t size,
                enum dma_data_direction dir, unsigned long attrs)
 {
-       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), handle);
+       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), handle, size);
 }
 
 /*
@@ -643,10 +728,10 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
                int nents, int prot)
 {
        struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
-       struct iova_domain *iovad = cookie_iovad(domain);
-       struct iova *iova;
+       struct iommu_dma_cookie *cookie = domain->iova_cookie;
+       struct iova_domain *iovad = &cookie->iovad;
        struct scatterlist *s, *prev = NULL;
-       dma_addr_t dma_addr;
+       dma_addr_t iova;
        size_t iova_len = 0;
        unsigned long mask = dma_get_seg_boundary(dev);
        int i;
@@ -690,7 +775,7 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
                prev = s;
        }
 
-       iova = __alloc_iova(domain, iova_len, dma_get_mask(dev), dev);
+       iova = iommu_dma_alloc_iova(domain, iova_len, dma_get_mask(dev), dev);
        if (!iova)
                goto out_restore_sg;
 
@@ -698,14 +783,13 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
         * We'll leave any physical concatenation to the IOMMU driver's
         * implementation - it knows better than we do.
         */
-       dma_addr = iova_dma_addr(iovad, iova);
-       if (iommu_map_sg(domain, dma_addr, sg, nents, prot) < iova_len)
+       if (iommu_map_sg(domain, iova, sg, nents, prot) < iova_len)
                goto out_free_iova;
 
-       return __finalise_sg(dev, sg, nents, dma_addr);
+       return __finalise_sg(dev, sg, nents, iova);
 
 out_free_iova:
-       __free_iova(iovad, iova);
+       iommu_dma_free_iova(cookie, iova, iova_len);
 out_restore_sg:
        __invalidate_sg(sg, nents);
        return 0;
@@ -714,11 +798,21 @@ out_restore_sg:
 void iommu_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
                enum dma_data_direction dir, unsigned long attrs)
 {
+       dma_addr_t start, end;
+       struct scatterlist *tmp;
+       int i;
        /*
         * The scatterlist segments are mapped into a single
         * contiguous IOVA allocation, so this is incredibly easy.
         */
-       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), sg_dma_address(sg));
+       start = sg_dma_address(sg);
+       for_each_sg(sg_next(sg), tmp, nents - 1, i) {
+               if (sg_dma_len(tmp) == 0)
+                       break;
+               sg = tmp;
+       }
+       end = sg_dma_address(sg) + sg_dma_len(sg);
+       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), start, end - start);
 }
 
 dma_addr_t iommu_dma_map_resource(struct device *dev, phys_addr_t phys,
@@ -731,7 +825,7 @@ dma_addr_t iommu_dma_map_resource(struct device *dev, phys_addr_t phys,
 void iommu_dma_unmap_resource(struct device *dev, dma_addr_t handle,
                size_t size, enum dma_data_direction dir, unsigned long attrs)
 {
-       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), handle);
+       __iommu_dma_unmap(iommu_get_domain_for_dev(dev), handle, size);
 }
 
 int iommu_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
@@ -745,7 +839,7 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
        struct iommu_dma_cookie *cookie = domain->iova_cookie;
        struct iommu_dma_msi_page *msi_page;
        struct iova_domain *iovad = cookie_iovad(domain);
-       struct iova *iova;
+       dma_addr_t iova;
        int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
        size_t size = cookie_msi_granule(cookie);
 
@@ -760,10 +854,10 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
 
        msi_page->phys = msi_addr;
        if (iovad) {
-               iova = __alloc_iova(domain, size, dma_get_mask(dev), dev);
+               iova = iommu_dma_alloc_iova(domain, size, dma_get_mask(dev), dev);
                if (!iova)
                        goto out_free_page;
-               msi_page->iova = iova_dma_addr(iovad, iova);
+               msi_page->iova = iova;
        } else {
                msi_page->iova = cookie->msi_iova;
                cookie->msi_iova += size;
@@ -778,7 +872,7 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
 
 out_free_iova:
        if (iovad)
-               __free_iova(iovad, iova);
+               iommu_dma_free_iova(cookie, iova, size);
        else
                cookie->msi_iova -= size;
 out_free_page:
This page took 0.041209 seconds and 4 git commands to generate.