cpa-test.c (5757B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * self test for change_page_attr. 4 * 5 * Clears the a test pte bit on random pages in the direct mapping, 6 * then reverts and compares page tables forwards and afterwards. 7 */ 8#include <linux/memblock.h> 9#include <linux/kthread.h> 10#include <linux/random.h> 11#include <linux/kernel.h> 12#include <linux/init.h> 13#include <linux/mm.h> 14#include <linux/vmalloc.h> 15 16#include <asm/cacheflush.h> 17#include <asm/kdebug.h> 18 19/* 20 * Only print the results of the first pass: 21 */ 22static __read_mostly int print = 1; 23 24enum { 25 NTEST = 3 * 100, 26 NPAGES = 100, 27#ifdef CONFIG_X86_64 28 LPS = (1 << PMD_SHIFT), 29#elif defined(CONFIG_X86_PAE) 30 LPS = (1 << PMD_SHIFT), 31#else 32 LPS = (1 << 22), 33#endif 34 GPS = (1<<30) 35}; 36 37#define PAGE_CPA_TEST __pgprot(_PAGE_CPA_TEST) 38 39static int pte_testbit(pte_t pte) 40{ 41 return pte_flags(pte) & _PAGE_SOFTW1; 42} 43 44struct split_state { 45 long lpg, gpg, spg, exec; 46 long min_exec, max_exec; 47}; 48 49static int print_split(struct split_state *s) 50{ 51 long i, expected, missed = 0; 52 int err = 0; 53 54 s->lpg = s->gpg = s->spg = s->exec = 0; 55 s->min_exec = ~0UL; 56 s->max_exec = 0; 57 for (i = 0; i < max_pfn_mapped; ) { 58 unsigned long addr = (unsigned long)__va(i << PAGE_SHIFT); 59 unsigned int level; 60 pte_t *pte; 61 62 pte = lookup_address(addr, &level); 63 if (!pte) { 64 missed++; 65 i++; 66 continue; 67 } 68 69 if (level == PG_LEVEL_1G && sizeof(long) == 8) { 70 s->gpg++; 71 i += GPS/PAGE_SIZE; 72 } else if (level == PG_LEVEL_2M) { 73 if ((pte_val(*pte) & _PAGE_PRESENT) && !(pte_val(*pte) & _PAGE_PSE)) { 74 printk(KERN_ERR 75 "%lx level %d but not PSE %Lx\n", 76 addr, 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 (addr < s->min_exec) 88 s->min_exec = addr; 89 if (addr > s->max_exec) 90 s->max_exec = addr; 91 } 92 } 93 if (print) { 94 printk(KERN_INFO 95 " 4k %lu large %lu gb %lu x %lu[%lx-%lx] miss %lu\n", 96 s->spg, s->lpg, s->gpg, s->exec, 97 s->min_exec != ~0UL ? s->min_exec : 0, 98 s->max_exec, missed); 99 } 100 101 expected = (s->gpg*GPS + s->lpg*LPS)/PAGE_SIZE + s->spg + missed; 102 if (expected != i) { 103 printk(KERN_ERR "CPA max_pfn_mapped %lu but expected %lu\n", 104 max_pfn_mapped, expected); 105 return 1; 106 } 107 return err; 108} 109 110static unsigned long addr[NTEST]; 111static unsigned int len[NTEST]; 112 113static struct page *pages[NPAGES]; 114static unsigned long addrs[NPAGES]; 115 116/* Change the global bit on random pages in the direct mapping */ 117static int pageattr_test(void) 118{ 119 struct split_state sa, sb, sc; 120 unsigned long *bm; 121 pte_t *pte, pte0; 122 int failed = 0; 123 unsigned int level; 124 int i, k; 125 int err; 126 127 if (print) 128 printk(KERN_INFO "CPA self-test:\n"); 129 130 bm = vzalloc((max_pfn_mapped + 7) / 8); 131 if (!bm) { 132 printk(KERN_ERR "CPA Cannot vmalloc bitmap\n"); 133 return -ENOMEM; 134 } 135 136 failed += print_split(&sa); 137 138 for (i = 0; i < NTEST; i++) { 139 unsigned long pfn = prandom_u32() % max_pfn_mapped; 140 141 addr[i] = (unsigned long)__va(pfn << PAGE_SHIFT); 142 len[i] = prandom_u32() % NPAGES; 143 len[i] = min_t(unsigned long, len[i], max_pfn_mapped - pfn - 1); 144 145 if (len[i] == 0) 146 len[i] = 1; 147 148 pte = NULL; 149 pte0 = pfn_pte(0, __pgprot(0)); /* shut gcc up */ 150 151 for (k = 0; k < len[i]; k++) { 152 pte = lookup_address(addr[i] + k*PAGE_SIZE, &level); 153 if (!pte || pgprot_val(pte_pgprot(*pte)) == 0 || 154 !(pte_val(*pte) & _PAGE_PRESENT)) { 155 addr[i] = 0; 156 break; 157 } 158 if (k == 0) { 159 pte0 = *pte; 160 } else { 161 if (pgprot_val(pte_pgprot(*pte)) != 162 pgprot_val(pte_pgprot(pte0))) { 163 len[i] = k; 164 break; 165 } 166 } 167 if (test_bit(pfn + k, bm)) { 168 len[i] = k; 169 break; 170 } 171 __set_bit(pfn + k, bm); 172 addrs[k] = addr[i] + k*PAGE_SIZE; 173 pages[k] = pfn_to_page(pfn + k); 174 } 175 if (!addr[i] || !pte || !k) { 176 addr[i] = 0; 177 continue; 178 } 179 180 switch (i % 3) { 181 case 0: 182 err = change_page_attr_set(&addr[i], len[i], PAGE_CPA_TEST, 0); 183 break; 184 185 case 1: 186 err = change_page_attr_set(addrs, len[1], PAGE_CPA_TEST, 1); 187 break; 188 189 case 2: 190 err = cpa_set_pages_array(pages, len[i], PAGE_CPA_TEST); 191 break; 192 } 193 194 195 if (err < 0) { 196 printk(KERN_ERR "CPA %d failed %d\n", i, err); 197 failed++; 198 } 199 200 pte = lookup_address(addr[i], &level); 201 if (!pte || !pte_testbit(*pte) || pte_huge(*pte)) { 202 printk(KERN_ERR "CPA %lx: bad pte %Lx\n", addr[i], 203 pte ? (u64)pte_val(*pte) : 0ULL); 204 failed++; 205 } 206 if (level != PG_LEVEL_4K) { 207 printk(KERN_ERR "CPA %lx: unexpected level %d\n", 208 addr[i], level); 209 failed++; 210 } 211 212 } 213 vfree(bm); 214 215 failed += print_split(&sb); 216 217 for (i = 0; i < NTEST; i++) { 218 if (!addr[i]) 219 continue; 220 pte = lookup_address(addr[i], &level); 221 if (!pte) { 222 printk(KERN_ERR "CPA lookup of %lx failed\n", addr[i]); 223 failed++; 224 continue; 225 } 226 err = change_page_attr_clear(&addr[i], len[i], PAGE_CPA_TEST, 0); 227 if (err < 0) { 228 printk(KERN_ERR "CPA reverting failed: %d\n", err); 229 failed++; 230 } 231 pte = lookup_address(addr[i], &level); 232 if (!pte || pte_testbit(*pte)) { 233 printk(KERN_ERR "CPA %lx: bad pte after revert %Lx\n", 234 addr[i], pte ? (u64)pte_val(*pte) : 0ULL); 235 failed++; 236 } 237 238 } 239 240 failed += print_split(&sc); 241 242 if (failed) { 243 WARN(1, KERN_ERR "NOT PASSED. Please report.\n"); 244 return -EINVAL; 245 } else { 246 if (print) 247 printk(KERN_INFO "ok.\n"); 248 } 249 250 return 0; 251} 252 253static int do_pageattr_test(void *__unused) 254{ 255 while (!kthread_should_stop()) { 256 schedule_timeout_interruptible(HZ*30); 257 if (pageattr_test() < 0) 258 break; 259 if (print) 260 print--; 261 } 262 return 0; 263} 264 265static int start_pageattr_test(void) 266{ 267 struct task_struct *p; 268 269 p = kthread_create(do_pageattr_test, NULL, "pageattr-test"); 270 if (!IS_ERR(p)) 271 wake_up_process(p); 272 else 273 WARN_ON(1); 274 275 return 0; 276} 277device_initcall(start_pageattr_test);