[PATCH 3/3] swiotlb: Add support for CMA allocations

Konrad Rzeszutek Wilk konrad.wilk at oracle.com
Mon Dec 9 19:29:57 EST 2013


Laura Abbott <lauraa at codeaurora.org> wrote:
>Some architectures may implement the CMA APIs to allow allocation
>of larger contiguous blocks of memory. Add support in the swiotlb
>alloc/free functions to allocate from the CMA APIs instead of the
>basic page allocator.
>
>Cc: Will Deacon <will.deacon at arm.com>
>Cc: Catalin Marinas <catalin.marinas at arm.com>
>Cc: Konrad Rzeszutek Wilk <konrad.wilk at oracle.com>
>Cc: Marek Szyprowski <m.szyprowski at samsung.com>
>Signed-off-by: Laura Abbott <lauraa at codeaurora.org>
>---
>lib/swiotlb.c |   92
>+++++++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 files changed, 86 insertions(+), 6 deletions(-)
>
>diff --git a/lib/swiotlb.c b/lib/swiotlb.c
>index e4399fa..77b4b17 100644
>--- a/lib/swiotlb.c
>+++ b/lib/swiotlb.c
>@@ -29,6 +29,9 @@
> #include <linux/ctype.h>
> #include <linux/highmem.h>
> #include <linux/gfp.h>
>+#include <linux/dma-contiguous.h>
>+#include <linux/io.h>
>+#include <linux/vmalloc.h>
> 
> #include <asm/io.h>
> #include <asm/dma.h>
>@@ -610,6 +613,66 @@ void swiotlb_tbl_sync_single(struct device *hwdev,
>phys_addr_t tlb_addr,
> }
> EXPORT_SYMBOL_GPL(swiotlb_tbl_sync_single);
> 
>+static void * __alloc_from_contiguous(struct device *hwdev, size_t
>size,
>+					struct page **ret_page)
>+{
>+	unsigned long order = get_order(size);
>+	size_t count = size >> PAGE_SHIFT;
>+	struct page *page;
>+	void *ptr = NULL;
>+
>+	page = dma_alloc_from_contiguous(hwdev, count, order);
>+	if (!page)
>+		return NULL;
>+
>+	if (PageHighMem(page)) {
>+		struct vm_struct *area;
>+		unsigned long addr;
>+
>+		/*
>+		 * DMA allocation can be mapped to user space, so lets
>+		 * set VM_USERMAP flags too.
>+		 */
>+		area = get_vm_area(size, VM_USERMAP);
>+		if (!area)
>+			goto err;
>+		addr = (unsigned long)area->addr;
>+		area->phys_addr = __pfn_to_phys(page_to_pfn(page));
>+
>+		if (ioremap_page_range(addr, addr + size, area->phys_addr,
>+		    PAGE_KERNEL)) {
>+			vunmap((void *)addr);
>+			goto err;
>+		}
>+		ptr = area->addr;
>+	} else {
>+		ptr = page_address(page);
>+	}
>+
>+	*ret_page = page;
>+	return ptr;
>+
>+err:
>+	dma_release_from_contiguous(hwdev, page, count);
>+	return NULL;
>+}
>+
>+static void __free_from_contiguous(struct device *hwdev, struct page
>*page,
>+				void *cpu_addr, size_t size)
>+{
>+	if (PageHighMem(page)) {
>+		struct vm_struct *area = find_vm_area(cpu_addr);
>+		if (!area) {
>+			WARN(1, "trying to free invalid coherent area: %p\n", cpu_addr);
>+			return;
>+		}
>+		unmap_kernel_range((unsigned long)cpu_addr, size);
>+		vunmap(cpu_addr);
>+	}
>+	dma_release_from_contiguous(hwdev, page, size >> PAGE_SHIFT);
>+}
>+
>+
> void *
> swiotlb_alloc_coherent(struct device *hwdev, size_t size,
> 		       dma_addr_t *dma_handle, gfp_t flags)
>@@ -618,18 +681,27 @@ swiotlb_alloc_coherent(struct device *hwdev,
>size_t size,
> 	void *ret;
> 	int order = get_order(size);
> 	u64 dma_mask = DMA_BIT_MASK(32);
>+	struct page *page;
> 
> 	if (hwdev && hwdev->coherent_dma_mask)
> 		dma_mask = hwdev->coherent_dma_mask;
> 
>-	ret = (void *)__get_free_pages(flags, order);
>-	if (ret) {
>+	if (IS_ENABLED(CONFIG_DMA_CMA)) {
>+		ret = __alloc_from_contiguous(hwdev, size, &page);
>+		dev_addr = phys_to_dma(hwdev, page_to_phys(page));
>+	} else {
>+		ret = (void *)__get_free_pages(flags, order);
> 		dev_addr = swiotlb_virt_to_bus(hwdev, ret);
>+	}
>+	if (ret) {
> 		if (dev_addr + size - 1 > dma_mask) {
> 			/*
> 			 * The allocated memory isn't reachable by the device.
> 			 */
>-			free_pages((unsigned long) ret, order);
>+			if(IS_ENABLED(CONFIG_DMA_CMA))
>+				__free_from_contiguous(hwdev, page, ret, size);
>+			else
>+				free_pages((unsigned long) ret, order);
> 			ret = NULL;
> 		}
> 	}
>@@ -673,11 +745,19 @@ swiotlb_free_coherent(struct device *hwdev,
>size_t size, void *vaddr,
> 	phys_addr_t paddr = dma_to_phys(hwdev, dev_addr);
> 
> 	WARN_ON(irqs_disabled());
>-	if (!is_swiotlb_buffer(paddr))
>-		free_pages((unsigned long)vaddr, get_order(size));
>-	else
>+	if (!is_swiotlb_buffer(paddr)) {
>+		if (IS_ENABLED(CONFIG_DMA_CMA)) {
>+			__free_from_contiguous(hwdev,
>+				pfn_to_page(paddr >> PAGE_SHIFT),
>+				vaddr,
>+				size);
>+		} else {
>+			free_pages((unsigned long)vaddr, get_order(size));
>+		}
>+	} else {
> 		/* DMA_TO_DEVICE to avoid memcpy in swiotlb_tbl_unmap_single */
> 		swiotlb_tbl_unmap_single(hwdev, paddr, size, DMA_TO_DEVICE);
>+	}
> }
> EXPORT_SYMBOL(swiotlb_free_coherent);
> 

Can this be done in the platform dma_ops functions instead?



More information about the linux-arm-kernel mailing list