1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * MMU Tests 4 * 5 * Copyright 2024 Nicholas Piggin, IBM Corp. 6 */ 7 #include <libcflat.h> 8 #include <asm/atomic.h> 9 #include <asm/barrier.h> 10 #include <asm/processor.h> 11 #include <asm/mmu.h> 12 #include <asm/smp.h> 13 #include <asm/setup.h> 14 #include <asm/ppc_asm.h> 15 #include <vmalloc.h> 16 #include <devicetree.h> 17 18 static volatile bool tlbie_test_running = true; 19 static volatile bool tlbie_test_failed = false; 20 static int tlbie_fn_started; 21 22 static void *memory; 23 24 static void trap_handler(struct pt_regs *regs, void *opaque) 25 { 26 tlbie_test_failed = true; 27 regs_advance_insn(regs); 28 } 29 30 static void tlbie_fn(int cpu_id) 31 { 32 volatile char *m = memory; 33 34 setup_mmu(0, NULL); 35 36 atomic_fetch_inc(&tlbie_fn_started); 37 while (tlbie_test_running) { 38 unsigned long tmp; 39 40 /* 41 * This is intended to execuse a QEMU TCG bug by forming a 42 * large TB which can prevent async work from running while the 43 * TB executes, so it could miss a broadcast TLB invalidation 44 * and pick up a stale translation. 45 */ 46 asm volatile (".rept 256 ; lbz %0,0(%1) ; tdnei %0,0 ; .endr" : "=&r"(tmp) : "r"(m)); 47 } 48 } 49 50 #define ITERS 100000 51 52 static void test_tlbie(int argc, char **argv) 53 { 54 void *m[2]; 55 phys_addr_t p[2]; 56 pteval_t pteval[2]; 57 pteval_t *ptep; 58 int i; 59 60 if (argc > 2) 61 report_abort("Unsupported argument: '%s'", argv[2]); 62 63 if (nr_cpus_present < 2) { 64 report_skip("Requires SMP (2 or more CPUs)"); 65 return; 66 } 67 68 handle_exception(0x700, &trap_handler, NULL); 69 70 m[0] = alloc_page(); 71 p[0] = virt_to_phys(m[0]); 72 memset(m[0], 0, PAGE_SIZE); 73 m[1] = alloc_page(); 74 p[1] = virt_to_phys(m[1]); 75 memset(m[1], 0, PAGE_SIZE); 76 77 memory = alloc_vpages(1); 78 ptep = install_page(NULL, p[0], memory); 79 pteval[0] = *ptep; 80 assert(ptep == install_page(NULL, p[1], memory)); 81 pteval[1] = *ptep; 82 assert(ptep == install_page(NULL, p[0], memory)); 83 assert(pteval[0] == *ptep); 84 flush_tlb_page((unsigned long)memory); 85 86 if (!start_all_cpus(tlbie_fn)) 87 report_abort("Failed to start secondary cpus"); 88 89 while (tlbie_fn_started < nr_cpus_present - 1) { 90 cpu_relax(); 91 } 92 93 for (i = 0; i < ITERS; i++) { 94 *ptep = pteval[1]; 95 flush_tlb_page((unsigned long)memory); 96 *(long *)m[0] = -1; 97 barrier(); 98 *(long *)m[0] = 0; 99 barrier(); 100 *ptep = pteval[0]; 101 flush_tlb_page((unsigned long)memory); 102 *(long *)m[1] = -1; 103 barrier(); 104 *(long *)m[1] = 0; 105 barrier(); 106 if (tlbie_test_failed) 107 break; 108 } 109 110 tlbie_test_running = false; 111 112 stop_all_cpus(); 113 114 handle_exception(0x700, NULL, NULL); 115 116 /* TCG has a known race invalidating other CPUs */ 117 report_kfail(host_is_tcg, !tlbie_test_failed, "tlbie"); 118 } 119 120 #define THIS_ITERS 100000 121 122 static void test_tlbie_this_cpu(int argc, char **argv) 123 { 124 void *m[2]; 125 phys_addr_t p[2]; 126 pteval_t pteval[2]; 127 pteval_t *ptep; 128 int i; 129 bool success; 130 131 if (argc > 2) 132 report_abort("Unsupported argument: '%s'", argv[2]); 133 134 m[0] = alloc_page(); 135 p[0] = virt_to_phys(m[0]); 136 memset(m[0], 0, PAGE_SIZE); 137 m[1] = alloc_page(); 138 p[1] = virt_to_phys(m[1]); 139 memset(m[1], 0, PAGE_SIZE); 140 141 memory = alloc_vpages(1); 142 ptep = install_page(NULL, p[0], memory); 143 pteval[0] = *ptep; 144 assert(ptep == install_page(NULL, p[1], memory)); 145 pteval[1] = *ptep; 146 assert(ptep == install_page(NULL, p[0], memory)); 147 assert(pteval[0] == *ptep); 148 flush_tlb_page((unsigned long)memory); 149 150 *(long *)m[0] = 0; 151 *(long *)m[1] = -1; 152 153 success = true; 154 for (i = 0; i < THIS_ITERS; i++) { 155 if (*(long *)memory != 0) { 156 success = false; 157 break; 158 } 159 *ptep = pteval[1]; 160 flush_tlb_page_local((unsigned long)memory); 161 if (*(long *)memory != -1) { 162 success = false; 163 break; 164 } 165 *ptep = pteval[0]; 166 flush_tlb_page_local((unsigned long)memory); 167 } 168 report(success, "tlbiel"); 169 170 success = true; 171 flush_tlb_page((unsigned long)memory); 172 for (i = 0; i < THIS_ITERS; i++) { 173 if (*(long *)memory != 0) { 174 success = false; 175 break; 176 } 177 *ptep = pteval[1]; 178 flush_tlb_page((unsigned long)memory); 179 if (*(long *)memory != -1) { 180 success = false; 181 break; 182 } 183 *ptep = pteval[0]; 184 flush_tlb_page((unsigned long)memory); 185 } 186 report(success, "tlbie"); 187 } 188 189 190 struct { 191 const char *name; 192 void (*func)(int argc, char **argv); 193 } hctests[] = { 194 { "tlbi-this-cpu", test_tlbie_this_cpu }, 195 { "tlbi-other-cpu", test_tlbie }, 196 { NULL, NULL } 197 }; 198 199 int main(int argc, char **argv) 200 { 201 bool all; 202 int i; 203 204 if (!vm_available()) { 205 report_skip("MMU is only supported for radix"); 206 return 0; 207 } 208 209 setup_vm(); 210 211 all = argc == 1 || !strcmp(argv[1], "all"); 212 213 report_prefix_push("mmu"); 214 215 for (i = 0; hctests[i].name != NULL; i++) { 216 if (all || strcmp(argv[1], hctests[i].name) == 0) { 217 report_prefix_push(hctests[i].name); 218 hctests[i].func(argc, argv); 219 report_prefix_pop(); 220 } 221 } 222 223 report_prefix_pop(); 224 return report_summary(); 225 } 226