ARM: Fix dma_alloc_coherent() and friends for NOMMU
Subsystems (4)
v5 cover ARM: Fix dma_alloc_coherent() and friends for NOMMU
Hi,
It seem that addition of cache support for M-class CPUs uncovered latent bug in DMA usage. NOMMU memory model has been treated as being always consistent; however, for R/M CPU classes memory can be covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks dma_alloc_coherent() and friends, since data can stuck in caches now or be buffered.
This patch set is trying to address the issue by providing region of memory suitable for consistent DMA operations. It is supposed that such region is marked by MPU as non-cacheable. Robin suggested to advertise such memory as reserved shared-dma-pool, rather then using homebrew command line option, and extend dma-coherent to provide default DMA area in the similar way as it is done for CMA (PATCH 4/7). It allows us to offload all bookkeeping on generic coherent DMA framework, and it seems that it might be reused by other architectures like c6x and blackfin.
While reviewing/testing previous vesrions of the patch set it turned out that dma-coherent does not take into account "dma-ranges" device tree property, so it is addressed in PATCH 3/7.
For ARM, dedicated DMA region is required for cases other than:
- MMU/MPU is off
- cpu is v7m w/o cache support
- device is coherent
In case one of the above conditions is true dma operations are forced to be coherent and wired with dma_noop_ops.
To make life easier NOMMU dma operations are kept in separate compilation unit.
Since the issue was reported in the same time as Benjamin sent his patch [1] to allow mmap for NOMMU, his case is also addressed in this series (PATCH 1/7 and PATCH 2/7).
Thanks!
[1] http://www.armlinux.org.uk/developer/patches/viewpatch.php?id=8633/1
Vladimir Murzin (7):
dma: Take into account dma_pfn_offset
dma: Add simple dma_noop_mmap
drivers: dma-coherent: Account dma_pfn_offset when used with device
tree
drivers: dma-coherent: Introduce default DMA pool
ARM: NOMMU: Introduce dma operations for noMMU
ARM: NOMMU: Set ARM_DMA_MEM_BUFFERABLE for M-class cpus
ARM: dma-mapping: Remove traces of NOMMU code
.../bindings/reserved-memory/reserved-memory.txt | 3 + arch/arm/include/asm/dma-mapping.h | 3 +- arch/arm/mm/Kconfig | 2 +- arch/arm/mm/Makefile | 5 +- arch/arm/mm/dma-mapping-nommu.c | 252 +++++++++++++++++++++ arch/arm/mm/dma-mapping.c | 26 +-- drivers/base/dma-coherent.c | 76 ++++++- lib/dma-noop.c | 29 ++- 8 files changed, 355 insertions(+), 41 deletions(-) create mode 100644 arch/arm/mm/dma-mapping-nommu.c
Signature · 1 line
2.0.0
RFC PATCH v5 1/7 dma: Take into account dma_pfn_offset
Even though dma-noop-ops assumes 1:1 memory mapping DMA memory range can be different to RAM. For example, ARM STM32F4 MCU offers the possibility to remap SDRAM from 0xc000_0000 to 0x0 to get CPU performance boost, but DMA continue to see SDRAM at 0xc000_0000. This difference in mapping is handled via device-tree "dma-range" property which leads to dev->dma_pfn_offset is set nonzero. To handle such cases take dma_pfn_offset into account.
--- lib/dma-noop.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
lib/dma-noop.c +5 -3
@@ -7,6 +7,7 @@#include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> +#include <linux/pfn.h> static void *dma_noop_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp,
@@ -16,7 +17,8 @@ static void *dma_noop_alloc(struct device *dev, size_t size, ret = (void *)__get_free_pages (gfp,get_order (size));if (ret) - *dma_handle =virt_to_phys (ret); + *dma_handle =virt_to_phys (ret) -PFN_PHYS (dev->dma_pfn_offset); +return ret; }
@@ -32,7 +34,7 @@ static dma_addr_t dma_noop_map_page(struct device *dev, struct page *page, enum dma_data_direction dir, unsigned long attrs) { -return page_to_phys (page) + offset; +return page_to_phys (page) + offset -PFN_PHYS (dev->dma_pfn_offset); } static int dma_noop_map_sg(struct device *dev, struct scatterlist *sgl, int nents,
@@ -47,7 +49,7 @@ static int dma_noop_map_sg(struct device *dev, struct scatterlist *sgl, int nentBUG_ON (!sg_page (sg)); va =sg_virt (sg); -sg_dma_address (sg) = (dma_addr_t )virt_to_phys (va); +sg_dma_address (sg) = (dma_addr_t)(virt_to_phys (va) -PFN_PHYS (dev->dma_pfn_offset));sg_dma_len (sg) = sg->length; }
Signature · 1 line
2.0.0
RFC PATCH v5 1/7 dma: Take into account dma_pfn_offset
Quoted 29 lines · Vladimir Murzin
Even though dma-noop-ops assumes 1:1 memory mapping DMA memory range can be different to RAM. For example, ARM STM32F4 MCU offers the possibility to remap SDRAM from 0xc000_0000 to
0x0to get CPU performance boost, but DMA continue to see SDRAM at 0xc000_0000. This difference in mapping is handled via device-tree "dma-range" property which leads todev->dma_pfn_offsetis set nonzero. To handle such cases takedma_pfn_offsetinto account.Cc Joerg Roedel <[email protected]>Cc Christian Borntraeger <[email protected]>Reported-by Benjamin Gaignard <[email protected]>Signed-off-by Vladimir Murzin <[email protected]>--- lib/dma-noop.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)lib/dma-noop.c +1
-0@@ -7,6 +7,7 @@#include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> +#include <linux/pfn.h> static void *dma_noop_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp,
lib/dma-noop.c static void *dma_noop_alloc(struct device *dev, size_t size, · line 21
Quoted 6 lines
lib/dma-noop.c +2
-1@@ -16,7 +17,8 @@ static void *dma_noop_alloc(struct device *dev, size_t size, ret = (void *)__get_free_pages (gfp,get_order (size));if (ret) - *dma_handle =virt_to_phys (ret); + *dma_handle =virt_to_phys (ret) -PFN_PHYS (dev->dma_pfn_offset); +
If you need to do a '-' operation here, the offset is basically a cpu_pfn_offset for the device. Is that correct?
Joerg
RFC PATCH v5 1/7 dma: Take into account dma_pfn_offset
Quoted 31 lines · Joerg Roedel
Quoted 30 lines · Vladimir Murzin
Even though dma-noop-ops assumes 1:1 memory mapping DMA memory range can be different to RAM. For example, ARM STM32F4 MCU offers the possibility to remap SDRAM from 0xc000_0000 to
0x0to get CPU performance boost, but DMA continue to see SDRAM at 0xc000_0000. This difference in mapping is handled via device-tree "dma-range" property which leads todev->dma_pfn_offsetis set nonzero. To handle such cases takedma_pfn_offsetinto account.Cc Joerg Roedel <[email protected]>Cc Christian Borntraeger <[email protected]>Reported-by Benjamin Gaignard <[email protected]>Signed-off-by Vladimir Murzin <[email protected]>--- lib/dma-noop.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)lib/dma-noop.c +1
-0@@ -7,6 +7,7 @@#include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> +#include <linux/pfn.h> static void *dma_noop_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp,@@ -16,7 +17,8 @@ static void *dma_noop_alloc(struct device *dev, size_t size,
Quoted 9 lines
lib/dma-noop.c static void *dma_noop_alloc(struct device *dev, size_t size, · line 21
Quoted 5 lines
lib/dma-noop.c +2
-1ret = (void *)__get_free_pages (gfp,get_order (size));if (ret) - *dma_handle =virt_to_phys (ret); + *dma_handle =virt_to_phys (ret) -PFN_PHYS (dev->dma_pfn_offset); +If you need to do a '-' operation here, the offset is basically a
cpu_pfn_offsetfor the device. Is that correct?
dma_pfn_offset is calculated as PFN_DOWN(paddr - dma_addr) in of_dma_configure(), it is why '-' is used here.
Cheers
Vladimir
Quoted 4 lines
Joerg
RFC PATCH v5 1/7 dma: Take into account dma_pfn_offset
In reply to Joerg RoedelQuoted 31 lines · Joerg Roedel
Quoted 30 lines · Vladimir Murzin
Even though dma-noop-ops assumes 1:1 memory mapping DMA memory range can be different to RAM. For example, ARM STM32F4 MCU offers the possibility to remap SDRAM from 0xc000_0000 to
0x0to get CPU performance boost, but DMA continue to see SDRAM at 0xc000_0000. This difference in mapping is handled via device-tree "dma-range" property which leads todev->dma_pfn_offsetis set nonzero. To handle such cases takedma_pfn_offsetinto account.Cc Joerg Roedel <[email protected]>Cc Christian Borntraeger <[email protected]>Reported-by Benjamin Gaignard <[email protected]>Signed-off-by Vladimir Murzin <[email protected]>--- lib/dma-noop.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)lib/dma-noop.c +1
-0@@ -7,6 +7,7 @@#include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> +#include <linux/pfn.h> static void *dma_noop_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp,@@ -16,7 +17,8 @@ static void *dma_noop_alloc(struct device *dev, size_t size,
Quoted 8 lines
lib/dma-noop.c static void *dma_noop_alloc(struct device *dev, size_t size, · line 21
Quoted 5 lines
lib/dma-noop.c +2
-1ret = (void *)__get_free_pages (gfp,get_order (size));if (ret) - *dma_handle =virt_to_phys (ret); + *dma_handle =virt_to_phys (ret) -PFN_PHYS (dev->dma_pfn_offset); +If you need to do a '-' operation here, the offset is basically a
cpu_pfn_offsetfor the device. Is that correct?
Effectively, yes. The value of dev->dma_pfn_offset can be thought of as the physical (CPU) PFN to which "bus PFN" 0 (for the given device) corresponds. On the Keystone platform which begat this machinery, the DRAM is at 0x08_000_0000-0x09_ffff_ffff, but there is an I/O-coherent alias of the first 2GB which appears at 0x00_8000_0000-0x00_ffff_ffff to devices - thus on that platform 32-bit devices get a dma_mask of 0x7fff_ffff and a dma_pfn_offset of 0x7_8000_0000 >> PAGE_SHIFT, and everything works out.
The STM32 situation is a bit funkier, as it's actually the CPU which is making use of a DRAM alias there, but we achieve the same effect by giving all the DMA masters a (negative) offset to compensate, such that subtracting the offset translates addresses from the alias region back to the "real" physical address when handed off to a DMA master.
Robin.
Quoted 4 lines
Joerg
RFC PATCH v5 2/7 dma: Add simple dma_noop_mmap
In reply to Vladimir MurzinThis patch adds a simple implementation of mmap to dma_noop_ops.
--- lib/dma-noop.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
lib/dma-noop.c +21 -0
@@ -66,6 +66,26 @@ static int dma_noop_supported(struct device *dev, u64 mask)return 1 ; } +static int dma_noop_mmap (struct device *dev,struct vm_area_struct *vma, +void *cpu_addr,dma_addr_t dma_addr,size_t size, +unsigned long attrs) +{ +unsigned long user_count =vma_pages (vma); +unsigned long count =PAGE_ALIGN (size) >>PAGE_SHIFT ; +unsigned long pfn =page_to_pfn (virt_to_page (cpu_addr)); +unsigned long off = vma->vm_pgoff; +int ret = -ENXIO ; + +if (off < count && user_count <= (count - off)) { + ret =remap_pfn_range (vma, vma->vm_start, + pfn + off, + user_count <<PAGE_SHIFT , + vma->vm_page_prot); + } + +return ret; +} +struct dma_map_ops dma_noop_ops = { .alloc = dma_noop_alloc, .free = dma_noop_free,
@@ -73,6 +93,7 @@ struct dma_map_ops dma_noop_ops = { .map_sg = dma_noop_map_sg, .mapping_error = dma_noop_mapping_error, .dma_supported = dma_noop_supported, + .mmap = dma_noop_mmap, };EXPORT_SYMBOL (dma_noop_ops);
Signature · 1 line
2.0.0
RFC PATCH v5 3/7 drivers: dma-coherent: Account dma_pfn_offset when used with device tree
In reply to Vladimir Murzindma_declare_coherent_memory() and friends are designed to account difference in CPU and device addresses. However, when it is used with reserved memory regions there is assumption that CPU and device have the same view on address space. This assumption gets invalid when reserved memory for coherent DMA allocations is referenced by device with non-empty "dma-range" property.
Simply feeding device address as rmem->base + dev->dma_pfn_offset would not work due to reserved memory region can be shared, so this patch turns device address to be expressed with help of CPU address and device's dma_pfn_offset.
For the case where device tree is not used and device sees memory different to CPU we explicitly set device's dma_pfn_offset to accomplish such difference. The latter might look controversial, but it seems only a few drivers set device address different to CPU's:
- drivers/usb/host/ohci-sm501.c
- arch/sh/drivers/pci/fixups-dreamcast.c
so they can be screwed only if dma_pfn_offset there is set and not in sync with device address range - we try to catch such cases with WARN_ON.
--- drivers/base/dma-coherent.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-)
drivers/base/dma-coherent.c +15 -2
@@ -18,6 +18,12 @@ struct dma_coherent_mem {spinlock_t spinlock; }; +static inline dma_addr_t dma_get_device_base (struct device *dev, +struct dma_coherent_mem * mem) +{ +return (mem->pfn_base - dev->dma_pfn_offset) <<PAGE_SHIFT ; +} +static bool dma_init_coherent_memory (phys_addr_t phys_addr,dma_addr_t device_addr,size_t size,int flags,struct dma_coherent_mem **mem)
@@ -83,9 +89,16 @@ static void dma_release_coherent_memory(struct dma_coherent_mem *mem)static int dma_assign_coherent_memory (struct device *dev,struct dma_coherent_mem *mem) { +unsigned long dma_pfn_offset = mem->pfn_base -PFN_DOWN (mem->device_base); +if (dev->dma_mem)return -EBUSY ; +if (dev->dma_pfn_offset) +WARN_ON (dma_pfn_offset && (dev->dma_pfn_offset != dma_pfn_offset)); +else + dev->dma_pfn_offset = dma_pfn_offset; + dev->dma_mem = mem;/* FIXME: this routine just ignores DMA_MEMORY_INCLUDES_CHILDREN */
@@ -133,7 +146,7 @@ void *dma_mark_declared_memory_occupied(struct device *dev,return ERR_PTR (-EINVAL );spin_lock_irqsave (&mem->spinlock, flags); - pos = (device_addr - mem->device_base) >>PAGE_SHIFT ; + pos =PFN_DOWN (device_addr -dma_get_device_base (dev, mem)); err =bitmap_allocate_region (mem->bitmap, pos,get_order (size));spin_unlock_irqrestore (&mem->spinlock, flags);
@@ -186,7 +199,7 @@ int dma_alloc_from_coherent(struct device *dev, ssize_t size,/* * Memory was found in the per-device area. */ - *dma_handle = mem->device_base + (pageno <<PAGE_SHIFT ); + *dma_handle =dma_get_device_base (dev, mem) + (pageno <<PAGE_SHIFT ); *ret = mem->virt_base + (pageno <<PAGE_SHIFT ); dma_memory_map = (mem->flags &DMA_MEMORY_MAP );spin_unlock_irqrestore (&mem->spinlock, flags);
Signature · 1 line
2.0.0
RFC PATCH v5 4/7 drivers: dma-coherent: Introduce default DMA pool
In reply to Vladimir MurzinThis patch introduces default coherent DMA pool similar to default CMA area concept. To keep other users safe code kept under CONFIG_ARM.
--- .../bindings/reserved-memory/reserved-memory.txt | 3 ++ drivers/base/dma-coherent.c | 59 +++++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-)
Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt +3 -0
@@ -68,6 +68,9 @@ Linux implementation note: - If a "linux,cma-default" property is present, then Linux will use the region for the default pool of the contiguous memory allocator. +- If a "linux,dma-default" property is present, then Linux will use the + region for the default pool of the consistent DMA allocator. + Device node references to reserved memory ----------------------------------------- Regions in the /reserved-memory node may be referenced by other device
drivers/base/dma-coherent.c +52 -7
117 lines not on this page · raw message
Signature · 1 line
2.0.0
RFC PATCH v5 5/7 ARM: NOMMU: Introduce dma operations for noMMU
In reply to Vladimir MurzinR/M classes of cpus can have memory covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks dma_alloc_coherent() and friends, since data can stuck in caches now or be buffered.
This patch factors out DMA support for NOMMU configuration into separate entity which provides dedicated dma_ops. We have to handle there several cases:
- configurations with MMU/MPU setup
- configurations without MMU/MPU setup
- special case for M-class, since caches and MPU there are optional
In general we rely on default DMA area for coherent allocations or/and per-device memory reserves suitable for coherent DMA, so if such regions are set coherent allocations go from there.
In case MPU/MPU was not setup we fallback to normal page allocator for DMA memory allocation.
In case we run M-class cpus, for configuration without cache support (like Cortex-M3/M4) dma operations are forced to be coherent and wired with dma-noop (such decision is made based on cacheid global variable); however, if caches are detected there and no DMA coherent region is given (either default or per-device), dma is disallowed even MPU is not set - it is because M-class implement system memory map which defines part of address space as Normal memory.
--- arch/arm/include/asm/dma-mapping.h | 3 +- arch/arm/mm/Makefile | 5 +- arch/arm/mm/dma-mapping-nommu.c | 252 +++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 arch/arm/mm/dma-mapping-nommu.c
arch/arm/include/asm/dma-mapping.h +2 -1
@@ -20,7 +20,8 @@ static inline struct dma_map_ops *__generic_dma_ops(struct device *dev) {if (dev && dev->archdata.dma_ops)return dev->archdata.dma_ops; -return &arm_dma_ops; + +return IS_ENABLED (CONFIG_MMU ) ? &arm_dma_ops : &dma_noop_ops; }static inline struct dma_map_ops *get_dma_ops (struct device *dev)
arch/arm/mm/Makefile +2 -3
@@ -2,9 +2,8 @@ # Makefile for the linux arm-specific parts of the memory manager. # -obj-y := dma-mapping.o extable.o fault.o init.o \ - iomap.o - +obj-y := extable.o fault.o init.o iomap.o +obj-y += dma-mapping$(MMUEXT).o obj-$(CONFIG_MMU) += fault-armv.o flush.o idmap.o ioremap.o \ mmap.o pgd.o mmu.o pageattr.o
arch/arm/mm/dma-mapping-nommu.c new file +252 -0
252 lines not on this page · raw message
Signature · 1 line
2.0.0
RFC PATCH v5 5/7 ARM: NOMMU: Introduce dma operations for noMMU
Quoted 130 lines · Vladimir Murzin
R/M classes of cpus can have memory covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks
dma_alloc_coherent()and friends, since data can stuck in caches now or be buffered.This patch factors out DMA support for NOMMU configuration into separate entity which provides dedicated
dma_ops. We have to handle there several cases:
- configurations with MMU/MPU setup
- configurations without MMU/MPU setup
- special case for M-class, since caches and MPU there are optionalIn general we rely on default DMA area for coherent allocations or/and per-device memory reserves suitable for coherent DMA, so if such regions are set coherent allocations go from there.
In case MPU/MPU was not setup we fallback to normal page allocator for DMA memory allocation.
In case we run M-class cpus, for configuration without cache support (like Cortex-M3/M4) dma operations are forced to be coherent and wired with dma-noop (such decision is made based on cacheid global variable); however, if caches are detected there and no DMA coherent region is given (either default or per-device), dma is disallowed even MPU is not set - it is because M-class implement system memory map which defines part of address space as Normal memory.
Reported-by Alexandre Torgue <[email protected]>Reported-by Andras Szemzo <[email protected]>Signed-off-by Vladimir Murzin <[email protected]>--- arch/arm/include/asm/dma-mapping.h | 3 +- arch/arm/mm/Makefile | 5 +- arch/arm/mm/dma-mapping-nommu.c | 252 +++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 arch/arm/mm/dma-mapping-nommu.carch/arm/include/asm/dma-mapping.h +2
-1@@ -20,7 +20,8 @@ static inline struct dma_map_ops *__generic_dma_ops(struct device *dev) {if (dev && dev->archdata.dma_ops)return dev->archdata.dma_ops; -return &arm_dma_ops; + +return IS_ENABLED (CONFIG_MMU ) ? &arm_dma_ops : &dma_noop_ops; }static inline struct dma_map_ops *get_dma_ops (struct device *dev)arch/arm/mm/Makefile +2
-3@@ -2,9 +2,8 @@ # Makefile for the linux arm-specific parts of the memory manager. # -obj-y := dma-mapping.o extable.o fault.o init.o \ - iomap.o - +obj-y := extable.o fault.o init.o iomap.o +obj-y += dma-mapping$(MMUEXT).o obj-$(CONFIG_MMU) += fault-armv.o flush.o idmap.o ioremap.o \ mmap.o pgd.o mmu.o pageattr.oarch/arm/mm/dma-mapping-nommu.c new file +57
-0@@ -0,0 +1,252 @@ +/* +* Based on linux/arch/arm/mm/dma-mapping.c +* +* Copyright (C) 2000-2004 Russell King +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +*/ + +#include <linux/export.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/scatterlist.h> + +#include <asm/cachetype.h> +#include <asm/cacheflush.h> +#include <asm/outercache.h> +#include <asm/cp15.h> + +#include "dma.h" + +/* +* dma_noop_ops is used if +* - MMU/MPU is off +* - cpu is v7m w/o cache support +* - device is coherent +* otherwise arm_nommu_dma_ops is used. +* +* arm_nommu_dma_ops rely on consistent DMA memory (please, refer to +* [1] on how to declare such memory). +* +* [1] Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt +*/ + +static void *arm_nommu_dma_alloc (struct device *dev,size_t size, +dma_addr_t *dma_handle,gfp_t gfp, +unsigned long attrs) + +{ +struct dma_map_ops *ops = &dma_noop_ops; + +/* +* We are here because: +* - no consistent DMA region has been defined, so we can't +* continue. +* - there is no space left in consistent DMA region, so we +* only can fallback to generic allocator if we are +* advertised that consistency is not required. +*/ + +if (attrs &DMA_ATTR_NON_CONSISTENT ) +return ops->alloc (dev, size, dma_handle, gfp, attrs); + +WARN_ON_ONCE (1 ); +return NULL ;
arch/arm/mm/dma-mapping-nommu.c line 67
Quoted 10 lines
arch/arm/mm/dma-mapping-nommu.c new file +10
-0+} + +static void arm_nommu_dma_free (struct device *dev,size_t size, +void *cpu_addr,dma_addr_t dma_addr, +unsigned long attrs) +{ +struct dma_map_ops *ops = &dma_noop_ops; + +if (attrs &DMA_ATTR_NON_CONSISTENT ) + ops->free (dev, size, cpu_addr, dma_addr, attrs);
+ else
Unlike in alloc, there's no early return...
arch/arm/mm/dma-mapping-nommu.c line 68
Quoted 1 line
arch/arm/mm/dma-mapping-nommu.c new file +1
-0+WARN_ON_ONCE (1 );
...so this would still fire for a legitimate non-consistent free.
Otherwise, I think we're pretty much done - with the above fixed:
Quoted 184 lines
arch/arm/mm/dma-mapping-nommu.c new file +183
-0+return ; +} + +static int arm_nommu_dma_mmap (struct device *dev,struct vm_area_struct *vma, +void *cpu_addr,dma_addr_t dma_addr,size_t size, +unsigned long attrs) +{ +struct dma_map_ops *ops = &dma_noop_ops; +int ret; + +if (dma_mmap_from_coherent (dev, vma, cpu_addr, size, &ret)) +return ret; + +if (attrs &DMA_ATTR_NON_CONSISTENT ) +return ops->mmap (dev, vma, cpu_addr, dma_addr, size, attrs); + +WARN_ON_ONCE (1 ); +return -ENXIO ; +} + +static void __dma_page_cpu_to_dev (phys_addr_t paddr,size_t size, +enum dma_data_direction dir) +{ +dmac_map_area (__va (paddr), size, dir); + +if (dir ==DMA_FROM_DEVICE ) +outer_inv_range (paddr, paddr + size); +else +outer_clean_range (paddr, paddr + size); +} + +static void __dma_page_dev_to_cpu (phys_addr_t paddr,size_t size, +enum dma_data_direction dir) +{ +if (dir !=DMA_TO_DEVICE ) { +outer_inv_range (paddr, paddr + size); +dmac_unmap_area (__va (paddr), size, dir); + } +} + +static dma_addr_t arm_nommu_dma_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size, +enum dma_data_direction dir, +unsigned long attrs) +{ +dma_addr_t handle =page_to_phys (page) + offset; + +__dma_page_cpu_to_dev (handle, size, dir); + +return handle; +} + +static void arm_nommu_dma_unmap_page (struct device *dev,dma_addr_t handle, +size_t size,enum dma_data_direction dir, +unsigned long attrs) +{ +__dma_page_dev_to_cpu (handle, size, dir); +} + + +static int arm_nommu_dma_map_sg (struct device *dev,struct scatterlist *sgl, +int nents,enum dma_data_direction dir, +unsigned long attrs) +{ +int i; +struct scatterlist *sg; + +for_each_sg (sgl, sg, nents, i) { +sg_dma_address (sg) =sg_phys (sg); +sg_dma_len (sg) = sg->length; +__dma_page_cpu_to_dev (sg_dma_address (sg),sg_dma_len (sg), dir); + } + +return nents; +} + +static void arm_nommu_dma_unmap_sg (struct device *dev,struct scatterlist *sgl, +int nents,enum dma_data_direction dir, +unsigned long attrs) +{ +struct scatterlist *sg; +int i; + +for_each_sg (sgl, sg, nents, i) +__dma_page_dev_to_cpu (sg_dma_address (sg),sg_dma_len (sg), dir); +} + +static void arm_nommu_dma_sync_single_for_device (struct device *dev, +dma_addr_t handle,size_t size,enum dma_data_direction dir) +{ +__dma_page_cpu_to_dev (handle, size, dir); +} + +static void arm_nommu_dma_sync_single_for_cpu (struct device *dev, +dma_addr_t handle,size_t size,enum dma_data_direction dir) +{ +__dma_page_cpu_to_dev (handle, size, dir); +} + +static void arm_nommu_dma_sync_sg_for_device (struct device *dev,struct scatterlist *sgl, +int nents,enum dma_data_direction dir) +{ +struct scatterlist *sg; +int i; + +for_each_sg (sgl, sg, nents, i) +__dma_page_cpu_to_dev (sg_dma_address (sg),sg_dma_len (sg), dir); +} + +static void arm_nommu_dma_sync_sg_for_cpu (struct device *dev,struct scatterlist *sgl, +int nents,enum dma_data_direction dir) +{ +struct scatterlist *sg; +int i; + +for_each_sg (sgl, sg, nents, i) +__dma_page_dev_to_cpu (sg_dma_address (sg),sg_dma_len (sg), dir); +} + +struct dma_map_ops arm_nommu_dma_ops = { + .alloc = arm_nommu_dma_alloc, + .free = arm_nommu_dma_free, + .mmap = arm_nommu_dma_mmap, + .map_page = arm_nommu_dma_map_page, + .unmap_page = arm_nommu_dma_unmap_page, + .map_sg = arm_nommu_dma_map_sg, + .unmap_sg = arm_nommu_dma_unmap_sg, + .sync_single_for_device = arm_nommu_dma_sync_single_for_device, + .sync_single_for_cpu = arm_nommu_dma_sync_single_for_cpu, + .sync_sg_for_device = arm_nommu_dma_sync_sg_for_device, + .sync_sg_for_cpu = arm_nommu_dma_sync_sg_for_cpu, +}; +EXPORT_SYMBOL (arm_nommu_dma_ops); + +static struct dma_map_ops *arm_nommu_get_dma_map_ops (bool coherent) +{ +return coherent ? &dma_noop_ops : &arm_nommu_dma_ops; +} + +void arch_setup_dma_ops (struct device *dev,u64 dma_base,u64 size, +const struct iommu_ops *iommu,bool coherent) +{ +struct dma_map_ops *dma_ops; + +if (IS_ENABLED (CONFIG_CPU_V7M )) { +/* +* Cache support for v7m is optional, so can be treated as +* coherent if no cache has been detected. Note that it is not +* enough to check if MPU is in use or not since in absense of +* MPU system memory map is used. +*/ + dev->archdata.dma_coherent = (cacheid) ? coherent : true; + }else { +/* +* Assume coherent DMA in case MMU/MPU has not been set up. +*/ + dev->archdata.dma_coherent = (get_cr () &CR_M ) ? coherent : true; + } + + dma_ops =arm_nommu_get_dma_map_ops (dev->archdata.dma_coherent); + +set_dma_ops (dev, dma_ops); +} + +void arch_teardown_dma_ops (struct device *dev) +{ +} + +int dma_supported (struct device *dev,u64 mask) +{ +return 1 ; +} + +EXPORT_SYMBOL (dma_supported); + +#define PREALLOC_DMA_DEBUG_ENTRIES 4096 + +static int __init dma_debug_do_init (void ) +{ +dma_debug_init (PREALLOC_DMA_DEBUG_ENTRIES ); +return 0 ; +} +core_initcall (dma_debug_do_init);
RFC PATCH v5 5/7 ARM: NOMMU: Introduce dma operations for noMMU
Quoted 144 lines · Robin Murphy
Quoted 140 lines · Vladimir Murzin
R/M classes of cpus can have memory covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks
dma_alloc_coherent()and friends, since data can stuck in caches now or be buffered.This patch factors out DMA support for NOMMU configuration into separate entity which provides dedicated
dma_ops. We have to handle there several cases:
- configurations with MMU/MPU setup
- configurations without MMU/MPU setup
- special case for M-class, since caches and MPU there are optionalIn general we rely on default DMA area for coherent allocations or/and per-device memory reserves suitable for coherent DMA, so if such regions are set coherent allocations go from there.
In case MPU/MPU was not setup we fallback to normal page allocator for DMA memory allocation.
In case we run M-class cpus, for configuration without cache support (like Cortex-M3/M4) dma operations are forced to be coherent and wired with dma-noop (such decision is made based on cacheid global variable); however, if caches are detected there and no DMA coherent region is given (either default or per-device), dma is disallowed even MPU is not set - it is because M-class implement system memory map which defines part of address space as Normal memory.
Reported-by Alexandre Torgue <[email protected]>Reported-by Andras Szemzo <[email protected]>Signed-off-by Vladimir Murzin <[email protected]>--- arch/arm/include/asm/dma-mapping.h | 3 +- arch/arm/mm/Makefile | 5 +- arch/arm/mm/dma-mapping-nommu.c | 252 +++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 arch/arm/mm/dma-mapping-nommu.carch/arm/include/asm/dma-mapping.h +2
-1@@ -20,7 +20,8 @@ static inline struct dma_map_ops *__generic_dma_ops(struct device *dev) {if (dev && dev->archdata.dma_ops)return dev->archdata.dma_ops; -return &arm_dma_ops; + +return IS_ENABLED (CONFIG_MMU ) ? &arm_dma_ops : &dma_noop_ops; }static inline struct dma_map_ops *get_dma_ops (struct device *dev)arch/arm/mm/Makefile +2
-3@@ -2,9 +2,8 @@ # Makefile for the linux arm-specific parts of the memory manager. # -obj-y := dma-mapping.o extable.o fault.o init.o \ - iomap.o - +obj-y := extable.o fault.o init.o iomap.o +obj-y += dma-mapping$(MMUEXT).o obj-$(CONFIG_MMU) += fault-armv.o flush.o idmap.o ioremap.o \ mmap.o pgd.o mmu.o pageattr.oarch/arm/mm/dma-mapping-nommu.c new file +67
-0@@ -0,0 +1,252 @@ +/* +* Based on linux/arch/arm/mm/dma-mapping.c +* +* Copyright (C) 2000-2004 Russell King +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +*/ + +#include <linux/export.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/scatterlist.h> + +#include <asm/cachetype.h> +#include <asm/cacheflush.h> +#include <asm/outercache.h> +#include <asm/cp15.h> + +#include "dma.h" + +/* +* dma_noop_ops is used if +* - MMU/MPU is off +* - cpu is v7m w/o cache support +* - device is coherent +* otherwise arm_nommu_dma_ops is used. +* +* arm_nommu_dma_ops rely on consistent DMA memory (please, refer to +* [1] on how to declare such memory). +* +* [1] Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt +*/ + +static void *arm_nommu_dma_alloc (struct device *dev,size_t size, +dma_addr_t *dma_handle,gfp_t gfp, +unsigned long attrs) + +{ +struct dma_map_ops *ops = &dma_noop_ops; + +/* +* We are here because: +* - no consistent DMA region has been defined, so we can't +* continue. +* - there is no space left in consistent DMA region, so we +* only can fallback to generic allocator if we are +* advertised that consistency is not required. +*/ + +if (attrs &DMA_ATTR_NON_CONSISTENT ) +return ops->alloc (dev, size, dma_handle, gfp, attrs); + +WARN_ON_ONCE (1 ); +return NULL ; +} + +static void arm_nommu_dma_free (struct device *dev,size_t size, +void *cpu_addr,dma_addr_t dma_addr, +unsigned long attrs) +{ +struct dma_map_ops *ops = &dma_noop_ops; + +if (attrs &DMA_ATTR_NON_CONSISTENT ) + ops->free (dev, size, cpu_addr, dma_addr, attrs);arch/arm/mm/dma-mapping-nommu.c new file +1
-0+else
Quoted 5 lines
Unlike in alloc, there's no early return...
arch/arm/mm/dma-mapping-nommu.c line 68
Quoted 1 line
arch/arm/mm/dma-mapping-nommu.c new file +1
-0+WARN_ON_ONCE (1 );...so this would still fire for a legitimate non-consistent free.
Ahh, right, I'll fix this.
Quoted 4 lines
Otherwise, I think we're pretty much done - with the above fixed:
Reviewed-by Robin Murphy <[email protected]>
Much appreciated!
Cheers
Vladimir
RFC PATCH v5 6/7 ARM: NOMMU: Set ARM_DMA_MEM_BUFFERABLE for M-class cpus
In reply to Vladimir MurzinNow, we have dedicated non-cacheable region for consistent DMA operations. However, that region can still be marked as bufferable by MPU, so it'd be safer to have barriers by default.
--- arch/arm/mm/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/arm/mm/Kconfig +1 -1
@@ -1029,7 +1029,7 @@ config ARM_L1_CACHE_SHIFT config ARM_DMA_MEM_BUFFERABLE bool "Use non-cacheable memory for DMA" if (CPU_V6 || CPU_V6K) && !CPU_V7 - default y if CPU_V6 || CPU_V6K || CPU_V7 + default y if CPU_V6 || CPU_V6K || CPU_V7 || CPU_V7M help Historically, the kernel has used strongly ordered mappings to provide DMA coherent memory. With the advent of ARMv7, mapping
Signature · 1 line
2.0.0
RFC PATCH v5 7/7 ARM: dma-mapping: Remove traces of NOMMU code
In reply to Vladimir MurzinDMA operations for NOMMU case have been just factored out into separate compilation unit, so don't keep dead code.
--- arch/arm/mm/dma-mapping.c | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-)
arch/arm/mm/dma-mapping.c +2 -24
@@ -344,8 +344,6 @@ static void __dma_free_buffer(struct page *page, size_t size) } } -#ifdef CONFIG_MMU - static void *__alloc_from_contiguous(struct device *dev, size_t size, pgprot_t prot, struct page **ret_page, const void *caller, bool want_vaddr,
@@ -646,22 +644,6 @@ static inline pgprot_t __get_dma_pgprot(unsigned long attrs, pgprot_t prot)return prot; } -#define nommu () 0 - -#else /* !CONFIG_MMU */ - -#define nommu () 1 - -#define __get_dma_pgprot (attrs, prot) __pgprot(0) -#define __alloc_remap_buffer (dev, size, gfp, prot, ret, c, wv) NULL -#define __alloc_from_pool (size, ret_page) NULL -#define __alloc_from_contiguous (dev, size, prot, ret, c, wv, coherent_flag) NULL -#define __free_from_pool (cpu_addr, size) do { } while (0) -#define __free_from_contiguous (dev, page, cpu_addr, size, wv) do { } while (0) -#define __dma_free_remap (cpu_addr, size) do { } while (0) - -#endif /* CONFIG_MMU */ -static void *__alloc_simple_buffer (struct device *dev,size_t size,gfp_t gfp,struct page **ret_page) {
@@ -803,7 +785,7 @@ static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,if (cma) buf->allocator = &cma_allocator; -else if (nommu () || is_coherent) +else if (is_coherent) buf->allocator = &simple_allocator;else if (allowblock) buf->allocator = &remap_allocator;
@@ -852,8 +834,7 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma, void *cpu_addr, dma_addr_t dma_addr, size_t size, unsigned long attrs) { - int ret = -ENXIO; -#ifdef CONFIG_MMU + int ret;unsigned long nr_vma_pages = (vma->vm_end - vma->vm_start) >>PAGE_SHIFT ;unsigned long nr_pages =PAGE_ALIGN (size) >>PAGE_SHIFT ;unsigned long pfn =dma_to_pfn (dev, dma_addr);
@@ -868,7 +849,6 @@ static int __arm_dma_mmap(struct device *dev, struct vm_area_struct *vma, vma->vm_end - vma->vm_start, vma->vm_page_prot); } -#endif /* CONFIG_MMU */ return ret; }
@@ -887,9 +867,7 @@ int arm_dma_mmap(struct device *dev, struct vm_area_struct *vma, void *cpu_addr, dma_addr_t dma_addr, size_t size, unsigned long attrs) { -#ifdef CONFIG_MMU vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot); -#endif /* CONFIG_MMU */ return __arm_dma_mmap (dev, vma, cpu_addr, dma_addr, size, attrs); }
Signature · 1 line
2.0.0
v5 cover ARM: Fix dma_alloc_coherent() and friends for NOMMU
In reply to Vladimir MurzinHi,
Quoted 11 lines · Vladimir Murzin
Hi,
It seem that addition of cache support for M-class CPUs uncovered latent bug in DMA usage. NOMMU memory model has been treated as being always consistent; however, for R/M CPU classes memory can be covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks
dma_alloc_coherent()and friends, since data can stuck in caches now or be buffered.
Quoted 10 lines
This patch set is trying to address the issue by providing region of memory suitable for consistent DMA operations. It is supposed that such region is marked by MPU as non-cacheable. Robin suggested to advertise such memory as reserved shared-dma-pool, rather then using homebrew command line option, and extend dma-coherent to provide default DMA area in the similar way as it is done for CMA (PATCH 4/7). It allows us to offload all bookkeeping on generic coherent DMA framework, and it seems that it might be reused by other architectures like c6x and blackfin.
I have tested the v5 on my ATMEL armv7m board. Caches enabled, DMA on MMC, USART and Ethernet.
Everything works as expected, so you can add my tested-by.
Booting Linux on physical CPU 0x0
Linux version 4.9.0 (root at debian) (gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29) ) #1 Wed Jan 18 22:10:39 CET 2017
CPU: ARMv7-M [410fc271] revision 1 (ARMv7M), cr=00000000
CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
OF: fdt:Machine model: SAME70-sampione board
Reserved memory: created DMA memory pool at 0x73e00000, size 2 MiB
OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool
...
DMA: default coherent area is set
?
Thanks for the patches!
Regards,
Andras
v5 cover ARM: Fix dma_alloc_coherent() and friends for NOMMU
2017-01-19 16:23 GMT+01:00 Szemz? Andr?s <[email protected]>:
Quoted 38 lines
Hi,
Quoted 11 lines · Vladimir Murzin
Hi,
It seem that addition of cache support for M-class CPUs uncovered latent bug in DMA usage. NOMMU memory model has been treated as being always consistent; however, for R/M CPU classes memory can be covered by MPU which in turn might configure RAM as Normal i.e. bufferable and cacheable. It breaks
dma_alloc_coherent()and friends, since data can stuck in caches now or be buffered.Quoted 10 lines
This patch set is trying to address the issue by providing region of memory suitable for consistent DMA operations. It is supposed that such region is marked by MPU as non-cacheable. Robin suggested to advertise such memory as reserved shared-dma-pool, rather then using homebrew command line option, and extend dma-coherent to provide default DMA area in the similar way as it is done for CMA (PATCH 4/7). It allows us to offload all bookkeeping on generic coherent DMA framework, and it seems that it might be reused by other architectures like c6x and blackfin.
I have tested the v5 on my ATMEL armv7m board. Caches enabled, DMA on MMC, USART and Ethernet.
Everything works as expected, so you can add my tested-by.Booting Linux on physical CPU
0x0
Linux version 4.9.0 (root at debian) (gcc version 4.8.3 20140320 (prerelease) (Sourcery CodeBench Lite 2014.05-29) ) #1 Wed Jan 18 22:10:39 CET 2017
CPU: ARMv7-M [410fc271] revision 1 (ARMv7M), cr=00000000
CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
OF: fdt:Machine model: SAME70-sampione board
Reserved memory: created DMA memory pool at0x73e00000, size 2 MiB
OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool
...
DMA: default coherent area is set
?
Quoted 6 lines
Thanks for the patches!
Regards,
Andras
I have tested it on my stm32f4 CortexM4 (no cache, no mmu) with display driver (modetest and fbdemo)
dma-noop part is working fine on my side.
You can add my tested-by,
Thanks,
Benjamin
Signature · 7 lines
Benjamin Gaignard Graphic Study Group Linaro.org ? Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog