Skip to contentSkip to conversation

DMA Atomic pool for arm64

PATCH Superseded Laura Abbott 6 participants 28 messages Jul 2, 2014 – Jul 23, 2014

Subsystems (6)

v4 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

v4 cover DMA Atomic pool for arm64

Hi,

This is the latest in a series to add support for atomic DMA allocations for non-coherent/CMA code paths in arm64. I did some refactoring to have arm also use genalloc and pull out some of the remapping code.

This could probably use more testing on other platforms, especially those that have CONFIG_ARM_DMA_USE_IOMMU set.

Thanks,
Laura

v4: Simplified the logic in gen_pool_first_fit_order_align which makes the data argument actually unused.

v3: Now a patch series due to refactoring of arm code. arm and arm64 now both use genalloc for atomic pool management. genalloc extensions added. DMA remapping code factored out as well.

v2: Various bug fixes pointed out by David and Ritesh (CMA dependency, swapping coherent, noncoherent). I'm still not sure how to address the devicetree suggestion by Will [1][2]. I added the devicetree mailing list this time around to get more input on this.

[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2014-April/249180.html
[2] http://lists.infradead.org/pipermail/linux-arm-kernel/2014-April/249528.html

Laura Abbott (5):
lib/genalloc.c: Add power aligned algorithm lib/genalloc.c: Add genpool range check function
common: dma-mapping: Introduce common remapping functions
arm: use genalloc for the atomic pool
arm64: Add atomic pool for non-coherent and CMA allocations.

 arch/arm/Kconfig                         |   1 +
 arch/arm/mm/dma-mapping.c                | 200 ++++++++-----------------------
 arch/arm64/Kconfig                       |   1 +
 arch/arm64/mm/dma-mapping.c              | 154 +++++++++++++++++++++---
 drivers/base/dma-mapping.c               |  67 +++++++++++
 include/asm-generic/dma-mapping-common.h |   9 ++
 include/linux/genalloc.h                 |   7 ++
 lib/genalloc.c                           |  49 ++++++++
 8 files changed, 323 insertions(+), 165 deletions(-)
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
#1 lore raw

PATCH v4 1/5 lib/genalloc.c: Add power aligned algorithm

One of the more common algorithms used for allocation is to align the start address of the allocation to the order of size requested. Add this as an algorithm option for genalloc.

---
 include/linux/genalloc.h |  4 ++++
 lib/genalloc.c           | 20 ++++++++++++++++++++
 2 files changed, 24 insertions(+)
include/linux/genalloc.h +4 -0
index 1c2fdaa..3cd0934 100644
@@ -110,6 +110,10 @@ extern void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo,
 extern unsigned long gen_pool_first_fit(unsigned long *map, unsigned long size,
 		unsigned long start, unsigned int nr, void *data);
 
+extern unsigned long gen_pool_first_fit_order_align(unsigned long *map,
+		unsigned long size, unsigned long start, unsigned int nr,
+		void *data);
+
 extern unsigned long gen_pool_best_fit(unsigned long *map, unsigned long size,
 		unsigned long start, unsigned int nr, void *data);
 
lib/genalloc.c +20 -0
index bdb9a45..c2b3ad7 100644
@@ -481,6 +481,26 @@ unsigned long gen_pool_first_fit(unsigned long *map, unsigned long size,
 EXPORT_SYMBOL(gen_pool_first_fit);
 
 /**
+ * gen_pool_first_fit_order_align - find the first available region
+ * of memory matching the size requirement. The region will be aligned
+ * to the order of the size specified.
+ * @map: The address to base the search on
+ * @size: The bitmap size in bits
+ * @start: The bitnumber to start searching at
+ * @nr: The number of zeroed bits we're looking for
+ * @data: additional data - unused
+ */
+unsigned long gen_pool_first_fit_order_align(unsigned long *map,
+		unsigned long size, unsigned long start,
+		unsigned int nr, void *data)
+{
+	unsigned long align_mask = roundup_pow_of_two(nr) - 1;
+
+	return bitmap_find_next_zero_area(map, size, start, nr, align_mask);
+}
+EXPORT_SYMBOL(gen_pool_first_fit_order_align);
+
+/**
  * gen_pool_best_fit - find the best fitting region of memory
  * macthing the size requirement (no alignment constraint)
  * @map: The address to base the search on
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
Acked-by on v4 1/5
#2 lore raw

Re: [PATCHv4 1/5] lib/genalloc.c: Add power aligned algorithm

Quoted 17 lines · Laura Abbott

One of the more common algorithms used for allocation is to align the start address of the allocation to the order of size requested. Add this as an algorithm option for genalloc.

---
 include/linux/genalloc.h |  4 ++++
 lib/genalloc.c           | 20 ++++++++++++++++++++
 2 files changed, 24 insertions(+)
include/linux/genalloc.h +0 -0
index 1c2fdaa..3cd0934 100644

include/linux/genalloc.h extern void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo, · line 116

Quoted 7 lines
include/linux/genalloc.h +4 -0
@@ -110,6 +110,10 @@ extern void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo,
 extern unsigned long gen_pool_first_fit(unsigned long *map, unsigned long size,
 		unsigned long start, unsigned int nr, void *data);
 
+extern unsigned long gen_pool_first_fit_order_align(unsigned long *map,
+		unsigned long size, unsigned long start, unsigned int nr,
+		void *data);
+

You could also update gen_pool_first_fit to call this new function instead.

Anyway, that's up to you:

Will

#3 lore raw

Re: [PATCHv4 1/5] lib/genalloc.c: Add power aligned algorithm

Quoted 21 lines · Will Deacon
Quoted 20 lines · Laura Abbott

One of the more common algorithms used for allocation is to align the start address of the allocation to the order of size requested. Add this as an algorithm option for genalloc.

---
 include/linux/genalloc.h |  4 ++++
 lib/genalloc.c           | 20 ++++++++++++++++++++
 2 files changed, 24 insertions(+)
include/linux/genalloc.h +0 -0
index 1c2fdaa..3cd0934 100644
@@ -110,6 +110,10 @@ extern void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo,
 extern unsigned long gen_pool_first_fit(unsigned long *map, unsigned long size,
              unsigned long start, unsigned int nr, void *data);
 
Quoted 6 lines

include/linux/genalloc.h extern void gen_pool_set_algo(struct gen_pool *pool, genpool_algo_t algo, · line 116

Quoted 4 lines
include/linux/genalloc.h +4 -0
+extern unsigned long gen_pool_first_fit_order_align(unsigned long *map,
+             unsigned long size, unsigned long start, unsigned int nr,
+             void *data);
+

You could also update gen_pool_first_fit to call this new function instead.

+1, that'd be slightly nicer and remove one exported symbol.

But, as Will says, up to you. Either option:

#4 lore raw

PATCH v4 2/5 lib/genalloc.c: Add genpool range check function

In reply to Laura Abbott

After allocating an address from a particular genpool, there is no good way to verify if that address actually belongs to a genpool. Introduce addr_in_gen_pool which will return if an address plus size falls completely within the genpool range.

---
 include/linux/genalloc.h |  3 +++
 lib/genalloc.c           | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
include/linux/genalloc.h +3 -0
index 3cd0934..1ccaab4 100644
@@ -121,6 +121,9 @@ extern struct gen_pool *devm_gen_pool_create(struct device *dev,
 		int min_alloc_order, int nid);
 extern struct gen_pool *dev_get_gen_pool(struct device *dev);
 
+bool addr_in_gen_pool(struct gen_pool *pool, unsigned long start,
+			size_t size);
+
 #ifdef CONFIG_OF
 extern struct gen_pool *of_get_named_gen_pool(struct device_node *np,
 	const char *propname, int index);
lib/genalloc.c +29 -0
index c2b3ad7..6f03722 100644
@@ -403,6 +403,35 @@ void gen_pool_for_each_chunk(struct gen_pool *pool,
 EXPORT_SYMBOL(gen_pool_for_each_chunk);
 
 /**
+ * addr_in_gen_pool - checks if an address falls within the range of a pool
+ * @pool:	the generic memory pool
+ * @start:	start address
+ * @size:	size of the region
+ *
+ * Check if the range of addresses falls within the specified pool. Takes
+ * the rcu_read_lock for the duration of the check.
+ */
+bool addr_in_gen_pool(struct gen_pool *pool, unsigned long start,
+			size_t size)
+{
+	bool found = false;
+	unsigned long end = start + size;
+	struct gen_pool_chunk *chunk;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(chunk, &(pool)->chunks, next_chunk) {
+		if (start >= chunk->start_addr && start <= chunk->end_addr) {
+			if (end <= chunk->end_addr) {
+				found = true;
+				break;
+			}
+		}
+	}
+	rcu_read_unlock();
+	return found;
+}
+
+/**
  * gen_pool_avail - get available free space of the pool
  * @pool: pool to get available free space
  *
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
Acked-by on v4 2/5
#5 lore raw

Re: [PATCHv4 2/5] lib/genalloc.c: Add genpool range check function

Quoted 9 lines · Laura Abbott

After allocating an address from a particular genpool, there is no good way to verify if that address actually belongs to a genpool. Introduce addr_in_gen_pool which will return if an address plus size falls completely within the genpool range.

---

Will

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

Reviewed-by on v4 2/5
#6 lore raw

Re: [PATCHv4 2/5] lib/genalloc.c: Add genpool range check function

In reply to Laura Abbott
Quoted 8 lines · Laura Abbott

After allocating an address from a particular genpool, there is no good way to verify if that address actually belongs to a genpool. Introduce addr_in_gen_pool which will return if an address plus size falls completely within the genpool range.

What's the merge path for this code? Part of the arm64 code that needs it, I presume?

-Olof

#7 lore raw

Re: [PATCHv4 2/5] lib/genalloc.c: Add genpool range check function

Quoted 8 lines · Olof Johansson
Quoted 7 lines · Laura Abbott

After allocating an address from a particular genpool, there is no good way to verify if that address actually belongs to a genpool. Introduce addr_in_gen_pool which will return if an address plus size falls completely within the genpool range.

Quoted 7 lines
Quoted 1 line

What's the merge path for this code? Part of the arm64 code that needs it, I presume?

My plan was to have the entire series go through the arm64 tree unless someone has a better idea.

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#8 lore raw

Re: [PATCHv4 2/5] lib/genalloc.c: Add genpool range check function

Quoted 11 lines · Laura Abbott
Quoted 10 lines · Olof Johansson
Quoted 8 lines · Laura Abbott

After allocating an address from a particular genpool, there is no good way to verify if that address actually belongs to a genpool. Introduce addr_in_gen_pool which will return if an address plus size falls completely within the genpool range.

Quoted 7 lines
Quoted 4 lines

What's the merge path for this code? Part of the arm64 code that needs it, I presume?

My plan was to have the entire series go through the arm64 tree unless someone has a better idea.

It's fine by me. But since it touches core arch/arm code, I would like to see an Ack from Russell.

Signature · 1 line
Catalin
#9 lore raw

PATCH v4 3/5 common: dma-mapping: Introduce common remapping functions

In reply to Laura Abbott

For architectures without coherent DMA, memory for DMA may need to be remapped with coherent attributes. Factor out the the remapping code from arm and put it in a common location to reduced code duplication.

---
 arch/arm/mm/dma-mapping.c                | 57 +++++----------------------
 drivers/base/dma-mapping.c               | 67 ++++++++++++++++++++++++++++++++
 include/asm-generic/dma-mapping-common.h |  9 +++++
 3 files changed, 85 insertions(+), 48 deletions(-)
arch/arm/mm/dma-mapping.c +9 -48
index 4c88935..f5190ac 100644
@@ -297,37 +297,19 @@ static void *
 __dma_alloc_remap(struct page *page, size_t size, gfp_t gfp, pgprot_t prot,
 	const void *caller)
 {
-	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_caller(size, VM_ARM_DMA_CONSISTENT | VM_USERMAP,
-				  caller);
-	if (!area)
-		return NULL;
-	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, prot)) {
-		vunmap((void *)addr);
-		return NULL;
-	}
-	return (void *)addr;
+	return dma_common_contiguous_remap(page, size,
+			VM_ARM_DMA_CONSISTENT | VM_USERMAP,
+			prot, caller);
 }
 
 static void __dma_free_remap(void *cpu_addr, size_t size)
 {
-	unsigned int flags = VM_ARM_DMA_CONSISTENT | VM_USERMAP;
-	struct vm_struct *area = find_vm_area(cpu_addr);
-	if (!area || (area->flags & flags) != flags) {
-		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_common_free_remap(cpu_addr, size,
+			VM_ARM_DMA_CONSISTENT | VM_USERMAP);
 }
 
 #define DEFAULT_DMA_COHERENT_POOL_SIZE	SZ_256K
@@ -1261,29 +1243,8 @@ static void *
 __iommu_alloc_remap(struct page **pages, size_t size, gfp_t gfp, pgprot_t prot,
 		    const void *caller)
 {
-	unsigned int i, nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
-	struct vm_struct *area;
-	unsigned long p;
-
-	area = get_vm_area_caller(size, VM_ARM_DMA_CONSISTENT | VM_USERMAP,
-				  caller);
-	if (!area)
-		return NULL;
-
-	area->pages = pages;
-	area->nr_pages = nr_pages;
-	p = (unsigned long)area->addr;
-
-	for (i = 0; i < nr_pages; i++) {
-		phys_addr_t phys = __pfn_to_phys(page_to_pfn(pages[i]));
-		if (ioremap_page_range(p, p + PAGE_SIZE, phys, prot))
-			goto err;
-		p += PAGE_SIZE;
-	}
-	return area->addr;
-err:
-	unmap_kernel_range((unsigned long)area->addr, size);
-	vunmap(area->addr);
+	return dma_common_pages_remap(pages, size,
+			VM_ARM_DMA_CONSISTENT | VM_USERMAP, prot, caller);
 	return NULL;
 }
 
@@ -1491,8 +1452,8 @@ void arm_iommu_free_attrs(struct device *dev, size_t size, void *cpu_addr,
 	}
 
 	if (!dma_get_attr(DMA_ATTR_NO_KERNEL_MAPPING, attrs)) {
-		unmap_kernel_range((unsigned long)cpu_addr, size);
-		vunmap(cpu_addr);
+		dma_common_free_remap(cpu_addr, size,
+			VM_ARM_DMA_CONSISTENT | VM_USERMAP);
 	}
 
 	__iommu_remove_mapping(dev, handle, size);
drivers/base/dma-mapping.c +67 -0

76 lines not on this page · raw message

include/asm-generic/dma-mapping-common.h +9 -0
index de8bf89..a9fd248 100644
@@ -179,6 +179,15 @@ dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
 extern int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
 			   void *cpu_addr, dma_addr_t dma_addr, size_t size);
 
+void *dma_common_contiguous_remap(struct page *page, size_t size,
+			unsigned long vm_flags,
+			pgprot_t prot, const void *caller);
+
+void *dma_common_pages_remap(struct page **pages, size_t size,
+			unsigned long vm_flags, pgprot_t prot,
+			const void *caller);
+void dma_common_free_remap(void *cpu_addr, size_t size, unsigned long vm_flags);
+
 /**
  * dma_mmap_attrs - map a coherent DMA allocation into user space
  * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
#10 lore raw

Re: [PATCHv4 3/5] common: dma-mapping: Introduce common remapping functions

Quoted 7 lines · Laura Abbott

For architectures without coherent DMA, memory for DMA may need to be remapped with coherent attributes. Factor out the the remapping code from arm and put it in a common location to reduced code duplication.

Hm. The switch from ioremap to map_vm_area() here seems to imply that lib/ioremap can/should be reworked to use just wrap the vmalloc functions too?

Unrelated to this change.

I did a pass of review here. Nothing stands out as wrong but I don't claim to know this area well these days.

What's the merge/ack plan here? It might reduce the complexity of merging to add the common functions in your series, then move the ARM code over separately?

-Olof

maintainer
#11 lore raw

Re: [PATCHv4 3/5] common: dma-mapping: Introduce common remapping functions

Quoted 11 lines · Olof Johansson
Quoted 6 lines · Laura Abbott

For architectures without coherent DMA, memory for DMA may need to be remapped with coherent attributes. Factor out the the remapping code from arm and put it in a common location to reduced code duplication.

Hm. The switch from ioremap to map_vm_area() here seems to imply that lib/ioremap can/should be reworked to use just wrap the vmalloc functions too?

ioremap_page_range() does not require the allocation of a map page array and assumes that the mapped memory is physically contiguous. This would be more efficient than the vmap() implementation which is generic enough to allow non-contiguous memory allocations.

At some point, we'll have to support SMMU on arm64 and we can have 4 combinations of coherency and IOMMU. When an IOMMU is present, we don't require physically contiguous memory but we may require non-cacheable mappings, in which case vmap comes in handy.

Signature · 1 line
Catalin
maintainer
#12 lore raw

Re: [PATCHv4 3/5] common: dma-mapping: Introduce common remapping functions

In reply to Laura Abbott
Quoted 7 lines · Laura Abbott
drivers/base/dma-mapping.c +7 -0
+void *dma_common_pages_remap(struct page **pages, size_t size,
+			unsigned long vm_flags, pgprot_t prot,
+			const void *caller)
+{
+	struct vm_struct *area;
+
+	area = get_vm_area_caller(size, vm_flags, caller);

drivers/base/dma-mapping.c int dma_common_mmap(struct device *dev, struct vm_area_struct *vma, · line 320

Quoted 10 lines
drivers/base/dma-mapping.c +10 -0
+	if (!area)
+		return NULL;
+
+	if (map_vm_area(area, prot, &pages)) {
+		vunmap(area->addr);
+		return NULL;
+	}
+
+	return area->addr;
+}

Why not just replace this function with vmap()? It is nearly identical.

Signature · 1 line
Catalin
#13 lore raw

Re: [PATCHv4 3/5] common: dma-mapping: Introduce common remapping functions

Quoted 12 lines · Catalin Marinas
Quoted 11 lines · Laura Abbott
drivers/base/dma-mapping.c +11 -0
+void *dma_common_pages_remap(struct page **pages, size_t size,
+			unsigned long vm_flags, pgprot_t prot,
+			const void *caller)
+{
+	struct vm_struct *area;
+
+	area = get_vm_area_caller(size, vm_flags, caller);
+	if (!area)
+		return NULL;
+
+	if (map_vm_area(area, prot, &pages)) {
Quoted 9 lines

drivers/base/dma-mapping.c int dma_common_mmap(struct device *dev, struct vm_area_struct *vma, · line 320

Quoted 6 lines
drivers/base/dma-mapping.c +6 -0
+		vunmap(area->addr);
+		return NULL;
+	}
+
+	return area->addr;
+}

Why not just replace this function with vmap()? It is nearly identical.

With this version, the caller stored and printed via /proc/vmallocinfo is the actual caller of the DMA API whereas if we just call vmap we don't get any useful caller information. Going to vmap would change the existing behavior on ARM so it seems unwise to switch. Another option is to move this into vmalloc.c and add vmap_caller.

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#14 lore raw

Re: [PATCHv4 3/5] common: dma-mapping: Introduce common remapping functions

Quoted 22 lines · Laura Abbott
Quoted 12 lines · Catalin Marinas
Quoted 11 lines · Laura Abbott
drivers/base/dma-mapping.c +11 -0
+void *dma_common_pages_remap(struct page **pages, size_t size,
+			unsigned long vm_flags, pgprot_t prot,
+			const void *caller)
+{
+	struct vm_struct *area;
+
+	area = get_vm_area_caller(size, vm_flags, caller);
+	if (!area)
+		return NULL;
+
+	if (map_vm_area(area, prot, &pages)) {
Quoted 8 lines

drivers/base/dma-mapping.c int dma_common_mmap(struct device *dev, struct vm_area_struct *vma, · line 320

Quoted 6 lines
drivers/base/dma-mapping.c +6 -0
+		vunmap(area->addr);
+		return NULL;
+	}
+
+	return area->addr;
+}

Why not just replace this function with vmap()? It is nearly identical.

Quoted 4 lines

With this version, the caller stored and printed via /proc/vmallocinfo is the actual caller of the DMA API whereas if we just call vmap we don't get any useful caller information. Going to vmap would change the existing behavior on ARM so it seems unwise to switch.

OK.

Quoted 1 line

Another option is to move this into vmalloc.c and add vmap_caller.

Maybe as a subsequent clean-up (once this series gets merged).

Signature · 1 line
Catalin
#15 lore raw

PATCH v4 4/5 arm: use genalloc for the atomic pool

In reply to Laura Abbott

ARM currently uses a bitmap for tracking atomic allocations. genalloc already handles this type of memory pool allocation so switch to using that instead.

---
 arch/arm/Kconfig          |   1 +
 arch/arm/mm/dma-mapping.c | 143 ++++++++++++++--------------------------------
 2 files changed, 44 insertions(+), 100 deletions(-)
arch/arm/Kconfig +1 -0
index 245058b..190d085 100644
@@ -13,6 +13,7 @@ config ARM
 	select CLONE_BACKWARDS
 	select CPU_PM if (SUSPEND || CPU_IDLE)
 	select DCACHE_WORD_ACCESS if HAVE_EFFICIENT_UNALIGNED_ACCESS
+	select GENERIC_ALLOCATOR
 	select GENERIC_ATOMIC64 if (CPU_V7M || CPU_V6 || !CPU_32v6K || !AEABI)
 	select GENERIC_CLOCKEVENTS_BROADCAST if SMP
 	select GENERIC_IDLE_POLL_SETUP
arch/arm/mm/dma-mapping.c +43 -100

217 lines not on this page · raw message

Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
#16 lore raw

Re: [PATCHv4 4/5] arm: use genalloc for the atomic pool

On Wed, Jul 02, 2014 at 11:03:37AM -0700, Laura Abbott wrote:
[...]

Quoted 1 line
arch/arm/mm/dma-mapping.c +0 -0

[...]

arch/arm/mm/dma-mapping.c line 29

Quoted 8 lines

index f5190ac..02a1939 100644

arch/arm/mm/dma-mapping.c +1 -0
@@ -26,6 +26,7 @@
 #include <linux/io.h>
 #include <linux/vmalloc.h>
 #include <linux/sizes.h>
+#include <linux/genalloc.h>

Includes should be sorted alphabetically. I realize that's not the case for this particular file, but the downside of that is that your patch no longer applies cleanly on top of linux-next because some other patch did add linux/cma.h at the same location.

arch/arm/mm/dma-mapping.c

Quoted 9 lines
arch/arm/mm/dma-mapping.c +1 -1
 static int __init early_coherent_pool(char *p)
 {
-	atomic_pool.size = memparse(p, &p);
+	atomic_pool_size = memparse(p, &p);
arch/arm/mm/dma-mapping.c +1 -0
 	return 0;
 }
 early_param("coherent_pool", early_coherent_pool);
 
+

There's a gratuituous blank line her.

I also need the below hunk on top of you patch to make this compile on ARM.

Thierry

arch/arm/mm/dma-mapping.c +6 -4
index b323032f0850..460aaf965a87 100644
@@ -1250,11 +1250,13 @@ static int __iommu_remove_mapping(struct device *dev, dma_addr_t iova, size_t si
 
 static struct page **__atomic_get_pages(void *addr)
 {
-	struct dma_pool *pool = &atomic_pool;
-	struct page **pages = pool->pages;
-	int offs = (addr - pool->vaddr) >> PAGE_SHIFT;
+	struct page *page;
+	phys_addr_t phys;
+
+	phys = gen_pool_virt_to_phys(atomic_pool, (unsigned long)addr);
+	page = phys_to_page(phys);
 
-	return pages + offs;
+	return (struct page **)page;
 }
 
 static struct page **__iommu_get_pages(void *cpu_addr, struct dma_attrs *attrs)
arch/arm64/mm/dma-mapping.c +0 -1
index a2487f12b2fc..764f53565958 100644
@@ -441,7 +441,6 @@ remove_mapping:
 	dma_common_free_remap(addr, atomic_pool_size, VM_USERMAP);
 destroy_genpool:
 	gen_pool_destroy(atomic_pool);
-	atomic_pool == NULL;
 free_page:
 	if (!dma_release_from_contiguous(NULL, page, nr_pages))
 		__free_pages(page, get_order(atomic_pool_size));

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140704/a3416bd7/attachment.sig>

#17 lore raw

Re: [PATCHv4 4/5] arm: use genalloc for the atomic pool

Quoted 4 lines · Thierry Reding

On Wed, Jul 02, 2014 at 11:03:37AM -0700, Laura Abbott wrote:
[...]

Quoted 1 line
arch/arm/mm/dma-mapping.c +0 -0

[...]

Quoted 14 lines

arch/arm/mm/dma-mapping.c line 29

Quoted 8 lines

index f5190ac..02a1939 100644

arch/arm/mm/dma-mapping.c +1 -0
@@ -26,6 +26,7 @@
 #include <linux/io.h>
 #include <linux/vmalloc.h>
 #include <linux/sizes.h>
+#include <linux/genalloc.h>

Includes should be sorted alphabetically. I realize that's not the case for this particular file, but the downside of that is that your patch no longer applies cleanly on top of linux-next because some other patch did add linux/cma.h at the same location.

Yes, I'll fix that up. I'll put genalloc.h before gfp.h.

Quoted 8 lines
Quoted 8 lines
arch/arm/mm/dma-mapping.c +1 -1
 static int __init early_coherent_pool(char *p)
 {
-	atomic_pool.size = memparse(p, &p);
+	atomic_pool_size = memparse(p, &p);

return 0;
}
early_param("coherent_pool", early_coherent_pool);

Quoted 7 lines
Quoted 1 line

+

There's a gratuituous blank line her.

I also need the below hunk on top of you patch to make this compile on ARM.

Yes, that does indeed need to be fixed up.

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
#18 lore raw

PATCH v4 5/5 arm64: Add atomic pool for non-coherent and CMA allocations.

In reply to Laura Abbott

Neither CMA nor noncoherent allocations support atomic allocations. Add a dedicated atomic pool to support this.

---
 arch/arm64/Kconfig          |   1 +
 arch/arm64/mm/dma-mapping.c | 154 +++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 138 insertions(+), 17 deletions(-)
arch/arm64/Kconfig +1 -0
index a474de34..cd402b9 100644
@@ -15,6 +15,7 @@ config ARM64
 	select COMMON_CLK
 	select CPU_PM if (SUSPEND || CPU_IDLE)
 	select DCACHE_WORD_ACCESS
+	select GENERIC_ALLOCATOR
 	select GENERIC_CLOCKEVENTS
 	select GENERIC_CLOCKEVENTS_BROADCAST if SMP
 	select GENERIC_CPU_AUTOPROBE
arch/arm64/mm/dma-mapping.c +137 -17

218 lines not on this page · raw message

Signature · 2 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation
#19 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

On Wed, Jul 02, 2014 at 11:03:38AM -0700, Laura Abbott wrote:
[...]

Quoted 1 line
arch/arm64/mm/dma-mapping.c +0 -0

[...]

arch/arm64/mm/dma-mapping.c static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot, · line 48

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+static struct gen_pool *atomic_pool;
+
+#define DEFAULT_DMA_COHERENT_POOL_SIZE  SZ_256K
+static size_t atomic_pool_size = DEFAULT_DMA_COHERENT_POOL_SIZE;

There doesn't seem to be much use for this since it can't be overridden via init_dma_coherent_pool_size like on ARM.

arch/arm64/mm/dma-mapping.c static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot, · line 93

Quoted 11 lines
arch/arm64/mm/dma-mapping.c +11 -0
+static int __free_from_pool(void *start, size_t size)
+{
+	if (!__in_atomic_pool(start, size))
+		return 0;
+
+	gen_pool_free(atomic_pool, (unsigned long)start, size);
+
+	return 1;
+}
+
+

There's a gratuituous blank line here.

arch/arm64/mm/dma-mapping.c static void *__dma_alloc_coherent(struct device *dev, size_t size, · line 107

Quoted 10 lines

static void *__dma_alloc_coherent(struct device *dev, size_t size,

arch/arm64/mm/dma-mapping.c +2 -1
 				  dma_addr_t *dma_handle, gfp_t flags,
 				  struct dma_attrs *attrs)
@@ -53,7 +103,8 @@ static void *__dma_alloc_coherent(struct device *dev, size_t size,
 	if (IS_ENABLED(CONFIG_ZONE_DMA) &&
 	    dev->coherent_dma_mask <= DMA_BIT_MASK(32))
 		flags |= GFP_DMA;
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
+
+	if (!(flags & __GFP_WAIT) && IS_ENABLED(CONFIG_DMA_CMA)) {

I think the diff would be more readable here if this wasn't introducing a blank linke and kept the IS_ENABLED() first.

Quoted 9 lines
arch/arm64/mm/dma-mapping.c +2 -0
 		struct page *page;
 
 		size = PAGE_ALIGN(size);
@@ -73,50 +124,56 @@ static void __dma_free_coherent(struct device *dev, size_t size,
 				void *vaddr, dma_addr_t dma_handle,
 				struct dma_attrs *attrs)
 {
+	bool freed;
+	phys_addr_t paddr = dma_to_phys(dev, dma_handle);

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 84

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +1 -3
+
 	if (dev == NULL) {
 		WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
 		return;
 	}
 
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
-		phys_addr_t paddr = dma_to_phys(dev, dma_handle);
 
-		dma_release_from_contiguous(dev,

The above leaves an unnecessary blank line in place.

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 170

Quoted 11 lines
arch/arm64/mm/dma-mapping.c +1 -3
 	ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs);
 	if (!ptr)
 		goto no_mem;
-	map = kmalloc(sizeof(struct page *) << order, flags & ~GFP_DMA);
-	if (!map)
-		goto no_map;
 
 	/* remove any dirty cache lines on the kernel alias */
 	__dma_flush_range(ptr, ptr + size);
 
+

Adds an unnecessary blank line.

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 401

Quoted 12 lines
arch/arm64/mm/dma-mapping.c +8 -0
@@ -332,6 +391,67 @@ static struct notifier_block amba_bus_nb = {
 
 extern int swiotlb_late_init_with_default_size(size_t default_size);
 
+static int __init atomic_pool_init(void)
+{
+	pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+	unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+	struct page *page;
+	void *addr;
+
+

There's another gratuituous blank line here...

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 408

Quoted 7 lines
arch/arm64/mm/dma-mapping.c +7 -0
+	if (dev_get_cma_area(NULL))
+		page = dma_alloc_from_contiguous(NULL, nr_pages,
+					get_order(atomic_pool_size));
+	else
+		page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));
+
+

and here.

Quoted 16 lines
arch/arm64/mm/dma-mapping.c +16 -0
+	if (page) {
+		int ret;
+
+		atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+		if (!atomic_pool)
+			goto free_page;
+
+		addr = dma_common_contiguous_remap(page, atomic_pool_size,
+					VM_USERMAP, prot, atomic_pool_init);
+
+		if (!addr)
+			goto destroy_genpool;
+
+		memset(addr, 0, atomic_pool_size);
+		__dma_flush_range(addr, addr + atomic_pool_size);
+

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 434

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+		ret = gen_pool_add_virt(atomic_pool, (unsigned long)addr,
+					page_to_phys(page),
+					atomic_pool_size, -1);
+		if (ret)
+			goto remove_mapping;
+
+		gen_pool_set_algo(atomic_pool,
+				  gen_pool_first_fit_order_align, NULL);
+
+		pr_info("DMA: preallocated %zd KiB pool for atomic allocations\n",

I think this should be "%zu" because atomic_pool_size is a size_t, not a ssize_t.

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 444

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+			atomic_pool_size / 1024);
+		return 0;
+	}
+	goto out;
+
+remove_mapping:
+	dma_common_free_remap(addr, atomic_pool_size, VM_USERMAP);
+destroy_genpool:
+	gen_pool_destroy(atomic_pool);
+	atomic_pool == NULL;

This probably doesn't belong here.

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 447

Quoted 3 lines
arch/arm64/mm/dma-mapping.c +3 -0
+free_page:
+	if (!dma_release_from_contiguous(NULL, page, nr_pages))
+		__free_pages(page, get_order(atomic_pool_size));

You use get_order(atomic_pool_size) a lot, perhaps it should be a temporary variable?

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 450

Quoted 3 lines
arch/arm64/mm/dma-mapping.c +3 -0
+out:
+	pr_err("DMA: failed to allocate %zx KiB pool for atomic coherent allocation\n",
+		atomic_pool_size / 1024);

Print in decimal rather than hexadecimal?

Thierry
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20140704/1e719c0a/attachment.sig>

#20 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

Quoted 4 lines · Thierry Reding

On Wed, Jul 02, 2014 at 11:03:38AM -0700, Laura Abbott wrote:
[...]

Quoted 1 line
arch/arm64/mm/dma-mapping.c +0 -0

[...]

Quoted 8 lines

arch/arm64/mm/dma-mapping.c static pgprot_t __get_dma_pgprot(struct dma_attrs *attrs, pgprot_t prot, · line 48

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+static struct gen_pool *atomic_pool;
+
+#define DEFAULT_DMA_COHERENT_POOL_SIZE  SZ_256K
+static size_t atomic_pool_size = DEFAULT_DMA_COHERENT_POOL_SIZE;

There doesn't seem to be much use for this since it can't be overridden via init_dma_coherent_pool_size like on ARM.

There is still the command line option coherent_pool=<size> though

[...]

Quoted 21 lines
Quoted 21 lines
arch/arm64/mm/dma-mapping.c +21 -0
+	if (page) {
+		int ret;
+
+		atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+		if (!atomic_pool)
+			goto free_page;
+
+		addr = dma_common_contiguous_remap(page, atomic_pool_size,
+					VM_USERMAP, prot, atomic_pool_init);
+
+		if (!addr)
+			goto destroy_genpool;
+
+		memset(addr, 0, atomic_pool_size);
+		__dma_flush_range(addr, addr + atomic_pool_size);
+
+		ret = gen_pool_add_virt(atomic_pool, (unsigned long)addr,
+					page_to_phys(page),
+					atomic_pool_size, -1);
+		if (ret)
+			goto remove_mapping;
Quoted 9 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 434

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+
+		gen_pool_set_algo(atomic_pool,
+				  gen_pool_first_fit_order_align, NULL);
+
+		pr_info("DMA: preallocated %zd KiB pool for atomic allocations\n",

I think this should be "%zu" because atomic_pool_size is a size_t, not a ssize_t.

Yes, will fix.

Quoted 4 lines
Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+			atomic_pool_size / 1024);
+		return 0;
+	}
+	goto out;
Quoted 9 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 444

Quoted 6 lines
arch/arm64/mm/dma-mapping.c +6 -0
+
+remove_mapping:
+	dma_common_free_remap(addr, atomic_pool_size, VM_USERMAP);
+destroy_genpool:
+	gen_pool_destroy(atomic_pool);
+	atomic_pool == NULL;

This probably doesn't belong here.

Dastardly typo.

Quoted 7 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 447

Quoted 3 lines
arch/arm64/mm/dma-mapping.c +3 -0
+free_page:
+	if (!dma_release_from_contiguous(NULL, page, nr_pages))
+		__free_pages(page, get_order(atomic_pool_size));

You use get_order(atomic_pool_size) a lot, perhaps it should be a temporary variable?

Yes, three usages is probably enough.

Quoted 6 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 450

Quoted 3 lines
arch/arm64/mm/dma-mapping.c +3 -0
+out:
+	pr_err("DMA: failed to allocate %zx KiB pool for atomic coherent allocation\n",
+		atomic_pool_size / 1024);

Print in decimal rather than hexadecimal?

I actually prefer hexadecimal but I should at least be consistent between error and non-error paths.

Quoted 2 lines

Thierry

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#21 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

In reply to Laura Abbott
Quoted 4 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +0 -0
index 4164c5a..a2487f1 100644

[...]

arch/arm64/mm/dma-mapping.c static void *__dma_alloc_coherent(struct device *dev, size_t size, · line 110

Quoted 13 lines

static void *__dma_alloc_coherent(struct device *dev, size_t size,

arch/arm64/mm/dma-mapping.c +2 -1
 				  dma_addr_t *dma_handle, gfp_t flags,
 				  struct dma_attrs *attrs)
@@ -53,7 +103,8 @@ static void *__dma_alloc_coherent(struct device *dev, size_t size,
 	if (IS_ENABLED(CONFIG_ZONE_DMA) &&
 	    dev->coherent_dma_mask <= DMA_BIT_MASK(32))
 		flags |= GFP_DMA;
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
+
+	if (!(flags & __GFP_WAIT) && IS_ENABLED(CONFIG_DMA_CMA)) {
 		struct page *page;
 
 		size = PAGE_ALIGN(size);

I think that's the wrong condition here. You want to use CMA if (flags & __GFP_WAIT). CMA does not support atomic allocations so it can fall back to swiotlb_alloc_coherent().

Quoted 14 lines
arch/arm64/mm/dma-mapping.c +3 -2
@@ -73,50 +124,56 @@ static void __dma_free_coherent(struct device *dev, size_t size,
 				void *vaddr, dma_addr_t dma_handle,
 				struct dma_attrs *attrs)
 {
+	bool freed;
+	phys_addr_t paddr = dma_to_phys(dev, dma_handle);
+
 	if (dev == NULL) {
 		WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
 		return;
 	}
 
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
-		phys_addr_t paddr = dma_to_phys(dev, dma_handle);

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 141

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +2 -3
 
-		dma_release_from_contiguous(dev,
+	freed = dma_release_from_contiguous(dev,
 					phys_to_page(paddr),
 					size >> PAGE_SHIFT);
-	} else {
+	if (!freed)
 		swiotlb_free_coherent(dev, size, vaddr, dma_handle);
-	}
 }

Is __dma_free_coherent() ever called in atomic context? If yes, the dma_release_from_contiguous() may not like it since it tries to acquire a mutex. But since we don't have the gfp flags here, we don't have an easy way to know what to call.

So the initial idea of always calling __alloc_from_pool() for both coherent/non-coherent cases would work better (but still with a single shared pool, see below).

Quoted 12 lines
arch/arm64/mm/dma-mapping.c +2 -3
 static void *__dma_alloc_noncoherent(struct device *dev, size_t size,
 				     dma_addr_t *dma_handle, gfp_t flags,
 				     struct dma_attrs *attrs)
 {
-	struct page *page, **map;
+	struct page *page;
 	void *ptr, *coherent_ptr;
-	int order, i;
 
 	size = PAGE_ALIGN(size);
-	order = get_order(size);
+

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 161

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+	if (!(flags & __GFP_WAIT)) {
+		struct page *page = NULL;
+		void *addr = __alloc_from_pool(size, &page);
+
+		if (addr)
+			*dma_handle = phys_to_dma(dev, page_to_phys(page));
+
+		return addr;
+
+	}

If we do the above for the __dma_alloc_coherent() case, we could use the same pool but instead of returning addr it could just return page_address(page). The downside of sharing the pool is that you need cache flushing for every allocation (which we already do for the non-atomic case).

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 402

Quoted 13 lines
arch/arm64/mm/dma-mapping.c +9 -0
@@ -332,6 +391,67 @@ static struct notifier_block amba_bus_nb = {
 
 extern int swiotlb_late_init_with_default_size(size_t default_size);
 
+static int __init atomic_pool_init(void)
+{
+	pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+	unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+	struct page *page;
+	void *addr;
+
+
+	if (dev_get_cma_area(NULL))

Is it worth using this condition for other places where we check IS_ENABLED(CONFIG_DMA_CMA) (maybe as a separate patch).

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 406

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+		page = dma_alloc_from_contiguous(NULL, nr_pages,
+					get_order(atomic_pool_size));
+	else
+		page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));

One problem here is that the atomic pool wouldn't be able to honour GFP_DMA (in the latest kernel, CMA is by default in ZONE_DMA). You should probably pass GFP_KERNEL|GFP_DMA here. You could also use the swiotlb_alloc_coherent() which, with a NULL dev, assumes 32-bit DMA mask but it still expects GFP_DMA to be passed.

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+	if (page) {
+		int ret;
+
+		atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+		if (!atomic_pool)

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+			goto free_page;
+
+		addr = dma_common_contiguous_remap(page, atomic_pool_size,
+					VM_USERMAP, prot, atomic_pool_init);
+
+		if (!addr)
+			goto destroy_genpool;
+
+		memset(addr, 0, atomic_pool_size);
+		__dma_flush_range(addr, addr + atomic_pool_size);

If you add the flushing in the __dma_alloc_noncoherent(), it won't be needed here (of course, more efficient here but it would not work if we share the pool).

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 453

Quoted 1 line
arch/arm64/mm/dma-mapping.c +1 -0
+postcore_initcall(atomic_pool_init);

Why not arch_initcall? Or even better, we could have a common DMA init function that calls swiotlb_late_init() and atomic_pool_init() (in this order if you decide to use swiotlb allocation above).

Signature · 1 line
Catalin
#22 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

Quoted 54 lines · Catalin Marinas
Quoted 4 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +0 -0
index 4164c5a..a2487f1 100644

[...]

arch/arm64/mm/dma-mapping.c static void *__dma_alloc_coherent(struct device *dev, size_t size, · line 110

Quoted 13 lines

static void *__dma_alloc_coherent(struct device *dev, size_t size,

arch/arm64/mm/dma-mapping.c +2 -1
 				  dma_addr_t *dma_handle, gfp_t flags,
 				  struct dma_attrs *attrs)
@@ -53,7 +103,8 @@ static void *__dma_alloc_coherent(struct device *dev, size_t size,
 	if (IS_ENABLED(CONFIG_ZONE_DMA) &&
 	    dev->coherent_dma_mask <= DMA_BIT_MASK(32))
 		flags |= GFP_DMA;
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
+
+	if (!(flags & __GFP_WAIT) && IS_ENABLED(CONFIG_DMA_CMA)) {
 		struct page *page;
 
 		size = PAGE_ALIGN(size);

I think that's the wrong condition here. You want to use CMA if (flags & __GFP_WAIT). CMA does not support atomic allocations so it can fall back to swiotlb_alloc_coherent().

Quoted 14 lines
arch/arm64/mm/dma-mapping.c +3 -2
@@ -73,50 +124,56 @@ static void __dma_free_coherent(struct device *dev, size_t size,
 				void *vaddr, dma_addr_t dma_handle,
 				struct dma_attrs *attrs)
 {
+	bool freed;
+	phys_addr_t paddr = dma_to_phys(dev, dma_handle);
+
 	if (dev == NULL) {
 		WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
 		return;
 	}
 
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
-		phys_addr_t paddr = dma_to_phys(dev, dma_handle);

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 141

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +2 -3
 
-		dma_release_from_contiguous(dev,
+	freed = dma_release_from_contiguous(dev,
 					phys_to_page(paddr),
 					size >> PAGE_SHIFT);
-	} else {
+	if (!freed)
 		swiotlb_free_coherent(dev, size, vaddr, dma_handle);
-	}
 }

Is __dma_free_coherent() ever called in atomic context? If yes, the dma_release_from_contiguous() may not like it since it tries to acquire a mutex. But since we don't have the gfp flags here, we don't have an easy way to know what to call.

Quoted 4 lines

So the initial idea of always calling __alloc_from_pool() for both coherent/non-coherent cases would work better (but still with a single shared pool, see below).

We should be okay

__dma_free_coherent -> dma_release_from_contiguous -> cma_release which bounds checks the CMA region before taking any mutexes unless I missed something.

The existing behavior on arm is to not allow non-atomic allocations to be freed atomic context when CMA is enabled so we'd be giving arm64 more leeway there. Is being able to free non-atomic allocations in atomic context really necessary?

Quoted 33 lines
Quoted 12 lines
arch/arm64/mm/dma-mapping.c +2 -3
 static void *__dma_alloc_noncoherent(struct device *dev, size_t size,
 				     dma_addr_t *dma_handle, gfp_t flags,
 				     struct dma_attrs *attrs)
 {
-	struct page *page, **map;
+	struct page *page;
 	void *ptr, *coherent_ptr;
-	int order, i;
 
 	size = PAGE_ALIGN(size);
-	order = get_order(size);
+

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 161

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+	if (!(flags & __GFP_WAIT)) {
+		struct page *page = NULL;
+		void *addr = __alloc_from_pool(size, &page);
+
+		if (addr)
+			*dma_handle = phys_to_dma(dev, page_to_phys(page));
+
+		return addr;
+
+	}

If we do the above for the __dma_alloc_coherent() case, we could use the same pool but instead of returning addr it could just return page_address(page). The downside of sharing the pool is that you need cache flushing for every allocation (which we already do for the non-atomic case).

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +0 -0
@@ -332,6 +391,67 @@ static struct notifier_block amba_bus_nb = {
 
 extern int swiotlb_late_init_with_default_size(size_t default_size);
 
Quoted 13 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 402

Quoted 9 lines
arch/arm64/mm/dma-mapping.c +9 -0
+static int __init atomic_pool_init(void)
+{
+	pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+	unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+	struct page *page;
+	void *addr;
+
+
+	if (dev_get_cma_area(NULL))

Is it worth using this condition for other places where we check IS_ENABLED(CONFIG_DMA_CMA) (maybe as a separate patch).

Yes, it would be good to match arm in that respect.

Quoted 11 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 406

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+		page = dma_alloc_from_contiguous(NULL, nr_pages,
+					get_order(atomic_pool_size));
+	else
+		page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));

One problem here is that the atomic pool wouldn't be able to honour GFP_DMA (in the latest kernel, CMA is by default in ZONE_DMA). You should probably pass GFP_KERNEL|GFP_DMA here. You could also use the swiotlb_alloc_coherent() which, with a NULL dev, assumes 32-bit DMA mask but it still expects GFP_DMA to be passed.

I think I missed updating this to GFP_DMA. The only advantage I would see to using swiotlb_alloc_coherent vs. alloc_pages directly would be to allow the fallback to using a bounce buffer if __get_free_pages failed. I'll keep this as alloc_pages for now; it can be changed later if there is a particular need for swiotlb behavior.

Quoted 20 lines
Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+	if (page) {
+		int ret;
+
+		atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+		if (!atomic_pool)

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+			goto free_page;
+
+		addr = dma_common_contiguous_remap(page, atomic_pool_size,
+					VM_USERMAP, prot, atomic_pool_init);
+
+		if (!addr)
+			goto destroy_genpool;
+
+		memset(addr, 0, atomic_pool_size);
+		__dma_flush_range(addr, addr + atomic_pool_size);

If you add the flushing in the __dma_alloc_noncoherent(), it won't be needed here (of course, more efficient here but it would not work if we share the pool).

Quoted 6 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 453

Quoted 1 line
arch/arm64/mm/dma-mapping.c +1 -0
+postcore_initcall(atomic_pool_init);

Why not arch_initcall? Or even better, we could have a common DMA init function that calls swiotlb_late_init() and atomic_pool_init() (in this order if you decide to use swiotlb allocation above).

Good point. I'll combine the two.

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#23 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

Quoted 36 lines · Laura Abbott
Quoted 31 lines · Catalin Marinas
Quoted 14 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +3 -2
@@ -73,50 +124,56 @@ static void __dma_free_coherent(struct device *dev, size_t size,
 				void *vaddr, dma_addr_t dma_handle,
 				struct dma_attrs *attrs)
 {
+	bool freed;
+	phys_addr_t paddr = dma_to_phys(dev, dma_handle);
+
 	if (dev == NULL) {
 		WARN_ONCE(1, "Use an actual device structure for DMA allocation\n");
 		return;
 	}
 
-	if (IS_ENABLED(CONFIG_DMA_CMA)) {
-		phys_addr_t paddr = dma_to_phys(dev, dma_handle);

arch/arm64/mm/dma-mapping.c static void __dma_free_coherent(struct device *dev, size_t size, · line 141

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +2 -3
 
-		dma_release_from_contiguous(dev,
+	freed = dma_release_from_contiguous(dev,
 					phys_to_page(paddr),
 					size >> PAGE_SHIFT);
-	} else {
+	if (!freed)
 		swiotlb_free_coherent(dev, size, vaddr, dma_handle);
-	}
 }

Is __dma_free_coherent() ever called in atomic context? If yes, the dma_release_from_contiguous() may not like it since it tries to acquire a mutex. But since we don't have the gfp flags here, we don't have an easy way to know what to call.

Quoted 3 lines

So the initial idea of always calling __alloc_from_pool() for both coherent/non-coherent cases would work better (but still with a single shared pool, see below).

Quoted 5 lines

We should be okay

__dma_free_coherent -> dma_release_from_contiguous -> cma_release which bounds checks the CMA region before taking any mutexes unless I missed something.

Ah, good point. I missed the pfn range check in
dma_release_from_contiguous.

Quoted 4 lines

The existing behavior on arm is to not allow non-atomic allocations to be freed atomic context when CMA is enabled so we'd be giving arm64 more leeway there. Is being able to free non-atomic allocations in atomic context really necessary?

No. I was worried that an atomic coherent allocation (falling back to swiotlb) would trigger some CMA mutex in atomic context on the freeing path. But you are right, it shouldn't happen.

Quoted 12 lines
Quoted 11 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 406

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+		page = dma_alloc_from_contiguous(NULL, nr_pages,
+					get_order(atomic_pool_size));
+	else
+		page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));

One problem here is that the atomic pool wouldn't be able to honour GFP_DMA (in the latest kernel, CMA is by default in ZONE_DMA). You should probably pass GFP_KERNEL|GFP_DMA here. You could also use the swiotlb_alloc_coherent() which, with a NULL dev, assumes 32-bit DMA mask but it still expects GFP_DMA to be passed.

Quoted 5 lines

I think I missed updating this to GFP_DMA. The only advantage I would see to using swiotlb_alloc_coherent vs. alloc_pages directly would be to allow the fallback to using a bounce buffer if __get_free_pages failed. I'll keep this as alloc_pages for now; it can be changed later if there is a particular need for swiotlb behavior.

That's fine. Since we don't have a device at this point, I don't see how swiotlb could fall back to the bounce buffer.

Thanks.

Signature · 1 line
Catalin
maintainer
#24 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

In reply to Laura Abbott
Quoted 13 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +13 -0
+       pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+       unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+       struct page *page;
+       void *addr;
+
+
+       if (dev_get_cma_area(NULL))
+               page = dma_alloc_from_contiguous(NULL, nr_pages,
+                                       get_order(atomic_pool_size));
+       else
+               page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));
+
+

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 418

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+       if (page) {
+               int ret;
+
+               atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+               if (!atomic_pool)
+                       goto free_page;
+
+               addr = dma_common_contiguous_remap(page, atomic_pool_size,
+                                       VM_USERMAP, prot, atomic_pool_init);
+

I just stumbled over this thread and noticed the code here: When you do alloc_pages() above, you actually get pages that are already mapped into the linear kernel mapping as cacheable pages. Your new dma_common_contiguous_remap tries to map them as noncacheable. This seems broken because it allows the CPU to treat both mappings as cacheable, and that won't be coherent with device DMA.

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+               if (!addr)
+                       goto destroy_genpool;
+
+               memset(addr, 0, atomic_pool_size);
+               __dma_flush_range(addr, addr + atomic_pool_size);

It also seems weird to flush the cache on a virtual address of an uncacheable mapping. Is that well-defined? In the CMA case, the original mapping should already be uncached here, so you don't need to flush it. In the alloc_pages() case, I think you need to unmap the pages from the linear mapping instead.

Arnd

maintainer
#25 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

Quoted 23 lines · Arnd Bergmann
Quoted 22 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +22 -0
+       pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+       unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+       struct page *page;
+       void *addr;
+
+
+       if (dev_get_cma_area(NULL))
+               page = dma_alloc_from_contiguous(NULL, nr_pages,
+                                       get_order(atomic_pool_size));
+       else
+               page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));
+
+
+       if (page) {
+               int ret;
+
+               atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+               if (!atomic_pool)
+                       goto free_page;
+
+               addr = dma_common_contiguous_remap(page, atomic_pool_size,
+                                       VM_USERMAP, prot, atomic_pool_init);
Quoted 8 lines
Quoted 1 line

+

I just stumbled over this thread and noticed the code here: When you do alloc_pages() above, you actually get pages that are already mapped into the linear kernel mapping as cacheable pages. Your new dma_common_contiguous_remap tries to map them as noncacheable. This seems broken because it allows the CPU to treat both mappings as cacheable, and that won't be coherent with device DMA.

It does *not* allow the CPU to treat both as cacheable. It treats the non-cacheable mapping as non-cacheable (and the cacheable one as cacheable). The only requirements the ARM ARM makes in this situation (B2.9 point 5 in the ARMv8 ARM):

- Before writing to a location not using the Write-Back attribute, software must invalidate, or clean, a location from the caches if any agent might have written to the location with the Write-Back attribute. This avoids the possibility of overwriting the location with stale data.
- After writing to a location with the Write-Back attribute, software must clean the location from the caches, to make the write visible to external memory.
- Before reading the location with a cacheable attribute, software must invalidate the location from the caches, to ensure that any value held in the caches reflects the last value made visible in external memory.

So we as long as the CPU accesses such memory only via the non-cacheable mapping, the only requirement is to flush the cache so that there are no dirty lines that could be evicted.

(if the mismatched attributes were for example Normal vs Device, the Device guarantees would be lost but in the cacheable vs non-cacheable it's not too bad; same for ARMv7).

Quoted 8 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+               if (!addr)
+                       goto destroy_genpool;
+
+               memset(addr, 0, atomic_pool_size);
+               __dma_flush_range(addr, addr + atomic_pool_size);

It also seems weird to flush the cache on a virtual address of an uncacheable mapping. Is that well-defined?

Yes. According to D5.8.1 (Data and unified caches), "if cache maintenance is performed on a memory location, the effect of that cache maintenance is visible to all aliases of that physical memory location. These properties are consistent with implementing all caches that can handle data accesses as Physically-indexed, physically-tagged (PIPT) caches".

Quoted 3 lines

In the CMA case, the
original mapping should already be uncached here, so you don't need to flush it.

I don't think it is non-cacheable already, at least not for arm64 (CMA can be used on coherent architectures as well).

Quoted 2 lines

In the alloc_pages() case, I think you need to unmap the pages from the linear mapping instead.

Even if unmapped, it would not remove dirty cache lines (which are associated with physical addresses anyway). But we don't need to worry about unmapping anyway, see above (that's unless we find some architecture implementation where having such cacheable/non-cacheable aliases is not efficient enough, the efficiency is not guaranteed by the ARM ARM, just the correct behaviour).

Signature · 1 line
Catalin
#26 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

Quoted 1 line · Catalin Marinas

On Tue, Jul 22, 2014 at 07:06:44PM +0100, Arnd Bergmann wrote:

[...]

Quoted 9 lines
Quoted 8 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+               if (!addr)
+                       goto destroy_genpool;
+
+               memset(addr, 0, atomic_pool_size);
+               __dma_flush_range(addr, addr + atomic_pool_size);

It also seems weird to flush the cache on a virtual address of an uncacheable mapping. Is that well-defined?

Quoted 7 lines

Yes. According to D5.8.1 (Data and unified caches), "if cache maintenance is performed on a memory location, the effect of that cache maintenance is visible to all aliases of that physical memory location. These properties are consistent with implementing all caches that can handle data accesses as Physically-indexed, physically-tagged (PIPT) caches".

This was actually unintentional on my part. I'm going to clean this up to flush via the existing cached mapping to make it clearer what's going on.

Quoted 7 lines
Quoted 3 lines

In the CMA case, the
original mapping should already be uncached here, so you don't need to flush it.

I don't think it is non-cacheable already, at least not for arm64 (CMA can be used on coherent architectures as well).

Memory allocated via dma_alloc_from_contiguous is not guaranteed to be uncached. On arm, we allocate the page of memory and the remap it as appropriate.

Quoted 10 lines
Quoted 2 lines

In the alloc_pages() case, I think you need to unmap the pages from the linear mapping instead.

Even if unmapped, it would not remove dirty cache lines (which are associated with physical addresses anyway). But we don't need to worry about unmapping anyway, see above (that's unless we find some architecture implementation where having such cacheable/non-cacheable aliases is not efficient enough, the efficiency is not guaranteed by the ARM ARM, just the correct behaviour).

Let's hope that never happens.

Thanks,
Laura

Signature · 2 lines
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
maintainer
#27 lore raw

Re: [PATCHv4 5/5] arm64: Add atomic pool for non-coherent and CMA allocations.

In reply to Catalin Marinas
Quoted 50 lines · On Tuesday 22 July 2014 22:03:52 Catalin Marinas wrote:
Quoted 23 lines · Arnd Bergmann
Quoted 22 lines · Laura Abbott
arch/arm64/mm/dma-mapping.c +22 -0
+       pgprot_t prot = __pgprot(PROT_NORMAL_NC);
+       unsigned long nr_pages = atomic_pool_size >> PAGE_SHIFT;
+       struct page *page;
+       void *addr;
+
+
+       if (dev_get_cma_area(NULL))
+               page = dma_alloc_from_contiguous(NULL, nr_pages,
+                                       get_order(atomic_pool_size));
+       else
+               page = alloc_pages(GFP_KERNEL, get_order(atomic_pool_size));
+
+
+       if (page) {
+               int ret;
+
+               atomic_pool = gen_pool_create(PAGE_SHIFT, -1);
+               if (!atomic_pool)
+                       goto free_page;
+
+               addr = dma_common_contiguous_remap(page, atomic_pool_size,
+                                       VM_USERMAP, prot, atomic_pool_init);
Quoted 8 lines
Quoted 1 line

+

I just stumbled over this thread and noticed the code here: When you do alloc_pages() above, you actually get pages that are already mapped into the linear kernel mapping as cacheable pages. Your new dma_common_contiguous_remap tries to map them as noncacheable. This seems broken because it allows the CPU to treat both mappings as cacheable, and that won't be coherent with device DMA.

It does *not* allow the CPU to treat both as cacheable. It treats the non-cacheable mapping as non-cacheable (and the cacheable one as cacheable). The only requirements the ARM ARM makes in this situation (B2.9 point 5 in the ARMv8 ARM):

- Before writing to a location not using the Write-Back attribute, software must invalidate, or clean, a location from the caches if any agent might have written to the location with the Write-Back attribute. This avoids the possibility of overwriting the location with stale data.
- After writing to a location with the Write-Back attribute, software must clean the location from the caches, to make the write visible to external memory.
- Before reading the location with a cacheable attribute, software must invalidate the location from the caches, to ensure that any value held in the caches reflects the last value made visible in external memory.

Quoted 3 lines

So we as long as the CPU accesses such memory only via the non-cacheable mapping, the only requirement is to flush the cache so that there are no dirty lines that could be evicted.

Ok, thanks for the explanation.

Quoted 3 lines

(if the mismatched attributes were for example Normal vs Device, the Device guarantees would be lost but in the cacheable vs non-cacheable it's not too bad; same for ARMv7).

Right, that's probabably what I misremembered.

Quoted 9 lines
Quoted 8 lines

arch/arm64/mm/dma-mapping.c static struct notifier_block amba_bus_nb = { · line 423

Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+               if (!addr)
+                       goto destroy_genpool;
+
+               memset(addr, 0, atomic_pool_size);
+               __dma_flush_range(addr, addr + atomic_pool_size);

It also seems weird to flush the cache on a virtual address of an uncacheable mapping. Is that well-defined?

Quoted 6 lines

Yes. According to D5.8.1 (Data and unified caches), "if cache maintenance is performed on a memory location, the effect of that cache maintenance is visible to all aliases of that physical memory location. These properties are consistent with implementing all caches that can handle data accesses as Physically-indexed, physically-tagged (PIPT) caches".

interesting.

Quoted 6 lines
Quoted 3 lines

In the CMA case, the
original mapping should already be uncached here, so you don't need to flush it.

I don't think it is non-cacheable already, at least not for arm64 (CMA can be used on coherent architectures as well).

Ok, I see it now.

Sorry for all the confusion on my part.

Arnd