| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * KVM selftest powerpc library code - CPU-related functions (page tables...) | |
| */ | |
| #include <linux/sizes.h> | |
| #include "processor.h" | |
| #include "kvm_util.h" | |
| #include "ucall_common.h" | |
| #include "guest_modes.h" | |
| #include "hcall.h" | |
| #define RADIX_TREE_SIZE ((0x2UL << 61) | (0x5UL << 5)) /* 52-bits */ | |
| #define RADIX_PGD_INDEX_SIZE 13 | |
| /* | |
| * A radix table must be aligned to its size, and the tables are larger than | |
| * the guest base page size for several level/page size combinations. | |
| */ | |
| bool kvm_arch_needs_naturally_aligned_page_tables(void) | |
| { | |
| return true; | |
| } | |
| static void set_proc_table(struct kvm_vm *vm, int pid, u64 dw0, u64 dw1) | |
| { | |
| u64 *proc_table; | |
| proc_table = addr_gpa2hva(vm, vm->arch.prtb); | |
| proc_table[pid * 2 + 0] = cpu_to_be64(dw0); | |
| proc_table[pid * 2 + 1] = cpu_to_be64(dw1); | |
| } | |
| static void set_radix_proc_table(struct kvm_vm *vm, int pid, gpa_t pgd) | |
| { | |
| set_proc_table(vm, pid, pgd | RADIX_TREE_SIZE | RADIX_PGD_INDEX_SIZE, 0); | |
| } | |
| void virt_arch_pgd_alloc(struct kvm_vm *vm) | |
| { | |
| struct kvm_ppc_mmuv3_cfg mmu_cfg; | |
| gpa_t prtb, pgtb; | |
| size_t pgd_pages; | |
| TEST_ASSERT((vm->mode == VM_MODE_P52V52_4K) || | |
| (vm->mode == VM_MODE_P52V52_64K), | |
| "Unsupported guest mode, mode: 0x%x", vm->mode); | |
| if (vm->mmu.pgd_created) | |
| return; | |
| prtb = vm_alloc_page_table(vm); | |
| vm->arch.prtb = prtb; | |
| pgd_pages = (1UL << (RADIX_PGD_INDEX_SIZE + 3)) >> vm->page_shift; | |
| if (!pgd_pages) | |
| pgd_pages = 1; | |
| pgtb = vm_alloc_page_table_pages(vm, pgd_pages); | |
| vm->mmu.pgd = pgtb; | |
| /* Set the base page directory in the proc table */ | |
| set_radix_proc_table(vm, 0, pgtb); | |
| if (vm->mode == VM_MODE_P52V52_4K) | |
| mmu_cfg.process_table = prtb | 0x8000000000000000UL | 0x0; /* 4K size */ | |
| else /* vm->mode == VM_MODE_P52V52_64K */ | |
| mmu_cfg.process_table = prtb | 0x8000000000000000UL | 0x4; /* 64K size */ | |
| mmu_cfg.flags = KVM_PPC_MMUV3_RADIX | KVM_PPC_MMUV3_GTSE; | |
| vm_ioctl(vm, KVM_PPC_CONFIGURE_V3_MMU, &mmu_cfg); | |
| vm->mmu.pgd_created = true; | |
| } | |
| static int pt_shift(struct kvm_vm *vm, int level) | |
| { | |
| switch (level) { | |
| case 1: | |
| return 13; | |
| case 2: | |
| case 3: | |
| return 9; | |
| case 4: | |
| if (vm->mode == VM_MODE_P52V52_4K) | |
| return 9; | |
| else /* vm->mode == VM_MODE_P52V52_64K */ | |
| return 5; | |
| default: | |
| TEST_ASSERT(false, "Invalid page table level %d\n", level); | |
| return 0; | |
| } | |
| } | |
| static u64 pt_entry_coverage(struct kvm_vm *vm, int level) | |
| { | |
| u64 size = vm->page_size; | |
| if (level == 4) | |
| return size; | |
| size <<= pt_shift(vm, 4); | |
| if (level == 3) | |
| return size; | |
| size <<= pt_shift(vm, 3); | |
| if (level == 2) | |
| return size; | |
| size <<= pt_shift(vm, 2); | |
| return size; | |
| } | |
| static int pt_idx(struct kvm_vm *vm, gva_t gva, int level, u64 *nls) | |
| { | |
| switch (level) { | |
| case 1: | |
| if (nls) | |
| *nls = 0x9; | |
| return (gva >> 39) & 0x1fff; | |
| case 2: | |
| if (nls) | |
| *nls = 0x9; | |
| return (gva >> 30) & 0x1ff; | |
| case 3: | |
| if (vm->mode == VM_MODE_P52V52_4K) { | |
| if (nls) | |
| *nls = 0x9; | |
| } else { /* vm->mode == VM_MODE_P52V52_64K */ | |
| if (nls) | |
| *nls = 0x5; | |
| } | |
| return (gva >> 21) & 0x1ff; | |
| case 4: | |
| if (vm->mode == VM_MODE_P52V52_4K) | |
| return (gva >> 12) & 0x1ff; | |
| else /* vm->mode == VM_MODE_P52V52_64K */ | |
| return (gva >> 16) & 0x1f; | |
| default: | |
| TEST_ASSERT(false, "Invalid page table level %d\n", level); | |
| return 0; | |
| } | |
| } | |
| static u64 *virt_get_pte(struct kvm_vm *vm, gpa_t pt, | |
| gva_t gva, int level, u64 *nls) | |
| { | |
| int idx = pt_idx(vm, gva, level, nls); | |
| u64 *ptep = addr_gpa2hva(vm, pt + idx * 8); | |
| return ptep; | |
| } | |
| #define PTE_VALID 0x8000000000000000ull | |
| #define PTE_LEAF 0x4000000000000000ull | |
| #define PTE_REFERENCED 0x0000000000000100ull | |
| #define PTE_CHANGED 0x0000000000000080ull | |
| #define PTE_PRIV 0x0000000000000008ull | |
| #define PTE_READ 0x0000000000000004ull | |
| #define PTE_RW 0x0000000000000002ull | |
| #define PTE_EXEC 0x0000000000000001ull | |
| #define PTE_PAGE_MASK 0x01fffffffffff000ull | |
| #define PDE_VALID PTE_VALID | |
| #define PDE_NLS 0x0000000000000011ull | |
| #define PDE_PT_MASK 0x0fffffffffffff00ull | |
| static gpa_t __vm_alloc_pt(struct kvm_vm *vm, u64 pt_shift) | |
| { | |
| gpa_t pt; | |
| if (pt_shift >= vm->page_shift) { | |
| size_t pt_pages = 1ULL << (pt_shift - vm->page_shift); | |
| pt = vm_alloc_page_table_pages(vm, pt_pages); | |
| } else { | |
| struct vm_pt_frag_cache *pt_frag_cache; | |
| if (pt_shift == 8) { | |
| pt_frag_cache = &vm->arch.pt_frag_cache[0]; | |
| } else if (pt_shift == 12) { | |
| pt_frag_cache = &vm->arch.pt_frag_cache[1]; | |
| } else { | |
| TEST_ASSERT(0, "Invalid pt_shift:%lu\n", pt_shift); | |
| return 0; | |
| } | |
| if (!pt_frag_cache->page) | |
| pt_frag_cache->page = vm_alloc_page_table(vm); | |
| pt = pt_frag_cache->page + pt_frag_cache->page_nr_used; | |
| pt_frag_cache->page_nr_used += (1 << pt_shift); | |
| if (pt_frag_cache->page_nr_used == vm->page_size) { | |
| pt_frag_cache->page = 0; | |
| pt_frag_cache->page_nr_used = 0; | |
| } | |
| } | |
| return pt; | |
| } | |
| void virt_arch_pg_map(struct kvm_vm *vm, gva_t gva, gpa_t gpa) | |
| { | |
| gpa_t pt = vm->mmu.pgd; | |
| u64 *ptep, pte; | |
| int level; | |
| for (level = 1; level <= 3; level++) { | |
| u64 nls; | |
| u64 *pdep = virt_get_pte(vm, pt, gva, level, &nls); | |
| u64 pde = be64_to_cpu(*pdep); | |
| if (pde) { | |
| TEST_ASSERT((pde & PDE_VALID) && !(pde & PTE_LEAF), | |
| "Invalid PDE at level: %u gva: 0x%lx pde:0x%lx\n", | |
| level, gva, pde); | |
| pt = pde & PDE_PT_MASK; | |
| continue; | |
| } | |
| pt = __vm_alloc_pt(vm, nls + 3); | |
| pde = PDE_VALID | nls | pt; | |
| *pdep = cpu_to_be64(pde); | |
| } | |
| ptep = virt_get_pte(vm, pt, gva, level, NULL); | |
| pte = be64_to_cpu(*ptep); | |
| TEST_ASSERT(!pte, "PTE already present at level: %u gva: 0x%lx pte:0x%lx\n", | |
| level, gva, pte); | |
| pte = PTE_VALID | PTE_LEAF | PTE_REFERENCED | PTE_CHANGED | PTE_PRIV | | |
| PTE_READ | PTE_RW | PTE_EXEC | (gpa & PTE_PAGE_MASK); | |
| *ptep = cpu_to_be64(pte); | |
| } | |
| gpa_t addr_arch_gva2gpa(struct kvm_vm *vm, gva_t gva) | |
| { | |
| gpa_t pt = vm->mmu.pgd; | |
| u64 *ptep, pte; | |
| int level; | |
| for (level = 1; level <= 3; level++) { | |
| u64 nls; | |
| u64 *pdep = virt_get_pte(vm, pt, gva, level, &nls); | |
| u64 pde = be64_to_cpu(*pdep); | |
| TEST_ASSERT((pde & PDE_VALID) && !(pde & PTE_LEAF), | |
| "PDE not present at level: %u gva: 0x%lx pde:0x%lx\n", | |
| level, gva, pde); | |
| pt = pde & PDE_PT_MASK; | |
| } | |
| ptep = virt_get_pte(vm, pt, gva, level, NULL); | |
| pte = be64_to_cpu(*ptep); | |
| TEST_ASSERT(pte, | |
| "PTE not present at level: %u gva: 0x%lx pte:0x%lx\n", | |
| level, gva, pte); | |
| TEST_ASSERT((pte & PTE_VALID) && (pte & PTE_LEAF), | |
| "PTE not valid at level: %u gva: 0x%lx pte:0x%lx\n", | |
| level, gva, pte); | |
| return (pte & PTE_PAGE_MASK) + (gva & (vm->page_size - 1)); | |
| } | |
| static void virt_dump_pt(FILE *stream, struct kvm_vm *vm, gpa_t pt, | |
| gva_t va, int level, u8 indent) | |
| { | |
| int size, idx; | |
| size = 1U << (pt_shift(vm, level) + 3); | |
| for (idx = 0; idx < size; idx += 8, va += pt_entry_coverage(vm, level)) { | |
| u64 *page_table = addr_gpa2hva(vm, pt + idx); | |
| u64 pte = be64_to_cpu(*page_table); | |
| if (!(pte & PTE_VALID)) | |
| continue; | |
| if (pte & PTE_LEAF) { | |
| fprintf(stream, | |
| "%*s PTE[%d] gVA:0x%016lx -> gRA:0x%016llx\n", | |
| indent, "", idx / 8, va, pte & PTE_PAGE_MASK); | |
| } else { | |
| fprintf(stream, "%*sPDE%d[%d] gVA:0x%016lx\n", | |
| indent, "", level, idx / 8, va); | |
| virt_dump_pt(stream, vm, pte & PDE_PT_MASK, va, | |
| level + 1, indent + 2); | |
| } | |
| } | |
| } | |
| void virt_arch_dump(FILE *stream, struct kvm_vm *vm, u8 indent) | |
| { | |
| gpa_t pt = vm->mmu.pgd; | |
| if (!vm->mmu.pgd_created) | |
| return; | |
| virt_dump_pt(stream, vm, pt, 0, 1, indent); | |
| } | |
| static unsigned long get_r2(void) | |
| { | |
| unsigned long r2; | |
| asm("mr %0,%%r2" : "=r"(r2)); | |
| return r2; | |
| } | |
| /* | |
| * Under ELFv1 a function pointer is the address of a descriptor holding the | |
| * entry point and the TOC, not the entry point itself. ELFv2 dropped | |
| * descriptors, i.e. the pointer is already the entry point. Dereference the | |
| * descriptor as needed so that the guest starts executing code and not data. | |
| */ | |
| static uintptr_t function_entry(void *fn) | |
| { | |
| #if defined(_CALL_ELF) && _CALL_ELF == 2 | |
| return (uintptr_t)fn; | |
| #else | |
| return ((const u64 *)fn)[0]; | |
| #endif | |
| } | |
| void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) | |
| { | |
| uintptr_t entry = function_entry(guest_code); | |
| struct kvm_regs regs; | |
| vcpu_regs_get(vcpu, ®s); | |
| regs.pc = entry; | |
| regs.gpr[12] = entry; | |
| vcpu_regs_set(vcpu, ®s); | |
| } | |
| struct kvm_vcpu *vm_arch_vcpu_add(struct kvm_vm *vm, u32 vcpu_id) | |
| { | |
| const size_t stack_size = SZ_64K; | |
| gva_t stack_vaddr, ex_regs_vaddr; | |
| gpa_t ex_regs_paddr; | |
| struct ex_regs *ex_regs; | |
| struct kvm_regs regs; | |
| struct kvm_vcpu *vcpu; | |
| u64 lpcr; | |
| stack_vaddr = __vm_alloc(vm, stack_size, | |
| DEFAULT_GUEST_STACK_VADDR_MIN, | |
| MEM_REGION_DATA); | |
| ex_regs_vaddr = __vm_alloc(vm, stack_size, | |
| DEFAULT_GUEST_STACK_VADDR_MIN, | |
| MEM_REGION_DATA); | |
| ex_regs_paddr = addr_gva2gpa(vm, ex_regs_vaddr); | |
| ex_regs = addr_gpa2hva(vm, ex_regs_paddr); | |
| ex_regs->gva = ex_regs_vaddr; | |
| vcpu = __vm_vcpu_add(vm, vcpu_id); | |
| vcpu_enable_cap(vcpu, KVM_CAP_PPC_PAPR, 1); | |
| /* Setup guest registers */ | |
| vcpu_regs_get(vcpu, ®s); | |
| lpcr = vcpu_get_reg(vcpu, KVM_REG_PPC_LPCR_64); | |
| regs.gpr[1] = stack_vaddr + stack_size - 256; | |
| regs.gpr[2] = (uintptr_t)get_r2(); | |
| regs.gpr[13] = (uintptr_t)ex_regs_paddr; | |
| regs.msr = MSR_SF | MSR_VEC | MSR_VSX | MSR_FP | | |
| MSR_ME | MSR_IR | MSR_DR | MSR_RI; | |
| if (BYTE_ORDER == LITTLE_ENDIAN) { | |
| regs.msr |= MSR_LE; | |
| lpcr |= LPCR_ILE; | |
| } else { | |
| lpcr &= ~LPCR_ILE; | |
| } | |
| vcpu_regs_set(vcpu, ®s); | |
| vcpu_set_reg(vcpu, KVM_REG_PPC_LPCR_64, lpcr); | |
| return vcpu; | |
| } | |
| void vcpu_args_set(struct kvm_vcpu *vcpu, unsigned int num, ...) | |
| { | |
| va_list ap; | |
| struct kvm_regs regs; | |
| int i; | |
| TEST_ASSERT(num >= 1 && num <= 5, "Unsupported number of args: %u\n", | |
| num); | |
| va_start(ap, num); | |
| vcpu_regs_get(vcpu, ®s); | |
| for (i = 0; i < num; i++) | |
| regs.gpr[i + 3] = va_arg(ap, u64); | |
| vcpu_regs_set(vcpu, ®s); | |
| va_end(ap); | |
| } | |
| void vcpu_arch_dump(FILE *stream, struct kvm_vcpu *vcpu, u8 indent) | |
| { | |
| struct kvm_regs regs; | |
| vcpu_regs_get(vcpu, ®s); | |
| fprintf(stream, "%*sNIA: 0x%016llx MSR: 0x%016llx\n", | |
| indent, "", regs.pc, regs.msr); | |
| fprintf(stream, "%*sLR: 0x%016llx CTR :0x%016llx\n", | |
| indent, "", regs.lr, regs.ctr); | |
| fprintf(stream, "%*sCR: 0x%08llx XER :0x%016llx\n", | |
| indent, "", regs.cr, regs.xer); | |
| } | |
| void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus) | |
| { | |
| size_t excp_size = __interrupts_end - __interrupts_start; | |
| size_t excp_pages = (excp_size + vm->page_size - 1) / vm->page_size; | |
| gpa_t excp_paddr; | |
| void *mem; | |
| /* | |
| * Guest interrupts are taken in real-mode, i.e. the vectors must live | |
| * at gPA 0. Use the innermost allocator to override the minimum GPA | |
| * that MEM_REGION_DATA would otherwise impose. | |
| */ | |
| excp_paddr = ____vm_phy_pages_alloc(vm, excp_pages, 0, | |
| vm->memslots[MEM_REGION_DATA], | |
| vm_arch_has_protected_memory(vm), | |
| false); | |
| TEST_ASSERT(excp_paddr == 0, | |
| "Interrupt vectors not allocated at gPA address 0: (0x%lx)", | |
| excp_paddr); | |
| mem = addr_gpa2hva(vm, excp_paddr); | |
| memcpy(mem, __interrupts_start, excp_size); | |
| } | |
| void assert_on_unhandled_exception(struct kvm_vcpu *vcpu) | |
| { | |
| struct ucall uc; | |
| if (get_ucall(vcpu, &uc) == UCALL_UNHANDLED) { | |
| gpa_t ex_regs_paddr; | |
| struct ex_regs *ex_regs; | |
| struct kvm_regs regs; | |
| vcpu_regs_get(vcpu, ®s); | |
| ex_regs_paddr = (gpa_t)regs.gpr[13]; | |
| ex_regs = addr_gpa2hva(vcpu->vm, ex_regs_paddr); | |
| TEST_FAIL("Unexpected interrupt in guest NIA:0x%016lx MSR:0x%016lx TRAP:0x%04x", | |
| ex_regs->nia, ex_regs->msr, ex_regs->trap); | |
| } | |
| } | |
| struct handler { | |
| void (*fn)(struct ex_regs *regs); | |
| int trap; | |
| }; | |
| #define NR_HANDLERS 10 | |
| static struct handler handlers[NR_HANDLERS]; | |
| void route_interrupt(struct ex_regs *regs) | |
| { | |
| int i; | |
| for (i = 0; i < NR_HANDLERS; i++) { | |
| if (handlers[i].trap == regs->trap) { | |
| handlers[i].fn(regs); | |
| return; | |
| } | |
| } | |
| ucall(UCALL_UNHANDLED, 0); | |
| } | |
| void vm_install_exception_handler(struct kvm_vm *vm, int trap, | |
| void (*fn)(struct ex_regs *)) | |
| { | |
| int i; | |
| for (i = 0; i < NR_HANDLERS; i++) { | |
| if (!handlers[i].trap || handlers[i].trap == trap) { | |
| if (fn == NULL) | |
| trap = 0; /* Clear handler */ | |
| handlers[i].trap = trap; | |
| handlers[i].fn = fn; | |
| sync_global_to_guest(vm, handlers[i]); | |
| return; | |
| } | |
| } | |
| TEST_FAIL("Out of exception handlers"); | |
| } | |
| void kvm_selftest_arch_init(void) | |
| { | |
| TEST_REQUIRE(kvm_has_cap(KVM_CAP_PPC_MMU_RADIX)); | |
| /* | |
| * powerpc default mode is set by host page size and not static, | |
| * so start by computing that early. | |
| */ | |
| guest_modes_append_default(); | |
| } |