Add support for page alloc w/ custom cache attributes
cover Add support for page alloc w/ custom cache attributes
This series is an attempt to address the recent discussion regarding dual-mapping of kernel pages with different cache attributes that was spawned by the VCM patch series (and other IOMMU / SMMU patches), to allow memory allocations for the SMMU to use any kernel-managed page.
This adds 2 new APIs (enabled with the ARM_ATTRIB_ALLOCATOR config) to allocate and free pages with caller-specified cache attributes. When this config is enabled, the kernel's lowmem mapping is mapped using PTEs rather than sections, so that the cache attributes for the kernel's mapping can be updated when the page is allocated to match the caller's request (and returned to pgprot_kernel when freed).
Additionally, this patch stack adds an additional memory type (inner- writeback, outer-non-cacheable) that we (NVIDIA) have found to be extremely beneficial in optimizing the drawing that Android's window system (among others) uses.
I'm sending this out for comments on the overall direction. The APIs are similar to functions that we implemented in the Tegra system MMU driver.
- Gary
PATCH 1/4 mmu: add option to map lowmem with page mappings
add a kernel configuration to map the kernel's lowmem pages using PTE mappings, rather than the default behavior of 1MiB section mappings. on ARMv7 processors, to support allocating pages with DMA-coherent cache attributes, the cache attributes specified in the kernel's mapping must match cache attributes specified for other mappings; to ensure that this is the case, the kernel's attributes must be specified on a per-page basis.
to avoid problems caused by the init_mm page table allocations exceeding the available initial memory, when this config is enabled lowmem is initially mapped using sections (matches current behavior), then remapped using pages after bootmem is initialized
--- arch/arm/mm/Kconfig | 9 +++++++ arch/arm/mm/mmu.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 0 deletions(-)
arch/arm/mm/Kconfig +9 -0
@@ -837,3 +837,12 @@ config ARCH_HAS_BARRIERS help This option allows the use of custom mandatory barriers included via the mach/barriers.h file. + +config ARCH_LOWMEM_IN_PTES + bool + help + This option will map the kernel direct-mapped lowmem region + using page table mappings rather than section mappings. + +config ARCH_USES_PG_UNCACHED + bool
arch/arm/mm/mmu.c +63 -0
94 lines not on this page · raw message
Signature · 1 line
1.7.0.4
PATCH 2/4 mm: add page allocator for customizing cache attributes
In reply to Gary KingARM CPUs with speculative prefetching have undefined behaviors when the same physical page is mapped to two different virtual addresses with conflicting cache attributes.
since many recent systems include IOMMU functionality (i.e., remapping of discontiguous physical pages into a virtually-contiguous address range for I/O devices), it is desirable to support allocating any available OS memory for use by the I/O devices. however, since many systems do not support cache coherency between the CPU and DMA devices, these devices are left with using DMA-coherent allocations from the OS (which severely limits the benefit of an IOMMU) or performing cache maintenance (which can be a severe performance loss, particularly on systems with outer caches, compared to using DMA-coherent memory).
this change adds an API for allocating pages from the OS with specific cache maintenance properties and ensures that the kernel's mapping of the page reflects the desired cache attributes, in line with the ARMv7 architectural requirements
since the kmap page properties are now page-dependent, to ensure that highmem pages are always mapped with the desired attributes, kmap_prot is implemented as a static inline function which expects that "page" is declared as a struct page * variable in any function which uses kmap_prot, so that the correct properties will be returned.
--- arch/arm/include/asm/attrib_alloc.h | 57 ++++++++++++++++ arch/arm/include/asm/highmem.h | 12 ++++ arch/arm/mm/Kconfig | 24 +++++++ arch/arm/mm/Makefile | 2 + arch/arm/mm/attrib_alloc.c | 122 +++++++++++++++++++++++++++++++++++ arch/arm/mm/dma-mapping.c | 16 ++++- arch/arm/mm/flush.c | 9 +++ arch/arm/mm/highmem.c | 3 +- 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 arch/arm/include/asm/attrib_alloc.h create mode 100644 arch/arm/mm/attrib_alloc.c
arch/arm/include/asm/attrib_alloc.h new file +57 -0
@@ -0,0 +1,57 @@ +/* +* arch/arm/include/asm/attrib_alloc.h +* +* Page allocator with custom cache attributes +* +* Copyright (c) 2010, NVIDIA Corporation. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __ARCH_ARM_ATTRIB_ALLOC_H +#define __ARCH_ARM_ATTRIB_ALLOC_H + +#include <linux/types.h> +#include <asm/page.h> + +#ifdef CONFIG_ARM_ATTRIB_ALLOCATOR +struct page *arm_attrib_alloc_pages_node (int nid,gfp_t gfp, +unsigned int order,pgprot_t prot); + +void arm_attrib_free_pages (struct page *page,unsigned int order); +#else +static inline struct page *arm_attrib_alloc_pages_node (int ,gfp_t , +unsigned int ,pgprot_t ) +{ +return NULL ; +} + +static inline arm_attrib_free_pages(struct page *, unsigned int) +{ +} +#endif + +static inline struct page *arm_attrib_alloc_pages (gfp_t gfp, +unsigned int order,pgprot_t prot) +{ +return arm_attrib_alloc_pages_node (-1 , gfp, order, prot); +} + +#define arm_attrib_alloc_page (gfp, prot) \ + arm_attrib_alloc_pages((gfp), 0, (prot)) + +#define arm_attrib_free_page (page) arm_attrib_free_pages((page), 0) + +#endif /* __ARCH_ARM_ATTRIB_ALLOC_H */
arch/arm/include/asm/highmem.h +12 -0
@@ -9,7 +9,19 @@#define PKMAP_NR (virt) (((virt) - PKMAP_BASE) >> PAGE_SHIFT)#define PKMAP_ADDR (nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT)) +#ifndef CONFIG_ARM_ATTRIB_ALLOCATOR #define kmap_prot PAGE_KERNEL +#else +static inline pgprot_t kmap_prot_of (struct page *page) +{ +if (PageUncached (page)) { +return __pgprot_modify (PAGE_KERNEL ,L_PTE_MT_MASK , + page->private); + }else +return PAGE_KERNEL ; +} +#define kmap_prot kmap_prot_of(page) +#endif #define flush_cache_kmaps () \ do { \
arch/arm/mm/Kconfig +24 -0
@@ -811,6 +811,30 @@ config ARM_L1_CACHE_SHIFT default 6 if ARM_L1_CACHE_SHIFT_6 default 5 +config ARM_ATTRIB_ALLOCATOR + bool "Support custom cache attribute allocations in low memory" + select ARCH_LOWMEM_IN_PTES + select ARCH_USES_PG_UNCACHED + depends on MMU && !CPU_CACHE_VIVT + help + Historically, the kernel has only reserved a small region + of physical memory for uncached access, and relied on + explicit cache maintenance for ensuring coherency between + the CPU and DMA. + + However, many recent systems support mapping discontiguous + physical pages into contiguous DMA addresses (so-called + system MMUs). For some DMA clients (notably graphics and + multimedia engines), performing explict cache maintenance + between CPU and DMA mappings can be prohibitively expensive, + and since ARMv7, mapping the same physical page with different + cache attributes is disallowed and has unpredictable behavior. + + Say 'Y' here to include page allocation support with explicit + cache attributes; on ARMv7 systems this will also force the + kernel's page tables to be mapped using page tables rather + than sections. + config ARM_DMA_MEM_BUFFERABLE bool "Use non-cacheable memory for DMA" if CPU_V6 && !CPU_V7 depends on !(MACH_REALVIEW_PB1176 || REALVIEW_EB_ARM11MP || \
arch/arm/mm/Makefile +2 -0
@@ -12,6 +12,8 @@ ifneq ($(CONFIG_MMU),y) obj-y += nommu.o endif +obj-$(CONFIG_ARM_ATTRIB_ALLOCATOR) += attrib_alloc.o + obj-$(CONFIG_MODULES) += proc-syms.o obj-$(CONFIG_ALIGNMENT_TRAP) += alignment.o
arch/arm/mm/attrib_alloc.c new file +122 -0
122 lines not on this page · raw message
arch/arm/mm/dma-mapping.c +15 -1
@@ -485,6 +485,12 @@ void ___dma_page_cpu_to_dev(struct page *page, unsigned long off, {unsigned long paddr; +if (PageUncached (page) && + !(page_private (page) ==L_PTE_MT_WRITEBACK || +page_private (page) ==L_PTE_MT_WRITEALLOC || +page_private (page) ==L_PTE_MT_DEV_CACHED )) +return ; +dma_cache_maint_page (page, off, size, dir, dmac_map_area); paddr =page_to_phys (page) + off;
@@ -500,7 +506,15 @@ EXPORT_SYMBOL(___dma_page_cpu_to_dev);void ___dma_page_dev_to_cpu (struct page *page,unsigned long off,size_t size,enum dma_data_direction dir) { -unsigned long paddr =page_to_phys (page) + off; +unsigned long paddr; + +if (PageUncached (page) && + !(page_private (page) ==L_PTE_MT_WRITEBACK || +page_private (page) ==L_PTE_MT_WRITEALLOC || +page_private (page) ==L_PTE_MT_DEV_CACHED )) +return ; + + paddr =page_to_phys (page) + off;/* FIXME: non-speculating: not required */ /* don't bother invalidating if DMA to device */
arch/arm/mm/flush.c +9 -0
@@ -153,6 +153,15 @@ void copy_to_user_page(struct vm_area_struct *vma, struct page *page,void __flush_dcache_page (struct address_space *mapping,struct page *page) { +/* PageUncached is used to indicate that cache attribute flags +* are stored with the page; cache maintenance may still be +* required if the page is mapped inner-cacheable, outer +* non-cacheable */ +if (PageUncached (page) && + !(page_private (page) ==L_PTE_MT_WRITEBACK || +page_private (page) ==L_PTE_MT_WRITEALLOC || +page_private (page) ==L_PTE_MT_DEV_CACHED )) +return ;/* * Writeback any data associated with the kernel mapping of this * page. This ensures that data in the physical page is mutually
arch/arm/mm/highmem.c +2 -1
@@ -109,6 +109,7 @@ void *kmap_atomic_pfn(unsigned long pfn, enum km_type type) {unsigned int idx;unsigned long vaddr; +struct page *page =pfn_to_page (pfn);pagefault_disable ();
@@ -117,7 +118,7 @@ void *kmap_atomic_pfn(unsigned long pfn, enum km_type type)#ifdef CONFIG_DEBUG_HIGHMEM BUG_ON (!pte_none (*(TOP_PTE (vaddr))));#endif -set_pte_ext (TOP_PTE (vaddr),pfn_pte (pfn, kmap_prot),0 ); +set_pte_ext (TOP_PTE (vaddr),mk_pte (page, kmap_prot),0 );local_flush_tlb_kernel_page (vaddr);return (void *)vaddr;
Signature · 1 line
1.7.0.4
PATCH 3/4 mm: add memory type for inner-writeback
In reply to Gary KingFor streaming-style operations (e.g., software rendering of graphics surfaces shared with non-coherent DMA devices), the cost of performing L2 cache maintenance can exceed the benefit of having the larger cache (this is particularly true for OUTER_CACHE configurations like the ARM PL2x0).
This change uses the currently-unused mapping 5 (TEX[0]=1, C=0, B=1) in the tex remapping tables as an inner-writeback-write-allocate, outer non-cacheable memory type, so that this mapping will be available to clients which will benefit from the reduced L2 maintenance.
--- arch/arm/include/asm/pgtable.h | 3 +++ arch/arm/mm/proc-macros.S | 2 +- arch/arm/mm/proc-v7.S | 4 ++-- arch/arm/mm/proc-xsc3.S | 2 +- arch/arm/mm/proc-xscale.S | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-)
arch/arm/include/asm/pgtable.h +3 -0
@@ -184,6 +184,7 @@ extern void __pgd_error(const char *file, int line, unsigned long val);#define L_PTE_MT_DEV_NONSHARED (0x0c << 2)/* 1100 */ #define L_PTE_MT_DEV_WC (0x09 << 2)/* 1001 */ #define L_PTE_MT_DEV_CACHED (0x0b << 2)/* 1011 */ +#define L_PTE_MT_INNER_WB (0x05 << 2)/* 0101 (armv6, armv7) */ #define L_PTE_MT_MASK (0x0f << 2)#ifndef __ASSEMBLY__
@@ -321,6 +322,8 @@ static inline pte_t pte_mkspecial(pte_t pte) { return pte; }#define pgprot_dmacoherent (prot) \ __pgprot_modify(prot, L_PTE_MT_MASK|L_PTE_EXEC, L_PTE_MT_UNCACHED)#endif +#define pgprot_inner_writeback (prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_INNER_WB)#define pmd_none (pmd) (!pmd_val(pmd))#define pmd_present (pmd) (pmd_val(pmd))
arch/arm/mm/proc-macros.S +1 -1
@@ -107,7 +107,7 @@ .long PTE_CACHEABLE @ L_PTE_MT_WRITETHROUGH .long PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEBACK .long PTE_BUFFERABLE @ L_PTE_MT_DEV_SHARED - .long 0x00 @ unused + .long PTE_EXT_TEX(4) | PTE_BUFFERABLE @ L_PTE_MT_INNER_WB .long 0x00 @ L_PTE_MT_MINICACHE (not present) .long PTE_EXT_TEX(1) | PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEALLOC .long 0x00 @ unused
arch/arm/mm/proc-v7.S +2 -2
@@ -270,8 +270,8 @@ __v7_setup: * NS1 = PRRR[19] = 1 - normal shareable property * NOS = PRRR[24+n] = 1 - not outer shareable */ - ldr r5, =0xff0a81a8 @ PRRR - ldr r6, =0x40e040e0 @ NMRR + ldr r5, =0xff0a89a8 + ldr r6, =0x40e044e0 mcr p15, 0, r5, c10, c2, 0 @ write PRRR mcr p15, 0, r6, c10, c2, 1 @ write NMRR #endif
arch/arm/mm/proc-xsc3.S +1 -1
@@ -377,7 +377,7 @@ cpu_xsc3_mt_table: .long PTE_EXT_TEX(5) | PTE_CACHEABLE @ L_PTE_MT_WRITETHROUGH .long PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEBACK .long PTE_EXT_TEX(1) | PTE_BUFFERABLE @ L_PTE_MT_DEV_SHARED - .long 0x00 @ unused + .long PTE_EXT_TEX(4) | PTE_BUFFERABLE @ L_PTE_MT_INNER_WB (not present?) .long 0x00 @ L_PTE_MT_MINICACHE (not present) .long PTE_EXT_TEX(5) | PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEALLOC (not present?) .long 0x00 @ unused
arch/arm/mm/proc-xscale.S +1 -1
@@ -473,7 +473,7 @@ cpu_xscale_mt_table: .long PTE_CACHEABLE @ L_PTE_MT_WRITETHROUGH .long PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEBACK .long PTE_EXT_TEX(1) | PTE_BUFFERABLE @ L_PTE_MT_DEV_SHARED - .long 0x00 @ unused + .long PTE_EXT_TEX(1) | PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_INNER_WB .long PTE_EXT_TEX(1) | PTE_CACHEABLE @ L_PTE_MT_MINICACHE .long PTE_EXT_TEX(1) | PTE_CACHEABLE | PTE_BUFFERABLE @ L_PTE_MT_WRITEALLOC .long 0x00 @ unused
Signature · 1 line
1.7.0.4
PATCH 3/4 mm: add memory type for inner-writeback
Quoted 4 lines · Gary King
This change uses the currently-unused mapping 5 (TEX[0]=1, C=0, B=1) in the tex remapping tables as an inner-writeback-write-allocate, outer non-cacheable memory type, so that this mapping will be available to clients which will benefit from the reduced L2 maintenance.
No. This is not "free for use". Mapping 5 is unused because it's not architecturally defined - CPU implementations may not implement it. This is what the ARM ARM says:
For seven of the eight possible combinations of the TEX[0], C and B bits, a field in the PRRR defines the corresponding memory region as being Normal, Device or Strongly-ordered memory a field in the NMRR defines the Inner cache attributes that apply if the PRRR field identifies the region as Normal memory a second field in the NMRR defines the Outer cache attributes that apply if the PRRR field identifies the region as Normal memory.
The meaning of the eighth combination for the TEX[0], C and B bits is IMPLEMENTATION DEFINED
So we can't be sure that the PRRR and NMRR bits which correspond with mapping 5 even exist.
PATCH 3/4 mm: add memory type for inner-writeback
Russell,
Quoted 3 lines
No. This is not "free for use". Mapping 5 is unused because it's not architecturally defined - CPU implementations may not implement it. This is what the ARM ARM says:
According to the ARMv7-A ARM, n = 6 is implementation defined; n = 5 is available:
"The meaning of the field with n = 6 is IMPLEMENTATION DEFINED and might
differ from the meaning given here. This is because the meaning of the attribute combination {TEX[0] = 1, C = 1, B = 0} is IMPLEMENTATION DEFINED."
- Gary
Quoted 26 lines · Russell King - ARM Linux
Quoted 4 lines · Gary King
This change uses the currently-unused mapping 5 (TEX[0]=1, C=0, B=1) in the tex remapping tables as an inner-writeback-write-allocate, outer non-cacheable memory type, so that this mapping will be available to clients which will benefit from the reduced L2 maintenance.
No. This is not "free for use". Mapping 5 is unused because it's not architecturally defined - CPU implementations may not implement it. This is what the ARM ARM says:
For seven of the eight possible combinations of the TEX[0], C and B
bits,
a field in the PRRR defines the corresponding memory region as being Normal, Device or Strongly-ordered memory a field in the NMRR defines the Inner cache attributes that apply if the PRRR field identifies the region as Normal memory a second field in the NMRR defines the Outer cache attributes that apply if the PRRR field identifies the region as Normal memory.The meaning of the eighth combination for the TEX[0], C and B bits is IMPLEMENTATION DEFINED
So we can't be sure that the PRRR and NMRR bits which correspond with mapping 5 even exist.
PATCH 4/4 dma-mapping: add support for inner-writeback pages
In reply to Gary Kingadd support for pages allocated with the inner-writeback allocation attribute to the cache flush and DMA cache maintenance code.
pages allocated from the attribute page allocator with the inner- writeback attribute selected can bypass outer cache maintenance, since outer cachelines will not be allocated for these pages. this results in a significant reduction in the time spent performing cache maintenance for these pages.
--- arch/arm/mm/dma-mapping.c | 23 +++++++++++++++-------- arch/arm/mm/flush.c | 3 ++- 2 files changed, 17 insertions(+), 9 deletions(-)
arch/arm/mm/dma-mapping.c +15 -8
@@ -488,16 +488,20 @@ void ___dma_page_cpu_to_dev(struct page *page, unsigned long off,if (PageUncached (page) && !(page_private (page) ==L_PTE_MT_WRITEBACK ||page_private (page) ==L_PTE_MT_WRITEALLOC || -page_private (page) ==L_PTE_MT_DEV_CACHED )) +page_private (page) ==L_PTE_MT_DEV_CACHED || +page_private (page) ==L_PTE_MT_INNER_WB ))return ;dma_cache_maint_page (page, off, size, dir, dmac_map_area); paddr =page_to_phys (page) + off; -if (dir ==DMA_FROM_DEVICE ) { -outer_inv_range (paddr, paddr + size); - }else { -outer_clean_range (paddr, paddr + size); + +if (!PageUncached (page) || (page_private (page) !=L_PTE_MT_INNER_WB )) { +if (dir ==DMA_FROM_DEVICE ) { +outer_inv_range (paddr, paddr + size); + }else { +outer_clean_range (paddr, paddr + size); + } }/* FIXME: non-speculating: flush on bidirectional mappings? */ }
@@ -511,15 +515,18 @@ void ___dma_page_dev_to_cpu(struct page *page, unsigned long off,if (PageUncached (page) && !(page_private (page) ==L_PTE_MT_WRITEBACK ||page_private (page) ==L_PTE_MT_WRITEALLOC || -page_private (page) ==L_PTE_MT_DEV_CACHED )) +page_private (page) ==L_PTE_MT_DEV_CACHED || +page_private (page) ==L_PTE_MT_INNER_WB ))return ; paddr =page_to_phys (page) + off;/* FIXME: non-speculating: not required */ /* don't bother invalidating if DMA to device */ -if (dir !=DMA_TO_DEVICE ) -outer_inv_range (paddr, paddr + size); +if (!PageUncached (page) || (page_private (page) !=L_PTE_MT_INNER_WB )) { +if (dir !=DMA_TO_DEVICE ) +outer_inv_range (paddr, paddr + size); + }dma_cache_maint_page (page, off, size, dir, dmac_unmap_area); }
arch/arm/mm/flush.c +2 -1
@@ -160,7 +160,8 @@ void __flush_dcache_page(struct address_space *mapping, struct page *page)if (PageUncached (page) && !(page_private (page) ==L_PTE_MT_WRITEBACK ||page_private (page) ==L_PTE_MT_WRITEALLOC || -page_private (page) ==L_PTE_MT_DEV_CACHED )) +page_private (page) ==L_PTE_MT_DEV_CACHED || +page_private (page) ==L_PTE_MT_INNER_WB ))return ;/* * Writeback any data associated with the kernel mapping of this
Signature · 1 line
1.7.0.4