diff options
author | James Hogan <james.hogan@imgtec.com> | 2012-10-09 05:54:17 -0400 |
---|---|---|
committer | James Hogan <james.hogan@imgtec.com> | 2013-03-02 15:09:20 -0500 |
commit | bbc17704d5d3a8e4325dae74c2a2e77218c26057 (patch) | |
tree | fe13881c1f85fa781e3ac413216954dd946fafae | |
parent | e624e95bd88f94fc70bbe612789bcac44c6f0923 (diff) |
metag: Highmem support
Signed-off-by: James Hogan <james.hogan@imgtec.com>
-rw-r--r-- | arch/metag/include/asm/fixmap.h | 99 | ||||
-rw-r--r-- | arch/metag/include/asm/highmem.h | 62 | ||||
-rw-r--r-- | arch/metag/mm/highmem.c | 133 |
3 files changed, 294 insertions, 0 deletions
diff --git a/arch/metag/include/asm/fixmap.h b/arch/metag/include/asm/fixmap.h new file mode 100644 index 000000000000..33312751c92b --- /dev/null +++ b/arch/metag/include/asm/fixmap.h | |||
@@ -0,0 +1,99 @@ | |||
1 | /* | ||
2 | * fixmap.h: compile-time virtual memory allocation | ||
3 | * | ||
4 | * This file is subject to the terms and conditions of the GNU General Public | ||
5 | * License. See the file "COPYING" in the main directory of this archive | ||
6 | * for more details. | ||
7 | * | ||
8 | * Copyright (C) 1998 Ingo Molnar | ||
9 | * | ||
10 | * Support of BIGMEM added by Gerhard Wichert, Siemens AG, July 1999 | ||
11 | */ | ||
12 | |||
13 | #ifndef _ASM_FIXMAP_H | ||
14 | #define _ASM_FIXMAP_H | ||
15 | |||
16 | #include <asm/pgtable.h> | ||
17 | #ifdef CONFIG_HIGHMEM | ||
18 | #include <linux/threads.h> | ||
19 | #include <asm/kmap_types.h> | ||
20 | #endif | ||
21 | |||
22 | /* | ||
23 | * Here we define all the compile-time 'special' virtual | ||
24 | * addresses. The point is to have a constant address at | ||
25 | * compile time, but to set the physical address only | ||
26 | * in the boot process. We allocate these special addresses | ||
27 | * from the end of the consistent memory region backwards. | ||
28 | * Also this lets us do fail-safe vmalloc(), we | ||
29 | * can guarantee that these special addresses and | ||
30 | * vmalloc()-ed addresses never overlap. | ||
31 | * | ||
32 | * these 'compile-time allocated' memory buffers are | ||
33 | * fixed-size 4k pages. (or larger if used with an increment | ||
34 | * higher than 1) use fixmap_set(idx,phys) to associate | ||
35 | * physical memory with fixmap indices. | ||
36 | * | ||
37 | * TLB entries of such buffers will not be flushed across | ||
38 | * task switches. | ||
39 | */ | ||
40 | enum fixed_addresses { | ||
41 | #define FIX_N_COLOURS 8 | ||
42 | #ifdef CONFIG_HIGHMEM | ||
43 | /* reserved pte's for temporary kernel mappings */ | ||
44 | FIX_KMAP_BEGIN, | ||
45 | FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1, | ||
46 | #endif | ||
47 | __end_of_fixed_addresses | ||
48 | }; | ||
49 | |||
50 | #define FIXADDR_TOP (CONSISTENT_START - PAGE_SIZE) | ||
51 | #define FIXADDR_SIZE (__end_of_fixed_addresses << PAGE_SHIFT) | ||
52 | #define FIXADDR_START ((FIXADDR_TOP - FIXADDR_SIZE) & PMD_MASK) | ||
53 | |||
54 | #define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) | ||
55 | #define __virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT) | ||
56 | |||
57 | extern void __this_fixmap_does_not_exist(void); | ||
58 | /* | ||
59 | * 'index to address' translation. If anyone tries to use the idx | ||
60 | * directly without tranlation, we catch the bug with a NULL-deference | ||
61 | * kernel oops. Illegal ranges of incoming indices are caught too. | ||
62 | */ | ||
63 | static inline unsigned long fix_to_virt(const unsigned int idx) | ||
64 | { | ||
65 | /* | ||
66 | * this branch gets completely eliminated after inlining, | ||
67 | * except when someone tries to use fixaddr indices in an | ||
68 | * illegal way. (such as mixing up address types or using | ||
69 | * out-of-range indices). | ||
70 | * | ||
71 | * If it doesn't get removed, the linker will complain | ||
72 | * loudly with a reasonably clear error message.. | ||
73 | */ | ||
74 | if (idx >= __end_of_fixed_addresses) | ||
75 | __this_fixmap_does_not_exist(); | ||
76 | |||
77 | return __fix_to_virt(idx); | ||
78 | } | ||
79 | |||
80 | static inline unsigned long virt_to_fix(const unsigned long vaddr) | ||
81 | { | ||
82 | BUG_ON(vaddr >= FIXADDR_TOP || vaddr < FIXADDR_START); | ||
83 | return __virt_to_fix(vaddr); | ||
84 | } | ||
85 | |||
86 | #define kmap_get_fixmap_pte(vaddr) \ | ||
87 | pte_offset_kernel( \ | ||
88 | pmd_offset(pud_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr)), \ | ||
89 | (vaddr) \ | ||
90 | ) | ||
91 | |||
92 | /* | ||
93 | * Called from pgtable_init() | ||
94 | */ | ||
95 | extern void fixrange_init(unsigned long start, unsigned long end, | ||
96 | pgd_t *pgd_base); | ||
97 | |||
98 | |||
99 | #endif | ||
diff --git a/arch/metag/include/asm/highmem.h b/arch/metag/include/asm/highmem.h new file mode 100644 index 000000000000..6646a15c73dd --- /dev/null +++ b/arch/metag/include/asm/highmem.h | |||
@@ -0,0 +1,62 @@ | |||
1 | #ifndef _ASM_HIGHMEM_H | ||
2 | #define _ASM_HIGHMEM_H | ||
3 | |||
4 | #include <asm/cacheflush.h> | ||
5 | #include <asm/kmap_types.h> | ||
6 | #include <asm/fixmap.h> | ||
7 | |||
8 | /* | ||
9 | * Right now we initialize only a single pte table. It can be extended | ||
10 | * easily, subsequent pte tables have to be allocated in one physical | ||
11 | * chunk of RAM. | ||
12 | */ | ||
13 | /* | ||
14 | * Ordering is (from lower to higher memory addresses): | ||
15 | * | ||
16 | * high_memory | ||
17 | * Persistent kmap area | ||
18 | * PKMAP_BASE | ||
19 | * fixed_addresses | ||
20 | * FIXADDR_START | ||
21 | * FIXADDR_TOP | ||
22 | * Vmalloc area | ||
23 | * VMALLOC_START | ||
24 | * VMALLOC_END | ||
25 | */ | ||
26 | #define PKMAP_BASE (FIXADDR_START - PMD_SIZE) | ||
27 | #define LAST_PKMAP PTRS_PER_PTE | ||
28 | #define LAST_PKMAP_MASK (LAST_PKMAP - 1) | ||
29 | #define PKMAP_NR(virt) (((virt) - PKMAP_BASE) >> PAGE_SHIFT) | ||
30 | #define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT)) | ||
31 | |||
32 | #define kmap_prot PAGE_KERNEL | ||
33 | |||
34 | static inline void flush_cache_kmaps(void) | ||
35 | { | ||
36 | flush_cache_all(); | ||
37 | } | ||
38 | |||
39 | /* declarations for highmem.c */ | ||
40 | extern unsigned long highstart_pfn, highend_pfn; | ||
41 | |||
42 | extern pte_t *pkmap_page_table; | ||
43 | |||
44 | extern void *kmap_high(struct page *page); | ||
45 | extern void kunmap_high(struct page *page); | ||
46 | |||
47 | extern void kmap_init(void); | ||
48 | |||
49 | /* | ||
50 | * The following functions are already defined by <linux/highmem.h> | ||
51 | * when CONFIG_HIGHMEM is not set. | ||
52 | */ | ||
53 | #ifdef CONFIG_HIGHMEM | ||
54 | extern void *kmap(struct page *page); | ||
55 | extern void kunmap(struct page *page); | ||
56 | extern void *kmap_atomic(struct page *page); | ||
57 | extern void __kunmap_atomic(void *kvaddr); | ||
58 | extern void *kmap_atomic_pfn(unsigned long pfn); | ||
59 | extern struct page *kmap_atomic_to_page(void *ptr); | ||
60 | #endif | ||
61 | |||
62 | #endif | ||
diff --git a/arch/metag/mm/highmem.c b/arch/metag/mm/highmem.c new file mode 100644 index 000000000000..d71f621a2c0b --- /dev/null +++ b/arch/metag/mm/highmem.c | |||
@@ -0,0 +1,133 @@ | |||
1 | #include <linux/export.h> | ||
2 | #include <linux/highmem.h> | ||
3 | #include <linux/sched.h> | ||
4 | #include <linux/smp.h> | ||
5 | #include <linux/interrupt.h> | ||
6 | #include <asm/fixmap.h> | ||
7 | #include <asm/tlbflush.h> | ||
8 | |||
9 | static pte_t *kmap_pte; | ||
10 | |||
11 | unsigned long highstart_pfn, highend_pfn; | ||
12 | |||
13 | void *kmap(struct page *page) | ||
14 | { | ||
15 | might_sleep(); | ||
16 | if (!PageHighMem(page)) | ||
17 | return page_address(page); | ||
18 | return kmap_high(page); | ||
19 | } | ||
20 | EXPORT_SYMBOL(kmap); | ||
21 | |||
22 | void kunmap(struct page *page) | ||
23 | { | ||
24 | BUG_ON(in_interrupt()); | ||
25 | if (!PageHighMem(page)) | ||
26 | return; | ||
27 | kunmap_high(page); | ||
28 | } | ||
29 | EXPORT_SYMBOL(kunmap); | ||
30 | |||
31 | /* | ||
32 | * kmap_atomic/kunmap_atomic is significantly faster than kmap/kunmap because | ||
33 | * no global lock is needed and because the kmap code must perform a global TLB | ||
34 | * invalidation when the kmap pool wraps. | ||
35 | * | ||
36 | * However when holding an atomic kmap is is not legal to sleep, so atomic | ||
37 | * kmaps are appropriate for short, tight code paths only. | ||
38 | */ | ||
39 | |||
40 | void *kmap_atomic(struct page *page) | ||
41 | { | ||
42 | enum fixed_addresses idx; | ||
43 | unsigned long vaddr; | ||
44 | int type; | ||
45 | |||
46 | /* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ | ||
47 | pagefault_disable(); | ||
48 | if (!PageHighMem(page)) | ||
49 | return page_address(page); | ||
50 | |||
51 | type = kmap_atomic_idx_push(); | ||
52 | idx = type + KM_TYPE_NR * smp_processor_id(); | ||
53 | vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); | ||
54 | #ifdef CONFIG_DEBUG_HIGHMEM | ||
55 | BUG_ON(!pte_none(*(kmap_pte - idx))); | ||
56 | #endif | ||
57 | set_pte(kmap_pte - idx, mk_pte(page, PAGE_KERNEL)); | ||
58 | |||
59 | return (void *)vaddr; | ||
60 | } | ||
61 | EXPORT_SYMBOL(kmap_atomic); | ||
62 | |||
63 | void __kunmap_atomic(void *kvaddr) | ||
64 | { | ||
65 | unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; | ||
66 | int idx, type; | ||
67 | |||
68 | if (kvaddr >= (void *)FIXADDR_START) { | ||
69 | type = kmap_atomic_idx(); | ||
70 | idx = type + KM_TYPE_NR * smp_processor_id(); | ||
71 | |||
72 | /* | ||
73 | * Force other mappings to Oops if they'll try to access this | ||
74 | * pte without first remap it. Keeping stale mappings around | ||
75 | * is a bad idea also, in case the page changes cacheability | ||
76 | * attributes or becomes a protected page in a hypervisor. | ||
77 | */ | ||
78 | pte_clear(&init_mm, vaddr, kmap_pte-idx); | ||
79 | flush_tlb_kernel_range(vaddr, vaddr + PAGE_SIZE); | ||
80 | |||
81 | kmap_atomic_idx_pop(); | ||
82 | } | ||
83 | |||
84 | pagefault_enable(); | ||
85 | } | ||
86 | EXPORT_SYMBOL(__kunmap_atomic); | ||
87 | |||
88 | /* | ||
89 | * This is the same as kmap_atomic() but can map memory that doesn't | ||
90 | * have a struct page associated with it. | ||
91 | */ | ||
92 | void *kmap_atomic_pfn(unsigned long pfn) | ||
93 | { | ||
94 | enum fixed_addresses idx; | ||
95 | unsigned long vaddr; | ||
96 | int type; | ||
97 | |||
98 | pagefault_disable(); | ||
99 | |||
100 | type = kmap_atomic_idx_push(); | ||
101 | idx = type + KM_TYPE_NR * smp_processor_id(); | ||
102 | vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); | ||
103 | #ifdef CONFIG_DEBUG_HIGHMEM | ||
104 | BUG_ON(!pte_none(*(kmap_pte - idx))); | ||
105 | #endif | ||
106 | set_pte(kmap_pte - idx, pfn_pte(pfn, PAGE_KERNEL)); | ||
107 | flush_tlb_kernel_range(vaddr, vaddr + PAGE_SIZE); | ||
108 | |||
109 | return (void *)vaddr; | ||
110 | } | ||
111 | |||
112 | struct page *kmap_atomic_to_page(void *ptr) | ||
113 | { | ||
114 | unsigned long vaddr = (unsigned long)ptr; | ||
115 | int idx; | ||
116 | pte_t *pte; | ||
117 | |||
118 | if (vaddr < FIXADDR_START) | ||
119 | return virt_to_page(ptr); | ||
120 | |||
121 | idx = virt_to_fix(vaddr); | ||
122 | pte = kmap_pte - (idx - FIX_KMAP_BEGIN); | ||
123 | return pte_page(*pte); | ||
124 | } | ||
125 | |||
126 | void __init kmap_init(void) | ||
127 | { | ||
128 | unsigned long kmap_vstart; | ||
129 | |||
130 | /* cache the first kmap pte */ | ||
131 | kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN); | ||
132 | kmap_pte = kmap_get_fixmap_pte(kmap_vstart); | ||
133 | } | ||