| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * MMU Tests |
| * |
| * Copyright 2024 Nicholas Piggin, IBM Corp. |
| */ |
| #include <libcflat.h> |
| #include <asm/atomic.h> |
| #include <asm/barrier.h> |
| #include <asm/processor.h> |
| #include <asm/mmu.h> |
| #include <asm/smp.h> |
| #include <asm/setup.h> |
| #include <asm/ppc_asm.h> |
| #include <vmalloc.h> |
| #include <devicetree.h> |
| |
| static volatile bool tlbie_test_running = true; |
| static volatile bool tlbie_test_failed = false; |
| static int tlbie_fn_started; |
| |
| static void *memory; |
| |
| static void trap_handler(struct pt_regs *regs, void *opaque) |
| { |
| tlbie_test_failed = true; |
| regs_advance_insn(regs); |
| } |
| |
| static void tlbie_fn(int cpu_id) |
| { |
| volatile char *m = memory; |
| |
| setup_mmu(0, NULL); |
| |
| atomic_fetch_inc(&tlbie_fn_started); |
| while (tlbie_test_running) { |
| unsigned long tmp; |
| |
| /* |
| * This is intended to execuse a QEMU TCG bug by forming a |
| * large TB which can prevent async work from running while the |
| * TB executes, so it could miss a broadcast TLB invalidation |
| * and pick up a stale translation. |
| */ |
| asm volatile (".rept 256 ; lbz %0,0(%1) ; tdnei %0,0 ; .endr" : "=&r"(tmp) : "r"(m)); |
| } |
| } |
| |
| #define ITERS 100000 |
| |
| static void test_tlbie(int argc, char **argv) |
| { |
| void *m[2]; |
| phys_addr_t p[2]; |
| pteval_t pteval[2]; |
| pteval_t *ptep; |
| int i; |
| |
| if (argc > 2) |
| report_abort("Unsupported argument: '%s'", argv[2]); |
| |
| if (nr_cpus_present < 2) { |
| report_skip("Requires SMP (2 or more CPUs)"); |
| return; |
| } |
| |
| handle_exception(0x700, &trap_handler, NULL); |
| |
| m[0] = alloc_page(); |
| p[0] = virt_to_phys(m[0]); |
| memset(m[0], 0, PAGE_SIZE); |
| m[1] = alloc_page(); |
| p[1] = virt_to_phys(m[1]); |
| memset(m[1], 0, PAGE_SIZE); |
| |
| memory = alloc_vpages(1); |
| ptep = install_page(NULL, p[0], memory); |
| pteval[0] = *ptep; |
| assert(ptep == install_page(NULL, p[1], memory)); |
| pteval[1] = *ptep; |
| assert(ptep == install_page(NULL, p[0], memory)); |
| assert(pteval[0] == *ptep); |
| flush_tlb_page((unsigned long)memory); |
| |
| if (!start_all_cpus(tlbie_fn)) |
| report_abort("Failed to start secondary cpus"); |
| |
| while (tlbie_fn_started < nr_cpus_present - 1) { |
| cpu_relax(); |
| } |
| |
| for (i = 0; i < ITERS; i++) { |
| *ptep = pteval[1]; |
| flush_tlb_page((unsigned long)memory); |
| *(long *)m[0] = -1; |
| barrier(); |
| *(long *)m[0] = 0; |
| barrier(); |
| *ptep = pteval[0]; |
| flush_tlb_page((unsigned long)memory); |
| *(long *)m[1] = -1; |
| barrier(); |
| *(long *)m[1] = 0; |
| barrier(); |
| if (tlbie_test_failed) |
| break; |
| } |
| |
| tlbie_test_running = false; |
| |
| stop_all_cpus(); |
| |
| handle_exception(0x700, NULL, NULL); |
| |
| /* TCG has a known race invalidating other CPUs */ |
| report_kfail(host_is_tcg, !tlbie_test_failed, "tlbie"); |
| } |
| |
| #define THIS_ITERS 100000 |
| |
| static void test_tlbie_this_cpu(int argc, char **argv) |
| { |
| void *m[2]; |
| phys_addr_t p[2]; |
| pteval_t pteval[2]; |
| pteval_t *ptep; |
| int i; |
| bool success; |
| |
| if (argc > 2) |
| report_abort("Unsupported argument: '%s'", argv[2]); |
| |
| m[0] = alloc_page(); |
| p[0] = virt_to_phys(m[0]); |
| memset(m[0], 0, PAGE_SIZE); |
| m[1] = alloc_page(); |
| p[1] = virt_to_phys(m[1]); |
| memset(m[1], 0, PAGE_SIZE); |
| |
| memory = alloc_vpages(1); |
| ptep = install_page(NULL, p[0], memory); |
| pteval[0] = *ptep; |
| assert(ptep == install_page(NULL, p[1], memory)); |
| pteval[1] = *ptep; |
| assert(ptep == install_page(NULL, p[0], memory)); |
| assert(pteval[0] == *ptep); |
| flush_tlb_page((unsigned long)memory); |
| |
| *(long *)m[0] = 0; |
| *(long *)m[1] = -1; |
| |
| success = true; |
| for (i = 0; i < THIS_ITERS; i++) { |
| if (*(long *)memory != 0) { |
| success = false; |
| break; |
| } |
| *ptep = pteval[1]; |
| flush_tlb_page_local((unsigned long)memory); |
| if (*(long *)memory != -1) { |
| success = false; |
| break; |
| } |
| *ptep = pteval[0]; |
| flush_tlb_page_local((unsigned long)memory); |
| } |
| report(success, "tlbiel"); |
| |
| success = true; |
| flush_tlb_page((unsigned long)memory); |
| for (i = 0; i < THIS_ITERS; i++) { |
| if (*(long *)memory != 0) { |
| success = false; |
| break; |
| } |
| *ptep = pteval[1]; |
| flush_tlb_page((unsigned long)memory); |
| if (*(long *)memory != -1) { |
| success = false; |
| break; |
| } |
| *ptep = pteval[0]; |
| flush_tlb_page((unsigned long)memory); |
| } |
| report(success, "tlbie"); |
| } |
| |
| |
| struct { |
| const char *name; |
| void (*func)(int argc, char **argv); |
| } hctests[] = { |
| { "tlbi-this-cpu", test_tlbie_this_cpu }, |
| { "tlbi-other-cpu", test_tlbie }, |
| { NULL, NULL } |
| }; |
| |
| int main(int argc, char **argv) |
| { |
| bool all; |
| int i; |
| |
| if (!vm_available()) { |
| report_skip("MMU is only supported for radix"); |
| return 0; |
| } |
| |
| setup_vm(); |
| |
| all = argc == 1 || !strcmp(argv[1], "all"); |
| |
| report_prefix_push("mmu"); |
| |
| for (i = 0; hctests[i].name != NULL; i++) { |
| if (all || strcmp(argv[1], hctests[i].name) == 0) { |
| report_prefix_push(hctests[i].name); |
| hctests[i].func(argc, argv); |
| report_prefix_pop(); |
| } |
| } |
| |
| report_prefix_pop(); |
| return report_summary(); |
| } |