dma-mapping: implement dmam_alloc_coherent using dmam_alloc_attrs
[linux-2.6-microblaze.git] / kernel / dma / mapping.c
index 58dec7a..f00544c 100644 (file)
@@ -5,8 +5,9 @@
  * Copyright (c) 2006  SUSE Linux Products GmbH
  * Copyright (c) 2006  Tejun Heo <teheo@suse.de>
  */
-
+#include <linux/memblock.h> /* for max_pfn */
 #include <linux/acpi.h>
+#include <linux/dma-direct.h>
 #include <linux/dma-noncoherent.h>
 #include <linux/export.h>
 #include <linux/gfp.h>
@@ -44,45 +45,6 @@ static int dmam_match(struct device *dev, void *res, void *match_data)
        return 0;
 }
 
-/**
- * dmam_alloc_coherent - Managed dma_alloc_coherent()
- * @dev: Device to allocate coherent memory for
- * @size: Size of allocation
- * @dma_handle: Out argument for allocated DMA handle
- * @gfp: Allocation flags
- *
- * Managed dma_alloc_coherent().  Memory allocated using this function
- * will be automatically released on driver detach.
- *
- * RETURNS:
- * Pointer to allocated memory on success, NULL on failure.
- */
-void *dmam_alloc_coherent(struct device *dev, size_t size,
-                          dma_addr_t *dma_handle, gfp_t gfp)
-{
-       struct dma_devres *dr;
-       void *vaddr;
-
-       dr = devres_alloc(dmam_release, sizeof(*dr), gfp);
-       if (!dr)
-               return NULL;
-
-       vaddr = dma_alloc_coherent(dev, size, dma_handle, gfp);
-       if (!vaddr) {
-               devres_free(dr);
-               return NULL;
-       }
-
-       dr->vaddr = vaddr;
-       dr->dma_handle = *dma_handle;
-       dr->size = size;
-
-       devres_add(dev, dr);
-
-       return vaddr;
-}
-EXPORT_SYMBOL(dmam_alloc_coherent);
-
 /**
  * dmam_free_coherent - Managed dma_free_coherent()
  * @dev: Device to free coherent memory for
@@ -223,7 +185,20 @@ int dma_common_get_sgtable(struct device *dev, struct sg_table *sgt,
                sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
        return ret;
 }
-EXPORT_SYMBOL(dma_common_get_sgtable);
+
+int dma_get_sgtable_attrs(struct device *dev, struct sg_table *sgt,
+               void *cpu_addr, dma_addr_t dma_addr, size_t size,
+               unsigned long attrs)
+{
+       const struct dma_map_ops *ops = get_dma_ops(dev);
+
+       if (!dma_is_direct(ops) && ops->get_sgtable)
+               return ops->get_sgtable(dev, sgt, cpu_addr, dma_addr, size,
+                                       attrs);
+       return dma_common_get_sgtable(dev, sgt, cpu_addr, dma_addr, size,
+                       attrs);
+}
+EXPORT_SYMBOL(dma_get_sgtable_attrs);
 
 /*
  * Create userspace mapping for the DMA-coherent memory.
@@ -261,88 +236,179 @@ int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
        return -ENXIO;
 #endif /* !CONFIG_ARCH_NO_COHERENT_DMA_MMAP */
 }
-EXPORT_SYMBOL(dma_common_mmap);
 
-#ifdef CONFIG_MMU
-static struct vm_struct *__dma_common_pages_remap(struct page **pages,
-                       size_t size, unsigned long vm_flags, pgprot_t prot,
-                       const void *caller)
+/**
+ * dma_mmap_attrs - map a coherent DMA allocation into user space
+ * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
+ * @vma: vm_area_struct describing requested user mapping
+ * @cpu_addr: kernel CPU-view address returned from dma_alloc_attrs
+ * @dma_addr: device-view address returned from dma_alloc_attrs
+ * @size: size of memory originally requested in dma_alloc_attrs
+ * @attrs: attributes of mapping properties requested in dma_alloc_attrs
+ *
+ * Map a coherent DMA buffer previously allocated by dma_alloc_attrs into user
+ * space.  The coherent DMA buffer must not be freed by the driver until the
+ * user space mapping has been released.
+ */
+int dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
+               void *cpu_addr, dma_addr_t dma_addr, size_t size,
+               unsigned long attrs)
 {
-       struct vm_struct *area;
+       const struct dma_map_ops *ops = get_dma_ops(dev);
 
-       area = get_vm_area_caller(size, vm_flags, caller);
-       if (!area)
-               return NULL;
+       if (!dma_is_direct(ops) && ops->mmap)
+               return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
+       return dma_common_mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
+}
+EXPORT_SYMBOL(dma_mmap_attrs);
 
-       if (map_vm_area(area, prot, pages)) {
-               vunmap(area->addr);
-               return NULL;
+#ifndef ARCH_HAS_DMA_GET_REQUIRED_MASK
+static u64 dma_default_get_required_mask(struct device *dev)
+{
+       u32 low_totalram = ((max_pfn - 1) << PAGE_SHIFT);
+       u32 high_totalram = ((max_pfn - 1) >> (32 - PAGE_SHIFT));
+       u64 mask;
+
+       if (!high_totalram) {
+               /* convert to mask just covering totalram */
+               low_totalram = (1 << (fls(low_totalram) - 1));
+               low_totalram += low_totalram - 1;
+               mask = low_totalram;
+       } else {
+               high_totalram = (1 << (fls(high_totalram) - 1));
+               high_totalram += high_totalram - 1;
+               mask = (((u64)high_totalram) << 32) + 0xffffffff;
        }
+       return mask;
+}
 
-       return area;
+u64 dma_get_required_mask(struct device *dev)
+{
+       const struct dma_map_ops *ops = get_dma_ops(dev);
+
+       if (dma_is_direct(ops))
+               return dma_direct_get_required_mask(dev);
+       if (ops->get_required_mask)
+               return ops->get_required_mask(dev);
+       return dma_default_get_required_mask(dev);
 }
+EXPORT_SYMBOL_GPL(dma_get_required_mask);
+#endif
 
-/*
- * remaps an array of PAGE_SIZE pages into another vm_area
- * Cannot be used in non-sleeping contexts
- */
-void *dma_common_pages_remap(struct page **pages, size_t size,
-                       unsigned long vm_flags, pgprot_t prot,
-                       const void *caller)
+#ifndef arch_dma_alloc_attrs
+#define arch_dma_alloc_attrs(dev)      (true)
+#endif
+
+void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,
+               gfp_t flag, unsigned long attrs)
 {
-       struct vm_struct *area;
+       const struct dma_map_ops *ops = get_dma_ops(dev);
+       void *cpu_addr;
+
+       WARN_ON_ONCE(dev && !dev->coherent_dma_mask);
+
+       if (dma_alloc_from_dev_coherent(dev, size, dma_handle, &cpu_addr))
+               return cpu_addr;
 
-       area = __dma_common_pages_remap(pages, size, vm_flags, prot, caller);
-       if (!area)
+       /* let the implementation decide on the zone to allocate from: */
+       flag &= ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM);
+
+       if (!arch_dma_alloc_attrs(&dev))
                return NULL;
 
-       area->pages = pages;
+       if (dma_is_direct(ops))
+               cpu_addr = dma_direct_alloc(dev, size, dma_handle, flag, attrs);
+       else if (ops->alloc)
+               cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
+       else
+               return NULL;
 
-       return area->addr;
+       debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
+       return cpu_addr;
 }
+EXPORT_SYMBOL(dma_alloc_attrs);
 
-/*
- * remaps an allocated contiguous region into another vm_area.
- * Cannot be used in non-sleeping contexts
- */
-
-void *dma_common_contiguous_remap(struct page *page, size_t size,
-                       unsigned long vm_flags,
-                       pgprot_t prot, const void *caller)
+void dma_free_attrs(struct device *dev, size_t size, void *cpu_addr,
+               dma_addr_t dma_handle, unsigned long attrs)
 {
-       int i;
-       struct page **pages;
-       struct vm_struct *area;
+       const struct dma_map_ops *ops = get_dma_ops(dev);
 
-       pages = kmalloc(sizeof(struct page *) << get_order(size), GFP_KERNEL);
-       if (!pages)
-               return NULL;
+       if (dma_release_from_dev_coherent(dev, get_order(size), cpu_addr))
+               return;
+       /*
+        * On non-coherent platforms which implement DMA-coherent buffers via
+        * non-cacheable remaps, ops->free() may call vunmap(). Thus getting
+        * this far in IRQ context is a) at risk of a BUG_ON() or trying to
+        * sleep on some machines, and b) an indication that the driver is
+        * probably misusing the coherent API anyway.
+        */
+       WARN_ON(irqs_disabled());
+
+       if (!cpu_addr)
+               return;
 
-       for (i = 0; i < (size >> PAGE_SHIFT); i++)
-               pages[i] = nth_page(page, i);
+       debug_dma_free_coherent(dev, size, cpu_addr, dma_handle);
+       if (dma_is_direct(ops))
+               dma_direct_free(dev, size, cpu_addr, dma_handle, attrs);
+       else if (ops->free)
+               ops->free(dev, size, cpu_addr, dma_handle, attrs);
+}
+EXPORT_SYMBOL(dma_free_attrs);
 
-       area = __dma_common_pages_remap(pages, size, vm_flags, prot, caller);
+static inline void dma_check_mask(struct device *dev, u64 mask)
+{
+       if (sme_active() && (mask < (((u64)sme_get_me_mask() << 1) - 1)))
+               dev_warn(dev, "SME is active, device will require DMA bounce buffers\n");
+}
 
-       kfree(pages);
+int dma_supported(struct device *dev, u64 mask)
+{
+       const struct dma_map_ops *ops = get_dma_ops(dev);
 
-       if (!area)
-               return NULL;
-       return area->addr;
+       if (dma_is_direct(ops))
+               return dma_direct_supported(dev, mask);
+       if (!ops->dma_supported)
+               return 1;
+       return ops->dma_supported(dev, mask);
 }
+EXPORT_SYMBOL(dma_supported);
 
-/*
- * unmaps a range previously mapped by dma_common_*_remap
- */
-void dma_common_free_remap(void *cpu_addr, size_t size, unsigned long vm_flags)
+#ifndef HAVE_ARCH_DMA_SET_MASK
+int dma_set_mask(struct device *dev, u64 mask)
 {
-       struct vm_struct *area = find_vm_area(cpu_addr);
+       if (!dev->dma_mask || !dma_supported(dev, mask))
+               return -EIO;
 
-       if (!area || (area->flags & vm_flags) != vm_flags) {
-               WARN(1, "trying to free invalid coherent area: %p\n", cpu_addr);
-               return;
-       }
+       dma_check_mask(dev, mask);
+       *dev->dma_mask = mask;
+       return 0;
+}
+EXPORT_SYMBOL(dma_set_mask);
+#endif
 
-       unmap_kernel_range((unsigned long)cpu_addr, PAGE_ALIGN(size));
-       vunmap(cpu_addr);
+#ifndef CONFIG_ARCH_HAS_DMA_SET_COHERENT_MASK
+int dma_set_coherent_mask(struct device *dev, u64 mask)
+{
+       if (!dma_supported(dev, mask))
+               return -EIO;
+
+       dma_check_mask(dev, mask);
+       dev->coherent_dma_mask = mask;
+       return 0;
 }
+EXPORT_SYMBOL(dma_set_coherent_mask);
 #endif
+
+void dma_cache_sync(struct device *dev, void *vaddr, size_t size,
+               enum dma_data_direction dir)
+{
+       const struct dma_map_ops *ops = get_dma_ops(dev);
+
+       BUG_ON(!valid_dma_direction(dir));
+
+       if (dma_is_direct(ops))
+               arch_dma_cache_sync(dev, vaddr, size, dir);
+       else if (ops->cache_sync)
+               ops->cache_sync(dev, vaddr, size, dir);
+}
+EXPORT_SYMBOL(dma_cache_sync);