On Mon, 2 May 2011 17:03:21 +0200 Alexander Graf <ag...@suse.de> wrote:
> Most of the code to support e500 style MMUs is already in place, but > we're missing on some of the special TLB0-TLB1 handling code and slightly > different TLB modification. > > This patch adds support for the FSL style MMU. > > Signed-off-by: Alexander Graf <ag...@suse.de> You beat me to (part of) it... :-) How is this going to interact with the KVM MMU API, where it was suggested that qemu use that representation as its native structure? Should we just change the representation at that point, for both types of booke mmu (so 4xx would be represented with MAS registers internally, even though a different interface is exposed to the guest)? > @@ -678,8 +887,9 @@ struct CPUPPCState { > int nb_BATs; > target_ulong DBAT[2][8]; > target_ulong IBAT[2][8]; > - /* PowerPC TLB registers (for 4xx and 60x software driven TLBs) */ > + /* PowerPC TLB registers (for 4xx, e500 and 60x software driven TLBs) */ > int nb_tlb; /* Total number of TLB > */ > + int nb_tlbs[4]; /* Number of respective TLB entries (e500) > */ Looks like "number of tlbs", not "number of tlb entries"... Was less confusing when there was only one tlb array. > diff --git a/target-ppc/helper.c b/target-ppc/helper.c > index 5e4030b..261c1a9 100644 > --- a/target-ppc/helper.c > +++ b/target-ppc/helper.c > @@ -1153,48 +1153,144 @@ void store_40x_sler (CPUPPCState *env, uint32_t val) > env->spr[SPR_405_SLER] = val; > } > > +static inline int mmubooke_check_tlb (CPUState *env, ppcemb_tlb_t *tlb, > + target_phys_addr_t *raddr, int *prot, > + target_ulong address, int rw, > + int access_type, int i) > +{ > + int ret, _prot; > + > + if (ppcemb_tlb_check(env, tlb, raddr, address, > + env->spr[SPR_BOOKE_PID], > + !env->nb_pids, i) >= 0) { > + goto found_tlb; > + } > + > + if ((env->nb_pids > 1) && env->spr[SPR_BOOKE_PID1] && > + ppcemb_tlb_check(env, tlb, raddr, address, > + env->spr[SPR_BOOKE_PID1], 0, i) >= 0) { > + goto found_tlb; > + } > + > + if ((env->nb_pids > 2) && env->spr[SPR_BOOKE_PID2] && > + ppcemb_tlb_check(env, tlb, raddr, address, > + env->spr[SPR_BOOKE_PID2], 0, i) >= 0) { > + goto found_tlb; > + } Maybe PID1/PID2 should be moved into ppcemb_tlb_check, and eliminate the nb_pids checks by putting zero in PID1/PID2 on chips that don't have it? I'm assuming this is performance sensitive for non-KVM. > +static int mmue500_get_physical_address (CPUState *env, mmu_ctx_t *ctx, > + target_ulong address, int rw, > + int access_type) > +{ > + ppcemb_tlb_t *tlb; > + target_phys_addr_t raddr; > + int i, ret; > + uint32_t ea = (address >> MAS2_EPN_SHIFT) & 0x7f; > + > + ret = -1; > + raddr = (target_phys_addr_t)-1ULL; > + > + /* check TLB1 - hits often because the kernel is here */ I'd optimize for userspace instead -- that's where the real work is more likely to happen. Plus, compare what's likely to be 5-6 iterations before finding the entry for a kernel large-page entry if we check TLB0 first, versus 17+ iterations (65+ on e500mc) for finding a TLB0 entry if TLB1 is checked first -- based on Linux's TLB1 usage patterns. > + for (i = env->nb_tlbs[0]; i < env->nb_tlb; i++) { > + tlb = &env->tlb[i].tlbe; > + ret = mmubooke_check_tlb(env, tlb, &raddr, &ctx->prot, address, rw, > + access_type, i); > + if (!ret) { > + goto found_tlb; > } > } > - if (ret >= 0) > + > + /* check possible TLB0 entries */ > + for (i = 0; i < env->nb_ways; i++) { > + tlb = &env->tlb[ea | (i << 7)].tlbe; Do we have to hardcode 7 (and 0x7f) here? And if possible I'd rearrange the tlb so that ways within a set are contiguous. Better for cache utilization, and is what the KVM MMU API will want. > @@ -1348,6 +1446,44 @@ target_phys_addr_t cpu_get_phys_page_debug (CPUState > *env, target_ulong addr) > return ctx.raddr & TARGET_PAGE_MASK; > } > > +static void e500_update_mas_tlb_miss(CPUState *env, target_ulong address, > + int rw) > +{ > + env->spr[SPR_BOOKE_MAS0] = env->spr[SPR_BOOKE_MAS4] & MAS4_TLBSELD_MASK; > + env->spr[SPR_BOOKE_MAS1] = env->spr[SPR_BOOKE_MAS4] & MAS4_TSIZED_MASK; > + env->spr[SPR_BOOKE_MAS2] = env->spr[SPR_BOOKE_MAS4] & MAS4_WIMGED_MASK; > + env->spr[SPR_BOOKE_MAS6] = 0; > + > + /* AS */ > + if (((rw == 2) && msr_ir) || ((rw != 2) && msr_dr)) { > + env->spr[SPR_BOOKE_MAS1] |= MAS1_TS; > + env->spr[SPR_BOOKE_MAS6] |= MAS6_SAS; > + } > + > + env->spr[SPR_BOOKE_MAS1] |= MAS1_VALID; > + env->spr[SPR_BOOKE_MAS2] |= address & MAS2_EPN_MASK; > + > + switch (env->spr[SPR_BOOKE_MAS4] & MAS4_TIDSELD_PIDZ) { > + case MAS4_TIDSELD_PID0: > + env->spr[SPR_BOOKE_MAS1] |= env->spr[SPR_BOOKE_PID] << > MAS1_TID_SHIFT; > + break; > + case MAS4_TIDSELD_PID1: > + env->spr[SPR_BOOKE_MAS1] |= env->spr[SPR_BOOKE_PID1] << > MAS1_TID_SHIFT; > + break; > + case MAS4_TIDSELD_PID2: > + env->spr[SPR_BOOKE_MAS1] |= env->spr[SPR_BOOKE_PID2] << > MAS1_TID_SHIFT; > + break; > + } > + > + env->spr[SPR_BOOKE_MAS6] |= env->spr[SPR_BOOKE_PID] << 16; > + > + /* next victim logic */ > + env->spr[SPR_BOOKE_MAS0] |= env->last_way << MAS0_ESEL_SHIFT; > + env->last_way++; > + env->last_way &= 3; > + env->spr[SPR_BOOKE_MAS0] |= env->last_way << MAS0_NV_SHIFT; > +} MAS3/7 should be zeroed according to the architecture. Better victim selection would check for empty ways, and perhaps keep last_way on a per-set basis. > @@ -1820,12 +1956,8 @@ void ppc_tlb_invalidate_all (CPUPPCState *env) > cpu_abort(env, "MPC8xx MMU model is not implemented\n"); > break; > case POWERPC_MMU_BOOKE: > - tlb_flush(env, 1); > - break; > case POWERPC_MMU_BOOKE_FSL: > - /* XXX: TODO */ > - if (!kvm_enabled()) > - cpu_abort(env, "BookE MMU model is not implemented\n"); > + tlb_flush(env, 1); > break; > case POWERPC_MMU_32B: > case POWERPC_MMU_601: This flushes env->tlb_table, but don't we still need to clear out env->tlb? > diff --git a/target-ppc/op_helper.c b/target-ppc/op_helper.c > index d5db484..3c0b061 100644 > --- a/target-ppc/op_helper.c > +++ b/target-ppc/op_helper.c > @@ -4206,4 +4206,422 @@ target_ulong helper_440_tlbsx (target_ulong address) > return ppcemb_tlb_search(env, address, env->spr[SPR_440_MMUCR] & 0xFF); > } > > +/* PowerPC e500 TLB management */ > + > +static inline int e500_tlb_num(CPUState *env, ppcemb_tlb_t *tlb) > +{ > + ulong tlbel = (ulong)tlb; > + ulong tlbl = (ulong)env->tlb; > + > + return (tlbel - tlbl) / sizeof(env->tlb[0]); > +} > + > +static inline ppcemb_tlb_t *e500_cur_tlb(CPUState *env) This is a bit big to be inlined - let the compiler decide such things. > +{ > + uint32_t tlbncfg = 0; > + int esel = (env->spr[SPR_BOOKE_MAS0] & MAS0_ESEL_MASK) >> > MAS0_ESEL_SHIFT; > + int ea = (env->spr[SPR_BOOKE_MAS2] & MAS2_EPN_MASK) >> MAS2_EPN_SHIFT; > + int tlb; > + int r; > + > + tlb = (env->spr[SPR_BOOKE_MAS0] & MAS0_TLBSEL_MASK) >> MAS0_TLBSEL_SHIFT; > + tlbncfg = env->spr[SPR_BOOKE_TLB0CFG + tlb]; > + > + if ((tlbncfg & TLBnCFG_HES) && (env->spr[SPR_BOOKE_MAS0] & MAS0_HES)) { > + cpu_abort(env, "we don't support HES yet\n"); > + } > + > + if (tlb == 1) { > + r = env->nb_tlbs[0] + (esel % env->nb_tlbs[1]); Ouch, division by non-constant. There's no need for it anyway; esel >= env_nb_tlbs[1] is undefined behavior. Just crop the value arbitrarily (or error out, if possible) to avoid dereferencing a bad array index. > + } else { > + esel &= env->nb_ways - 1; > + esel <<= 7; > + ea &= 0x7f; > + r = (esel | ea) % env->nb_tlbs[0]; More unnecessary division -- use "& (env->nb_tlbs[0] - 1)", or variable-ize the 7 and 0x7f so that we know that we won't produce an out-of-bounds value. > +static const target_phys_addr_t e500_tlb_sizes[] = { > + 0, > + 4 * 1024, > + 16 * 1024, > + 64 * 1024, > + 256 * 1024, > + 1024 * 1024, > + 4 * 1024 * 1024, > + 16 * 1024 * 1024, > + 64 * 1024 * 1024, > + 256 * 1024 * 1024, > + 1024 * 1024 * 1024, > + (target_phys_addr_t)(4ULL * 1024ULL * 1024ULL * 1024ULL), > +}; FWIW, power-of-2 page sizes are architected, and may appear in future e500-family chips. The TSIZE field is extended by one bit on the right (previously reserved and should-be-zero). > +static inline target_phys_addr_t e500_tlb_to_page_size(int size) unsigned int > +{ > + target_phys_addr_t r; > + > + if (size > 11) { if (size >= ARRAY_SIZE(e500_tlb_sizes)) { > + /* should not happen */ > + r = 1024 * 1024 * 1024; Error message? + switch (env->spr[SPR_BOOKE_MAS0] & MAS0_WQ_MASK) { + case MAS0_WQ_ALWAYS: + /* good to go, write that entry */ + break; + case MAS0_WQ_COND: + /* XXX check if reserved */ + if (0) { + return; + } + break; + case MAS0_WQ_CLR_RSRV: + /* XXX clear entry */ + return; + default: + /* no idea what to do */ + return; + } If the plan is to support such things, might want a more generic name than "e500" -- we're really talking about isa 2.06 book3e. The first chip that implements this stuff will probably not be from Freescale, much less called "e500". :-) > + if (msr_gs) { > + cpu_abort(env, "missing HV implementation\n"); What's the policy on aborting versus error-message versus no-op if the guest invokes undefined/unimplemented behavior? > + } else { > + rpn = ((uint64_t)env->spr[SPR_BOOKE_MAS7] << 32) | > + (env->spr[SPR_BOOKE_MAS3] & 0xfffff000); > + } Not sure why this is in an else-branch versus msr_gs. > + tlb->RPN = rpn; > + > + tlb->PID = (env->spr[SPR_BOOKE_MAS1] & MAS1_TID_MASK) >> MAS1_TID_SHIFT; > + tlb->size = e500_tlb_to_page_size((env->spr[SPR_BOOKE_MAS1] > + & MAS1_TSIZE_MASK) >> MAS1_TSIZE_SHIFT); e500 manuals document that tsize is ignored in tlb0. > + tlb->EPN = (uint32_t)(env->spr[SPR_BOOKE_MAS2] & MAS2_EPN_MASK); > + tlb->attr = env->spr[SPR_BOOKE_MAS2] & (MAS2_ACM | MAS2_VLE | MAS2_W | > + MAS2_I | MAS2_M | MAS2_G | > MAS2_E) > + << 1; > + tlb->attr |= env->spr[SPR_BOOKE_MAS1] & MAS1_IPROT; > + tlb->attr |= (env->spr[SPR_BOOKE_MAS3] & > + ((MAS3_U0 | MAS3_U1 | MAS3_U2 | MAS3_U3)) << 8); Might be nice to #define the encoding of these bits into attr/etc, versus magic shifts (though it's moot if we're going to switch to a MAS-based representation). > + if (tlb->size == TARGET_PAGE_SIZE) { > + tlb_flush_page(env, tlb->EPN); > + } else { > + tlb_flush(env, 1); > + } Need to check whether the previous entry was a large page needing a full flush. > +void helper_e500_tlbre(void) > +{ > + ppcemb_tlb_t *tlb = NULL; > + > + tlb = e500_cur_tlb(env); > + e500_tlb_to_mas(env, tlb, e500_tlb_num(env, tlb)); > +} > + > +/* Generic TLB check function for embedded PowerPC implementations */ > +static inline int ppcemb_tlb_check(CPUState *env, ppcemb_tlb_t *tlb, > + target_phys_addr_t *raddrp, > + target_ulong address, uint32_t pid, int > ext, > + int i) Why is this both here and in helper.c? > +void helper_e500_tlbsx(target_ulong address_hi, target_ulong address_lo) > +{ > + ppcemb_tlb_t *tlb = NULL; > + int i; > + target_phys_addr_t raddr; > + uint32_t spid, sas; > + uint32_t ea = (address_lo >> MAS2_EPN_SHIFT) & 0x7f; > + > + spid = (env->spr[SPR_BOOKE_MAS6] & MAS6_SPID_MASK) >> MAS6_SPID_SHIFT; > + sas = env->spr[SPR_BOOKE_MAS6] & MAS6_SAS; > + > + /* check possible TLB0 entries */ > + for (i = 0; i < env->nb_ways; i++) { > + tlb = &env->tlb[ea | (i << 7)].tlbe; > + > + if (ppcemb_tlb_check(env, tlb, &raddr, address_lo, spid, 0, i) < 0) { > + continue; > + } > + > + if (sas != (tlb->attr & MAS6_SAS)) { > + continue; > + } > + > + e500_tlb_to_mas(env, tlb, ea | (i << 7)); > + return; > + } > + > + /* check TLB1 */ > + for (i = env->nb_tlbs[0]; i < (env->nb_tlbs[0] + env->nb_tlbs[1]); i++) { > + tlb = &env->tlb[i].tlbe; > + > + if (ppcemb_tlb_check(env, tlb, &raddr, address_lo, spid, 0, i) < 0) { > + continue; > + } > + > + if (sas != (tlb->attr & MAS6_SAS)) { > + continue; > + } > + > + e500_tlb_to_mas(env, tlb, i); > + return; > + } This has a lot of overlap with mmue500_get_physical_address()... > + > + /* no entry found, fill with defaults */ > + env->spr[SPR_BOOKE_MAS0] = env->spr[SPR_BOOKE_MAS4] & MAS4_TLBSELD_MASK; > + env->spr[SPR_BOOKE_MAS1] = env->spr[SPR_BOOKE_MAS4] & MAS4_TSIZED_MASK; > + env->spr[SPR_BOOKE_MAS2] = env->spr[SPR_BOOKE_MAS4] & MAS4_WIMGED_MASK; > + > + if (env->spr[SPR_BOOKE_MAS6] & MAS6_SAS) { > + env->spr[SPR_BOOKE_MAS1] |= MAS1_TS; > + } > + > + env->spr[SPR_BOOKE_MAS1] |= (env->spr[SPR_BOOKE_MAS6] >> 16) > + << MAS1_TID_SHIFT; > + > + /* next victim logic */ > + env->spr[SPR_BOOKE_MAS0] |= env->last_way << MAS0_ESEL_SHIFT; > + env->last_way++; > + env->last_way &= 3; > + env->spr[SPR_BOOKE_MAS0] |= env->last_way << MAS0_NV_SHIFT; ...and this with e500_update_mas_tlb_miss(). > @@ -8433,7 +8505,7 @@ GEN_HANDLER2(icbt_40x, "icbt", 0x1F, 0x06, 0x08, > 0x03E00001, PPC_40x_ICBT), > GEN_HANDLER(iccci, 0x1F, 0x06, 0x1E, 0x00000001, PPC_4xx_COMMON), > GEN_HANDLER(icread, 0x1F, 0x06, 0x1F, 0x03E00001, PPC_4xx_COMMON), > GEN_HANDLER2(rfci_40x, "rfci", 0x13, 0x13, 0x01, 0x03FF8001, PPC_40x_EXCP), > -GEN_HANDLER(rfci, 0x13, 0x13, 0x01, 0x03FF8001, PPC_BOOKE), > +GEN_HANDLER_E(rfci, 0x13, 0x13, 0x01, 0x03FF8001, PPC_BOOKE, PPC2_BOOKE_FSL), What is PPC2_BOOKE_FSL supposed to indicate? rfci is basic booke. It's in the 440. > +GEN_HANDLER2_E(icbt_440, "icbt", 0x1F, 0x16, 0x00, 0x03E00001, > + PPC_BOOKE, PPC2_BOOKE_FSL), "icbt_440" is FSL-specific? :-) > +static void spr_write_booke_pid (void *opaque, int sprn, int gprn) > +{ > + gen_store_spr(sprn, cpu_gpr[gprn]); > + /* switching context, so need to flush tlb */ > + gen_helper_tlbia(); > +} Well, we want to flush the non-guest-visible TLB that doesn't understand PIDs -- but I'd expect "tlbia" to flush the architected TLB. 8xx, at least, has both tlbia and an architected TLB. Plus, we need ppc_tlb_invalidate_all() to clear the architected TLB, unless we call something different on reset. > @@ -1578,45 +1614,38 @@ static void gen_spr_BookE_FSL (CPUPPCState *env, > uint32_t mas_mask) > SPR_NOACCESS, SPR_NOACCESS, > &spr_read_generic, SPR_NOACCESS, > 0x00000000); /* TOFIX */ > - /* XXX : not implemented */ > - spr_register(env, SPR_MMUCSR0, "MMUCSR0", > - SPR_NOACCESS, SPR_NOACCESS, > - &spr_read_generic, &spr_write_generic, > - 0x00000000); /* TOFIX */ > switch (env->nb_ways) { > case 4: > - /* XXX : not implemented */ > spr_register(env, SPR_BOOKE_TLB3CFG, "TLB3CFG", > SPR_NOACCESS, SPR_NOACCESS, > &spr_read_generic, SPR_NOACCESS, > - 0x00000000); /* TOFIX */ > + tlbncfg[3]); > /* Fallthru */ > case 3: > - /* XXX : not implemented */ > spr_register(env, SPR_BOOKE_TLB2CFG, "TLB2CFG", > SPR_NOACCESS, SPR_NOACCESS, > &spr_read_generic, SPR_NOACCESS, > - 0x00000000); /* TOFIX */ > + tlbncfg[2]); > /* Fallthru */ In some places you use nb_ways as the associativity of TLB0. But here it seems to be the number of TLB arrays? > @@ -4334,8 +4373,23 @@ static void init_proc_e500 (CPUPPCState *env) > /* Memory management */ > #if !defined(CONFIG_USER_ONLY) > env->nb_pids = 3; > + env->nb_ways = 2; > + env->id_tlbs = 0; > + if ((env->spr[SPR_PVR] & 0x00010000)) { > + /* e500v2 */ > + env->nb_tlbs[0] = 512; > + tlbncfg[0] = gen_tlbncfg(4, 1, 1, 0, env->nb_tlbs[0]); > + tlbncfg[1] = gen_tlbncfg(16, 1, 12, TLBnCFG_AVAIL | TLBnCFG_IPROT, > 16); > + } else { > + /* e500v1 */ > + env->nb_tlbs[0] = 256; > + tlbncfg[0] = gen_tlbncfg(2, 1, 1, 0, env->nb_tlbs[0]); > + tlbncfg[1] = gen_tlbncfg(16, 1, 9, TLBnCFG_AVAIL | TLBnCFG_IPROT, > 16); > + } It would be nice to be more precise with PVR testing, and complain if an unrecognized PVR is used (e.g. e500mc). -Scott