diff --git a/target/ppc/Makefile.objs b/target/ppc/Makefile.objs index f963777277..f92ba67ebd 100644 --- a/target/ppc/Makefile.objs +++ b/target/ppc/Makefile.objs @@ -4,6 +4,7 @@ obj-y += translate.o ifeq ($(CONFIG_SOFTMMU),y) obj-y += machine.o mmu_helper.o mmu-hash32.o monitor.o arch_dump.o obj-$(TARGET_PPC64) += mmu-hash64.o mmu-book3s-v3.o compat.o +obj-$(TARGET_PPC64) += mmu-radix64.o endif obj-$(CONFIG_KVM) += kvm.o obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o diff --git a/target/ppc/cpu.h b/target/ppc/cpu.h index ece535d611..c0f63f6436 100644 --- a/target/ppc/cpu.h +++ b/target/ppc/cpu.h @@ -482,6 +482,8 @@ struct ppc_slb_t { #define DSISR_ISSTORE 0x02000000 /* Not permitted by virtual page class key protection */ #define DSISR_AMR 0x00200000 +/* Unsupported Radix Tree Configuration */ +#define DSISR_R_BADCONFIG 0x00080000 /* SRR1 error code fields */ diff --git a/target/ppc/mmu-book3s-v3.c b/target/ppc/mmu-book3s-v3.c index 005c96340a..e7798b3582 100644 --- a/target/ppc/mmu-book3s-v3.c +++ b/target/ppc/mmu-book3s-v3.c @@ -22,15 +22,13 @@ #include "cpu.h" #include "mmu-hash64.h" #include "mmu-book3s-v3.h" -#include "qemu/error-report.h" +#include "mmu-radix64.h" int ppc64_v3_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx, int mmu_idx) { if (ppc64_radix_guest(cpu)) { /* Guest uses radix */ - /* TODO - Unsupported */ - error_report("Guest Radix Support Unimplemented"); - exit(1); + return ppc_radix64_handle_mmu_fault(cpu, eaddr, rwx, mmu_idx); } else { /* Guest uses hash */ return ppc_hash64_handle_mmu_fault(cpu, eaddr, rwx, mmu_idx); } diff --git a/target/ppc/mmu-book3s-v3.h b/target/ppc/mmu-book3s-v3.h index 636f6ab95f..56095dab52 100644 --- a/target/ppc/mmu-book3s-v3.h +++ b/target/ppc/mmu-book3s-v3.h @@ -25,6 +25,11 @@ /* Partition Table Entry Fields */ #define PATBE1_GR 0x8000000000000000 +/* Process Table Entry */ +struct prtb_entry { + uint64_t prtbe0, prtbe1; +}; + #ifdef TARGET_PPC64 static inline bool ppc64_use_proc_tbl(PowerPCCPU *cpu) diff --git a/target/ppc/mmu-radix64.c b/target/ppc/mmu-radix64.c new file mode 100644 index 0000000000..de18c0b69e --- /dev/null +++ b/target/ppc/mmu-radix64.c @@ -0,0 +1,259 @@ +/* + * PowerPC Radix MMU mulation helpers for QEMU. + * + * Copyright (c) 2016 Suraj Jitindar Singh, IBM Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "cpu.h" +#include "exec/exec-all.h" +#include "exec/helper-proto.h" +#include "qemu/error-report.h" +#include "sysemu/kvm.h" +#include "kvm_ppc.h" +#include "exec/log.h" +#include "mmu-radix64.h" +#include "mmu-book3s-v3.h" + +static bool ppc_radix64_get_fully_qualified_addr(CPUPPCState *env, vaddr eaddr, + uint64_t *lpid, uint64_t *pid) +{ + /* We don't have HV support yet and shouldn't get here with it set anyway */ + assert(!msr_hv); + + if (!msr_hv) { /* !MSR[HV] -> Guest */ + switch (eaddr & R_EADDR_QUADRANT) { + case R_EADDR_QUADRANT0: /* Guest application */ + *lpid = env->spr[SPR_LPIDR]; + *pid = env->spr[SPR_BOOKS_PID]; + break; + case R_EADDR_QUADRANT1: /* Illegal */ + case R_EADDR_QUADRANT2: + return false; + case R_EADDR_QUADRANT3: /* Guest OS */ + *lpid = env->spr[SPR_LPIDR]; + *pid = 0; /* pid set to 0 -> addresses guest operating system */ + break; + } + } + + return true; +} + +static void ppc_radix64_raise_segi(PowerPCCPU *cpu, int rwx, vaddr eaddr) +{ + CPUState *cs = CPU(cpu); + CPUPPCState *env = &cpu->env; + + if (rwx == 2) { /* Instruction Segment Interrupt */ + cs->exception_index = POWERPC_EXCP_ISEG; + } else { /* Data Segment Interrupt */ + cs->exception_index = POWERPC_EXCP_DSEG; + env->spr[SPR_DAR] = eaddr; + } + env->error_code = 0; +} + +static void ppc_radix64_raise_si(PowerPCCPU *cpu, int rwx, vaddr eaddr, + uint32_t cause) +{ + CPUState *cs = CPU(cpu); + CPUPPCState *env = &cpu->env; + + if (rwx == 2) { /* Instruction Storage Interrupt */ + cs->exception_index = POWERPC_EXCP_ISI; + env->error_code = cause; + } else { /* Data Storage Interrupt */ + cs->exception_index = POWERPC_EXCP_DSI; + if (rwx == 1) { /* Write -> Store */ + cause |= DSISR_ISSTORE; + } + env->spr[SPR_DSISR] = cause; + env->spr[SPR_DAR] = eaddr; + env->error_code = 0; + } +} + + +static bool ppc_radix64_check_prot(PowerPCCPU *cpu, int rwx, uint64_t pte, + int *fault_cause, int *prot) +{ + CPUPPCState *env = &cpu->env; + const int need_prot[] = { PAGE_READ, PAGE_WRITE, PAGE_EXEC }; + + /* Check Page Attributes (pte58:59) */ + if (((pte & R_PTE_ATT) == R_PTE_ATT_NI_IO) && (rwx == 2)) { + /* + * Radix PTE entries with the non-idempotent I/O attribute are treated + * as guarded storage + */ + *fault_cause |= SRR1_NOEXEC_GUARD; + return true; + } + + /* Determine permissions allowed by Encoded Access Authority */ + if ((pte & R_PTE_EAA_PRIV) && msr_pr) { /* Insufficient Privilege */ + *prot = 0; + } else if (msr_pr || (pte & R_PTE_EAA_PRIV)) { + *prot = ppc_radix64_get_prot_eaa(pte); + } else { /* !msr_pr && !(pte & R_PTE_EAA_PRIV) */ + *prot = ppc_radix64_get_prot_eaa(pte); + *prot &= ppc_radix64_get_prot_amr(cpu); /* Least combined permissions */ + } + + /* Check if requested access type is allowed */ + if (need_prot[rwx] & ~(*prot)) { /* Page Protected for that Access */ + *fault_cause |= DSISR_PROTFAULT; + return true; + } + + return false; +} + +static void ppc_radix64_set_rc(PowerPCCPU *cpu, int rwx, uint64_t pte, + hwaddr pte_addr, int *prot) +{ + CPUState *cs = CPU(cpu); + uint64_t npte; + + npte = pte | R_PTE_R; /* Always set reference bit */ + + if (rwx == 1) { /* Store/Write */ + npte |= R_PTE_C; /* Set change bit */ + } else { + /* + * Treat the page as read-only for now, so that a later write + * will pass through this function again to set the C bit. + */ + *prot &= ~PAGE_WRITE; + } + + if (pte ^ npte) { /* If pte has changed then write it back */ + stq_phys(cs->as, pte_addr, npte); + } +} + +static uint64_t ppc_radix64_walk_tree(PowerPCCPU *cpu, int rwx, vaddr eaddr, + uint64_t base_addr, uint64_t nls, + hwaddr *raddr, int *psize, + int *fault_cause, int *prot, + hwaddr *pte_addr) +{ + CPUState *cs = CPU(cpu); + uint64_t index, pde; + + if (nls < 5) { /* Directory maps less than 2**5 entries */ + *fault_cause |= DSISR_R_BADCONFIG; + return 0; + } + + /* Read page entry from guest address space */ + index = eaddr >> (*psize - nls); /* Shift */ + index &= ((1UL << nls) - 1); /* Mask */ + pde = ldq_phys(cs->as, base_addr + (index * sizeof(pde))); + if (!(pde & R_PTE_VALID)) { /* Invalid Entry */ + *fault_cause |= DSISR_NOPTE; + return 0; + } + + *psize -= nls; + + /* Check if Leaf Entry -> Page Table Entry -> Stop the Search */ + if (pde & R_PTE_LEAF) { + uint64_t rpn = pde & R_PTE_RPN; + uint64_t mask = (1UL << *psize) - 1; + + if (ppc_radix64_check_prot(cpu, rwx, pde, fault_cause, prot)) { + return 0; /* Protection Denied Access */ + } + + /* Or high bits of rpn and low bits to ea to form whole real addr */ + *raddr = (rpn & ~mask) | (eaddr & mask); + *pte_addr = base_addr + (index * sizeof(pde)); + return pde; + } + + /* Next Level of Radix Tree */ + return ppc_radix64_walk_tree(cpu, rwx, eaddr, pde & R_PDE_NLB, + pde & R_PDE_NLS, raddr, psize, + fault_cause, prot, pte_addr); +} + +int ppc_radix64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx, + int mmu_idx) +{ + CPUState *cs = CPU(cpu); + CPUPPCState *env = &cpu->env; + PPCVirtualHypervisorClass *vhc = + PPC_VIRTUAL_HYPERVISOR_GET_CLASS(cpu->vhyp); + hwaddr raddr, pte_addr; + uint64_t lpid = 0, pid = 0, offset, size, patbe, prtbe0, pte; + int page_size, prot, fault_cause = 0; + + assert((rwx == 0) || (rwx == 1) || (rwx == 2)); + assert(!msr_hv); /* For now there is no Radix PowerNV Support */ + assert(cpu->vhyp); + assert(ppc64_use_proc_tbl(cpu)); + + /* Real Mode Access */ + if (((rwx == 2) && (msr_ir == 0)) || ((rwx != 2) && (msr_dr == 0))) { + /* In real mode top 4 effective addr bits (mostly) ignored */ + raddr = eaddr & 0x0FFFFFFFFFFFFFFFULL; + + tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK, + PAGE_READ | PAGE_WRITE | PAGE_EXEC, mmu_idx, + TARGET_PAGE_SIZE); + return 0; + } + + /* Virtual Mode Access - get the fully qualified address */ + if (!ppc_radix64_get_fully_qualified_addr(env, eaddr, &lpid, &pid)) { + ppc_radix64_raise_segi(cpu, rwx, eaddr); + return 1; + } + + /* Get Process Table */ + patbe = vhc->get_patbe(cpu->vhyp); + + /* Index Process Table by PID to Find Corresponding Process Table Entry */ + offset = pid * sizeof(struct prtb_entry); + size = 1ULL << ((patbe & PATBE1_R_PRTS) + 12); + if (offset >= size) { + /* offset exceeds size of the process table */ + ppc_radix64_raise_si(cpu, rwx, eaddr, DSISR_NOPTE); + return 1; + } + prtbe0 = ldq_phys(cs->as, (patbe & PATBE1_R_PRTB) + offset); + + /* Walk Radix Tree from Process Table Entry to Convert EA to RA */ + page_size = PRTBE_R_GET_RTS(prtbe0); + pte = ppc_radix64_walk_tree(cpu, rwx, eaddr & R_EADDR_MASK, + prtbe0 & PRTBE_R_RPDB, prtbe0 & PRTBE_R_RPDS, + &raddr, &page_size, &fault_cause, &prot, + &pte_addr); + if (!pte) { + ppc_radix64_raise_si(cpu, rwx, eaddr, fault_cause); + return 1; + } + + /* Update Reference and Change Bits */ + ppc_radix64_set_rc(cpu, rwx, pte, pte_addr, &prot); + + tlb_set_page(cs, eaddr & TARGET_PAGE_MASK, raddr & TARGET_PAGE_MASK, + prot, mmu_idx, 1UL << page_size); + return 1; +} diff --git a/target/ppc/mmu-radix64.h b/target/ppc/mmu-radix64.h new file mode 100644 index 0000000000..1d5c7cfea5 --- /dev/null +++ b/target/ppc/mmu-radix64.h @@ -0,0 +1,72 @@ +#ifndef MMU_RADIX64_H +#define MMU_RADIX64_H + +#ifndef CONFIG_USER_ONLY + +/* Radix Quadrants */ +#define R_EADDR_MASK 0x3FFFFFFFFFFFFFFF +#define R_EADDR_QUADRANT 0xC000000000000000 +#define R_EADDR_QUADRANT0 0x0000000000000000 +#define R_EADDR_QUADRANT1 0x4000000000000000 +#define R_EADDR_QUADRANT2 0x8000000000000000 +#define R_EADDR_QUADRANT3 0xC000000000000000 + +/* Radix Partition Table Entry Fields */ +#define PATBE1_R_PRTB 0x0FFFFFFFFFFFF000 +#define PATBE1_R_PRTS 0x000000000000001F + +/* Radix Process Table Entry Fields */ +#define PRTBE_R_GET_RTS(rts) \ + ((((rts >> 58) & 0x18) | ((rts >> 5) & 0x7)) + 31) +#define PRTBE_R_RPDB 0x0FFFFFFFFFFFFF00 +#define PRTBE_R_RPDS 0x000000000000001F + +/* Radix Page Directory/Table Entry Fields */ +#define R_PTE_VALID 0x8000000000000000 +#define R_PTE_LEAF 0x4000000000000000 +#define R_PTE_SW0 0x2000000000000000 +#define R_PTE_RPN 0x01FFFFFFFFFFF000 +#define R_PTE_SW1 0x0000000000000E00 +#define R_GET_SW(sw) (((sw >> 58) & 0x8) | ((sw >> 9) & 0x7)) +#define R_PTE_R 0x0000000000000100 +#define R_PTE_C 0x0000000000000080 +#define R_PTE_ATT 0x0000000000000030 +#define R_PTE_ATT_NORMAL 0x0000000000000000 +#define R_PTE_ATT_SAO 0x0000000000000010 +#define R_PTE_ATT_NI_IO 0x0000000000000020 +#define R_PTE_ATT_TOLERANT_IO 0x0000000000000030 +#define R_PTE_EAA_PRIV 0x0000000000000008 +#define R_PTE_EAA_R 0x0000000000000004 +#define R_PTE_EAA_RW 0x0000000000000002 +#define R_PTE_EAA_X 0x0000000000000001 +#define R_PDE_NLB PRTBE_R_RPDB +#define R_PDE_NLS PRTBE_R_RPDS + +#ifdef TARGET_PPC64 + +int ppc_radix64_handle_mmu_fault(PowerPCCPU *cpu, vaddr eaddr, int rwx, + int mmu_idx); + +static inline int ppc_radix64_get_prot_eaa(uint64_t pte) +{ + return (pte & R_PTE_EAA_R ? PAGE_READ : 0) | + (pte & R_PTE_EAA_RW ? PAGE_READ | PAGE_WRITE : 0) | + (pte & R_PTE_EAA_X ? PAGE_EXEC : 0); +} + +static inline int ppc_radix64_get_prot_amr(PowerPCCPU *cpu) +{ + CPUPPCState *env = &cpu->env; + int amr = env->spr[SPR_AMR] >> 62; /* We only care about key0 AMR63:62 */ + int iamr = env->spr[SPR_IAMR] >> 62; /* We only care about key0 IAMR63:62 */ + + return (amr & 0x2 ? 0 : PAGE_WRITE) | /* Access denied if bit is set */ + (amr & 0x1 ? 0 : PAGE_READ) | + (iamr & 0x1 ? 0 : PAGE_EXEC); +} + +#endif /* TARGET_PPC64 */ + +#endif /* CONFIG_USER_ONLY */ + +#endif /* MMU_RADIX64_H */