diff options
-rw-r--r-- | arch/x86/mm/Makefile_32 | 1 | ||||
-rw-r--r-- | arch/x86/mm/Makefile_64 | 1 | ||||
-rw-r--r-- | arch/x86/mm/pageattr-test.c | 233 |
3 files changed, 235 insertions, 0 deletions
diff --git a/arch/x86/mm/Makefile_32 b/arch/x86/mm/Makefile_32 index 6a5e981981f7..be2f55160bf8 100644 --- a/arch/x86/mm/Makefile_32 +++ b/arch/x86/mm/Makefile_32 | |||
@@ -4,6 +4,7 @@ | |||
4 | 4 | ||
5 | obj-y := init_32.o pgtable_32.o fault_32.o ioremap_32.o extable.o pageattr_32.o mmap.o | 5 | obj-y := init_32.o pgtable_32.o fault_32.o ioremap_32.o extable.o pageattr_32.o mmap.o |
6 | 6 | ||
7 | obj-$(CONFIG_CPA_DEBUG) += pageattr-test.o | ||
7 | obj-$(CONFIG_NUMA) += discontig_32.o | 8 | obj-$(CONFIG_NUMA) += discontig_32.o |
8 | obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o | 9 | obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o |
9 | obj-$(CONFIG_HIGHMEM) += highmem_32.o | 10 | obj-$(CONFIG_HIGHMEM) += highmem_32.o |
diff --git a/arch/x86/mm/Makefile_64 b/arch/x86/mm/Makefile_64 index 09c997fe5856..cb723167631b 100644 --- a/arch/x86/mm/Makefile_64 +++ b/arch/x86/mm/Makefile_64 | |||
@@ -7,3 +7,4 @@ obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o | |||
7 | obj-$(CONFIG_NUMA) += numa_64.o | 7 | obj-$(CONFIG_NUMA) += numa_64.o |
8 | obj-$(CONFIG_K8_NUMA) += k8topology_64.o | 8 | obj-$(CONFIG_K8_NUMA) += k8topology_64.o |
9 | obj-$(CONFIG_ACPI_NUMA) += srat_64.o | 9 | obj-$(CONFIG_ACPI_NUMA) += srat_64.o |
10 | obj-$(CONFIG_CPA_DEBUG) += pageattr-test.o | ||
diff --git a/arch/x86/mm/pageattr-test.c b/arch/x86/mm/pageattr-test.c new file mode 100644 index 000000000000..4b22eb47f23a --- /dev/null +++ b/arch/x86/mm/pageattr-test.c | |||
@@ -0,0 +1,233 @@ | |||
1 | /* | ||
2 | * self test for change_page_attr. | ||
3 | * | ||
4 | * Clears the global bit on random pages in the direct mapping, then reverts | ||
5 | * and compares page tables forwards and afterwards. | ||
6 | */ | ||
7 | |||
8 | #include <linux/mm.h> | ||
9 | #include <linux/random.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/init.h> | ||
12 | #include <linux/bootmem.h> | ||
13 | #include <asm/cacheflush.h> | ||
14 | #include <asm/pgtable.h> | ||
15 | #include <asm/kdebug.h> | ||
16 | |||
17 | enum { | ||
18 | NTEST = 400, | ||
19 | #ifdef CONFIG_X86_64 | ||
20 | LOWEST_LEVEL = 4, | ||
21 | LPS = (1 << PMD_SHIFT), | ||
22 | #elif defined(CONFIG_X86_PAE) | ||
23 | LOWEST_LEVEL = 3, | ||
24 | LPS = (1 << PMD_SHIFT), | ||
25 | #else | ||
26 | LOWEST_LEVEL = 3, /* lookup_address lies here */ | ||
27 | LPS = (1 << 22), | ||
28 | #endif | ||
29 | GPS = (1<<30) | ||
30 | }; | ||
31 | |||
32 | #ifdef CONFIG_X86_64 | ||
33 | #include <asm/proto.h> | ||
34 | #define max_mapped end_pfn_map | ||
35 | #else | ||
36 | #define max_mapped max_low_pfn | ||
37 | #endif | ||
38 | |||
39 | struct split_state { | ||
40 | long lpg, gpg, spg, exec; | ||
41 | long min_exec, max_exec; | ||
42 | }; | ||
43 | |||
44 | static __init int print_split(struct split_state *s) | ||
45 | { | ||
46 | int printed = 0; | ||
47 | long i, expected, missed = 0; | ||
48 | int err = 0; | ||
49 | |||
50 | s->lpg = s->gpg = s->spg = s->exec = 0; | ||
51 | s->min_exec = ~0UL; | ||
52 | s->max_exec = 0; | ||
53 | for (i = 0; i < max_mapped; ) { | ||
54 | int level; | ||
55 | pte_t *pte; | ||
56 | unsigned long adr = (unsigned long)__va(i << PAGE_SHIFT); | ||
57 | |||
58 | pte = lookup_address(adr, &level); | ||
59 | if (!pte) { | ||
60 | if (!printed) { | ||
61 | dump_pagetable(adr); | ||
62 | printk("CPA %lx no pte level %d\n", adr, level); | ||
63 | printed = 1; | ||
64 | } | ||
65 | missed++; | ||
66 | i++; | ||
67 | continue; | ||
68 | } | ||
69 | |||
70 | if (level == 2 && sizeof(long) == 8) { | ||
71 | s->gpg++; | ||
72 | i += GPS/PAGE_SIZE; | ||
73 | } else if (level != LOWEST_LEVEL) { | ||
74 | if (!(pte_val(*pte) & _PAGE_PSE)) { | ||
75 | printk("%lx level %d but not PSE %Lx\n", | ||
76 | adr, level, (u64)pte_val(*pte)); | ||
77 | err = 1; | ||
78 | } | ||
79 | s->lpg++; | ||
80 | i += LPS/PAGE_SIZE; | ||
81 | } else { | ||
82 | s->spg++; | ||
83 | i++; | ||
84 | } | ||
85 | if (!(pte_val(*pte) & _PAGE_NX)) { | ||
86 | s->exec++; | ||
87 | if (adr < s->min_exec) | ||
88 | s->min_exec = adr; | ||
89 | if (adr > s->max_exec) | ||
90 | s->max_exec = adr; | ||
91 | } | ||
92 | } | ||
93 | printk("CPA mapping 4k %lu large %lu gb %lu x %lu[%lx-%lx] miss %lu\n", | ||
94 | s->spg, s->lpg, s->gpg, s->exec, | ||
95 | s->min_exec != ~0UL ? s->min_exec : 0, s->max_exec, missed); | ||
96 | expected = (s->gpg*GPS + s->lpg*LPS)/PAGE_SIZE + s->spg + missed; | ||
97 | if (expected != i) { | ||
98 | printk("CPA max_mapped %lu but expected %lu\n", | ||
99 | max_mapped, expected); | ||
100 | return 1; | ||
101 | } | ||
102 | return err; | ||
103 | } | ||
104 | |||
105 | static __init int state_same(struct split_state *a, struct split_state *b) | ||
106 | { | ||
107 | return a->lpg == b->lpg && a->gpg == b->gpg && a->spg == b->spg && | ||
108 | a->exec == b->exec; | ||
109 | } | ||
110 | |||
111 | static unsigned long addr[NTEST] __initdata; | ||
112 | static unsigned len[NTEST] __initdata; | ||
113 | |||
114 | /* Change the global bit on random pages in the direct mapping */ | ||
115 | static __init int exercise_pageattr(void) | ||
116 | { | ||
117 | int i, k; | ||
118 | pte_t *pte, pte0; | ||
119 | int level; | ||
120 | int err; | ||
121 | struct split_state sa, sb, sc; | ||
122 | int failed = 0; | ||
123 | unsigned long *bm; | ||
124 | |||
125 | printk("CPA exercising pageattr\n"); | ||
126 | |||
127 | bm = vmalloc((max_mapped + 7) / 8); | ||
128 | if (!bm) { | ||
129 | printk("CPA Cannot vmalloc bitmap\n"); | ||
130 | return -ENOMEM; | ||
131 | } | ||
132 | memset(bm, 0, (max_mapped + 7) / 8); | ||
133 | |||
134 | failed += print_split(&sa); | ||
135 | srandom32(100); | ||
136 | for (i = 0; i < NTEST; i++) { | ||
137 | unsigned long pfn = random32() % max_mapped; | ||
138 | addr[i] = (unsigned long)__va(pfn << PAGE_SHIFT); | ||
139 | len[i] = random32() % 100; | ||
140 | len[i] = min_t(unsigned long, len[i], max_mapped - pfn - 1); | ||
141 | if (len[i] == 0) | ||
142 | len[i] = 1; | ||
143 | |||
144 | pte = NULL; | ||
145 | pte0 = pfn_pte(0, __pgprot(0)); /* shut gcc up */ | ||
146 | for (k = 0; k < len[i]; k++) { | ||
147 | pte = lookup_address(addr[i] + k*PAGE_SIZE, &level); | ||
148 | if (!pte || pgprot_val(pte_pgprot(*pte)) == 0) { | ||
149 | addr[i] = 0; | ||
150 | break; | ||
151 | } | ||
152 | if (k == 0) | ||
153 | pte0 = *pte; | ||
154 | else if (pgprot_val(pte_pgprot(*pte)) != | ||
155 | pgprot_val(pte_pgprot(pte0))) { | ||
156 | len[i] = k; | ||
157 | break; | ||
158 | } | ||
159 | if (test_bit(pfn + k, bm)) { | ||
160 | len[i] = k; | ||
161 | break; | ||
162 | } | ||
163 | __set_bit(pfn + k, bm); | ||
164 | } | ||
165 | if (!addr[i] || !pte || !k) { | ||
166 | addr[i] = 0; | ||
167 | continue; | ||
168 | } | ||
169 | |||
170 | err = change_page_attr(virt_to_page(addr[i]), len[i], | ||
171 | pte_pgprot(pte_clrhuge(pte_clrglobal(pte0)))); | ||
172 | if (err < 0) { | ||
173 | printk("CPA %d failed %d\n", i, err); | ||
174 | failed++; | ||
175 | } | ||
176 | |||
177 | pte = lookup_address(addr[i], &level); | ||
178 | if (!pte || pte_global(*pte) || pte_huge(*pte)) { | ||
179 | printk("CPA %lx: bad pte %Lx\n", addr[i], | ||
180 | pte ? (u64)pte_val(*pte) : 0ULL); | ||
181 | failed++; | ||
182 | } | ||
183 | if (level != LOWEST_LEVEL) { | ||
184 | printk("CPA %lx: unexpected level %d\n", addr[i], | ||
185 | level); | ||
186 | failed++; | ||
187 | } | ||
188 | |||
189 | } | ||
190 | vfree(bm); | ||
191 | global_flush_tlb(); | ||
192 | |||
193 | failed += print_split(&sb); | ||
194 | |||
195 | printk("CPA reverting everything\n"); | ||
196 | for (i = 0; i < NTEST; i++) { | ||
197 | if (!addr[i]) | ||
198 | continue; | ||
199 | pte = lookup_address(addr[i], &level); | ||
200 | if (!pte) { | ||
201 | printk("CPA lookup of %lx failed\n", addr[i]); | ||
202 | failed++; | ||
203 | continue; | ||
204 | } | ||
205 | err = change_page_attr(virt_to_page(addr[i]), len[i], | ||
206 | pte_pgprot(pte_mkglobal(*pte))); | ||
207 | if (err < 0) { | ||
208 | printk("CPA reverting failed: %d\n", err); | ||
209 | failed++; | ||
210 | } | ||
211 | pte = lookup_address(addr[i], &level); | ||
212 | if (!pte || !pte_global(*pte)) { | ||
213 | printk("CPA %lx: bad pte after revert %Lx\n", addr[i], | ||
214 | pte ? (u64)pte_val(*pte) : 0ULL); | ||
215 | failed++; | ||
216 | } | ||
217 | |||
218 | } | ||
219 | global_flush_tlb(); | ||
220 | |||
221 | failed += print_split(&sc); | ||
222 | if (!state_same(&sa, &sc)) | ||
223 | failed++; | ||
224 | |||
225 | if (failed) | ||
226 | printk("CPA selftests NOT PASSED. Please report.\n"); | ||
227 | else | ||
228 | printk("CPA selftests PASSED\n"); | ||
229 | |||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | module_init(exercise_pageattr); | ||