[ARM] kmap support
authorNicolas Pitre <nico@cam.org>
Mon, 15 Sep 2008 20:44:55 +0000 (16:44 -0400)
committerNicolas Pitre <nico@cam.org>
Mon, 16 Mar 2009 01:01:20 +0000 (21:01 -0400)
The kmap virtual area borrows a 2MB range at the top of the 16MB area
below PAGE_OFFSET currently reserved for kernel modules and/or the
XIP kernel.  This 2MB corresponds to the range covered by 2 consecutive
second-level page tables, or a single pmd entry as seen by the Linux
page table abstraction.  Because XIP kernels are unlikely to be seen
on systems needing highmem support, there shouldn't be any shortage of
VM space for modules (14 MB for modules is still way more than twice the
typical usage).

Because the virtual mapping of highmem pages can go away at any moment
after kunmap() is called on them, we need to bypass the delayed cache
flushing provided by flush_dcache_page() in that case.

The atomic kmap versions are based on fixmaps, and
__cpuc_flush_dcache_page() is used directly in that case.

Signed-off-by: Nicolas Pitre <nico@marvell.com>
arch/arm/include/asm/highmem.h [new file with mode: 0644]
arch/arm/include/asm/memory.h
arch/arm/mm/Makefile
arch/arm/mm/flush.c
arch/arm/mm/highmem.c [new file with mode: 0644]
arch/arm/mm/mmu.c

diff --git a/arch/arm/include/asm/highmem.h b/arch/arm/include/asm/highmem.h
new file mode 100644 (file)
index 0000000..023d5b3
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef _ASM_HIGHMEM_H
+#define _ASM_HIGHMEM_H
+
+#include <asm/kmap_types.h>
+
+#define PKMAP_BASE             (PAGE_OFFSET - PMD_SIZE)
+#define LAST_PKMAP             PTRS_PER_PTE
+#define LAST_PKMAP_MASK                (LAST_PKMAP - 1)
+#define PKMAP_NR(virt)         (((virt) - PKMAP_BASE) >> PAGE_SHIFT)
+#define PKMAP_ADDR(nr)         (PKMAP_BASE + ((nr) << PAGE_SHIFT))
+
+#define kmap_prot              PAGE_KERNEL
+
+#define flush_cache_kmaps()    flush_cache_all()
+
+extern pte_t *pkmap_page_table;
+
+extern void *kmap_high(struct page *page);
+extern void kunmap_high(struct page *page);
+
+extern void *kmap(struct page *page);
+extern void kunmap(struct page *page);
+extern void *kmap_atomic(struct page *page, enum km_type type);
+extern void kunmap_atomic(void *kvaddr, enum km_type type);
+extern void *kmap_atomic_pfn(unsigned long pfn, enum km_type type);
+extern struct page *kmap_atomic_to_page(const void *ptr);
+
+#endif
index 0202a7c..ae472bc 100644 (file)
  * The module space lives between the addresses given by TASK_SIZE
  * and PAGE_OFFSET - it must be within 32MB of the kernel text.
  */
-#define MODULES_END            (PAGE_OFFSET)
-#define MODULES_VADDR          (MODULES_END - 16*1048576)
-
+#define MODULES_VADDR          (PAGE_OFFSET - 16*1024*1024)
 #if TASK_SIZE > MODULES_VADDR
 #error Top of user space clashes with start of module space
 #endif
 
 /*
+ * The highmem pkmap virtual space shares the end of the module area.
+ */
+#ifdef CONFIG_HIGHMEM
+#define MODULES_END            (PAGE_OFFSET - PMD_SIZE)
+#else
+#define MODULES_END            (PAGE_OFFSET)
+#endif
+
+/*
  * The XIP kernel gets mapped at the bottom of the module vm area.
  * Since we use sections to map it, this macro replaces the physical address
  * with its virtual address while keeping offset from the base section.
index 480f78a..185e7dc 100644 (file)
@@ -16,6 +16,7 @@ obj-$(CONFIG_MODULES)         += proc-syms.o
 
 obj-$(CONFIG_ALIGNMENT_TRAP)   += alignment.o
 obj-$(CONFIG_DISCONTIGMEM)     += discontig.o
+obj-$(CONFIG_HIGHMEM)          += highmem.o
 
 obj-$(CONFIG_CPU_ABRT_NOMMU)   += abort-nommu.o
 obj-$(CONFIG_CPU_ABRT_EV4)     += abort-ev4.o
index 0fa9bf3..4e28348 100644 (file)
@@ -192,7 +192,7 @@ void flush_dcache_page(struct page *page)
        struct address_space *mapping = page_mapping(page);
 
 #ifndef CONFIG_SMP
-       if (mapping && !mapping_mapped(mapping))
+       if (!PageHighMem(page) && mapping && !mapping_mapped(mapping))
                set_bit(PG_dcache_dirty, &page->flags);
        else
 #endif
diff --git a/arch/arm/mm/highmem.c b/arch/arm/mm/highmem.c
new file mode 100644 (file)
index 0000000..a34954d
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * arch/arm/mm/highmem.c -- ARM highmem support
+ *
+ * Author:     Nicolas Pitre
+ * Created:    september 8, 2008
+ * Copyright:  Marvell Semiconductors Inc.
+ *
+ * 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/module.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <asm/fixmap.h>
+#include <asm/cacheflush.h>
+#include <asm/tlbflush.h>
+#include "mm.h"
+
+void *kmap(struct page *page)
+{
+       might_sleep();
+       if (!PageHighMem(page))
+               return page_address(page);
+       return kmap_high(page);
+}
+EXPORT_SYMBOL(kmap);
+
+void kunmap(struct page *page)
+{
+       BUG_ON(in_interrupt());
+       if (!PageHighMem(page))
+               return;
+       kunmap_high(page);
+}
+EXPORT_SYMBOL(kunmap);
+
+void *kmap_atomic(struct page *page, enum km_type type)
+{
+       unsigned int idx;
+       unsigned long vaddr;
+
+       pagefault_disable();
+       if (!PageHighMem(page))
+               return page_address(page);
+
+       idx = type + KM_TYPE_NR * smp_processor_id();
+       vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
+#ifdef CONFIG_DEBUG_HIGHMEM
+       /*
+        * With debugging enabled, kunmap_atomic forces that entry to 0.
+        * Make sure it was indeed properly unmapped.
+        */
+       BUG_ON(!pte_none(*(TOP_PTE(vaddr))));
+#endif
+       set_pte_ext(TOP_PTE(vaddr), mk_pte(page, kmap_prot), 0);
+       /*
+        * When debugging is off, kunmap_atomic leaves the previous mapping
+        * in place, so this TLB flush ensures the TLB is updated with the
+        * new mapping.
+        */
+       local_flush_tlb_kernel_page(vaddr);
+
+       return (void *)vaddr;
+}
+EXPORT_SYMBOL(kmap_atomic);
+
+void kunmap_atomic(void *kvaddr, enum km_type type)
+{
+       unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK;
+       unsigned int idx = type + KM_TYPE_NR * smp_processor_id();
+
+       if (kvaddr >= (void *)FIXADDR_START) {
+               __cpuc_flush_dcache_page((void *)vaddr);
+#ifdef CONFIG_DEBUG_HIGHMEM
+               BUG_ON(vaddr != __fix_to_virt(FIX_KMAP_BEGIN + idx));
+               set_pte_ext(TOP_PTE(vaddr), __pte(0), 0);
+               local_flush_tlb_kernel_page(vaddr);
+#else
+               (void) idx;  /* to kill a warning */
+#endif
+       }
+       pagefault_enable();
+}
+EXPORT_SYMBOL(kunmap_atomic);
+
+void *kmap_atomic_pfn(unsigned long pfn, enum km_type type)
+{
+       unsigned int idx;
+       unsigned long vaddr;
+
+       pagefault_disable();
+
+       idx = type + KM_TYPE_NR * smp_processor_id();
+       vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
+#ifdef CONFIG_DEBUG_HIGHMEM
+       BUG_ON(!pte_none(*(TOP_PTE(vaddr))));
+#endif
+       set_pte_ext(TOP_PTE(vaddr), pfn_pte(pfn, kmap_prot), 0);
+       local_flush_tlb_kernel_page(vaddr);
+
+       return (void *)vaddr;
+}
+
+struct page *kmap_atomic_to_page(const void *ptr)
+{
+       unsigned long vaddr = (unsigned long)ptr;
+       pte_t *pte;
+
+       if (vaddr < FIXADDR_START)
+               return virt_to_page(ptr);
+
+       pte = TOP_PTE(vaddr);
+       return pte_page(*pte);
+}
index d4d082c..4810a4c 100644 (file)
@@ -21,6 +21,7 @@
 #include <asm/setup.h>
 #include <asm/sizes.h>
 #include <asm/tlb.h>
+#include <asm/highmem.h>
 
 #include <asm/mach/arch.h>
 #include <asm/mach/map.h>
@@ -895,6 +896,17 @@ static void __init devicemaps_init(struct machine_desc *mdesc)
        flush_cache_all();
 }
 
+static void __init kmap_init(void)
+{
+#ifdef CONFIG_HIGHMEM
+       pmd_t *pmd = pmd_off_k(PKMAP_BASE);
+       pte_t *pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));
+       BUG_ON(!pmd_none(*pmd) || !pte);
+       __pmd_populate(pmd, __pa(pte) | _PAGE_KERNEL_TABLE);
+       pkmap_page_table = pte + PTRS_PER_PTE;
+#endif
+}
+
 /*
  * paging_init() sets up the page tables, initialises the zone memory
  * maps, and sets up the zero page, bad page and bad page tables.
@@ -908,6 +920,7 @@ void __init paging_init(struct machine_desc *mdesc)
        prepare_page_table();
        bootmem_init();
        devicemaps_init(mdesc);
+       kmap_init();
 
        top_pmd = pmd_off_k(0xffff0000);