target/mips: Implement hardware page table walker for MIPS32
Implement hardware page table walker. This implementation is limiter only to MIPS32. Reviewed-by: Aleksandar Markovic <amarkovic@wavecomp.com> Signed-off-by: Yongbok Kim <yongbok.kim@mips.com> Signed-off-by: Aleksandar Markovic <amarkovic@wavecomp.com>
This commit is contained in:
parent
6301079557
commit
074cfcb4da
@ -537,6 +537,342 @@ hwaddr mips_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_USER_ONLY)
|
||||
#if !defined(TARGET_MIPS64)
|
||||
|
||||
/*
|
||||
* Perform hardware page table walk
|
||||
*
|
||||
* Memory accesses are performed using the KERNEL privilege level.
|
||||
* Synchronous exceptions detected on memory accesses cause a silent exit
|
||||
* from page table walking, resulting in a TLB or XTLB Refill exception.
|
||||
*
|
||||
* Implementations are not required to support page table walk memory
|
||||
* accesses from mapped memory regions. When an unsupported access is
|
||||
* attempted, a silent exit is taken, resulting in a TLB or XTLB Refill
|
||||
* exception.
|
||||
*
|
||||
* Note that if an exception is caused by AddressTranslation or LoadMemory
|
||||
* functions, the exception is not taken, a silent exit is taken,
|
||||
* resulting in a TLB or XTLB Refill exception.
|
||||
*/
|
||||
|
||||
static bool get_pte(CPUMIPSState *env, uint64_t vaddr, int entry_size,
|
||||
uint64_t *pte)
|
||||
{
|
||||
if ((vaddr & ((entry_size >> 3) - 1)) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (entry_size == 64) {
|
||||
*pte = cpu_ldq_code(env, vaddr);
|
||||
} else {
|
||||
*pte = cpu_ldl_code(env, vaddr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint64_t get_tlb_entry_layout(CPUMIPSState *env, uint64_t entry,
|
||||
int entry_size, int ptei)
|
||||
{
|
||||
uint64_t result = entry;
|
||||
uint64_t rixi;
|
||||
if (ptei > entry_size) {
|
||||
ptei -= 32;
|
||||
}
|
||||
result >>= (ptei - 2);
|
||||
rixi = result & 3;
|
||||
result >>= 2;
|
||||
result |= rixi << CP0EnLo_XI;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int walk_directory(CPUMIPSState *env, uint64_t *vaddr,
|
||||
int directory_index, bool *huge_page, bool *hgpg_directory_hit,
|
||||
uint64_t *pw_entrylo0, uint64_t *pw_entrylo1)
|
||||
{
|
||||
int dph = (env->CP0_PWCtl >> CP0PC_DPH) & 0x1;
|
||||
int psn = (env->CP0_PWCtl >> CP0PC_PSN) & 0x3F;
|
||||
int hugepg = (env->CP0_PWCtl >> CP0PC_HUGEPG) & 0x1;
|
||||
int pf_ptew = (env->CP0_PWField >> CP0PF_PTEW) & 0x3F;
|
||||
int ptew = (env->CP0_PWSize >> CP0PS_PTEW) & 0x3F;
|
||||
int native_shift = (((env->CP0_PWSize >> CP0PS_PS) & 1) == 0) ? 2 : 3;
|
||||
int directory_shift = (ptew > 1) ? -1 :
|
||||
(hugepg && (ptew == 1)) ? native_shift + 1 : native_shift;
|
||||
int leaf_shift = (ptew > 1) ? -1 :
|
||||
(ptew == 1) ? native_shift + 1 : native_shift;
|
||||
uint32_t direntry_size = 1 << (directory_shift + 3);
|
||||
uint32_t leafentry_size = 1 << (leaf_shift + 3);
|
||||
uint64_t entry;
|
||||
uint64_t paddr;
|
||||
int prot;
|
||||
uint64_t lsb = 0;
|
||||
uint64_t w = 0;
|
||||
|
||||
if (get_physical_address(env, &paddr, &prot, *vaddr, MMU_DATA_LOAD,
|
||||
ACCESS_INT, cpu_mmu_index(env, false)) !=
|
||||
TLBRET_MATCH) {
|
||||
/* wrong base address */
|
||||
return 0;
|
||||
}
|
||||
if (!get_pte(env, *vaddr, direntry_size, &entry)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((entry & (1 << psn)) && hugepg) {
|
||||
*huge_page = true;
|
||||
*hgpg_directory_hit = true;
|
||||
entry = get_tlb_entry_layout(env, entry, leafentry_size, pf_ptew);
|
||||
w = directory_index - 1;
|
||||
if (directory_index & 0x1) {
|
||||
/* Generate adjacent page from same PTE for odd TLB page */
|
||||
lsb = (1 << w) >> 6;
|
||||
*pw_entrylo0 = entry & ~lsb; /* even page */
|
||||
*pw_entrylo1 = entry | lsb; /* odd page */
|
||||
} else if (dph) {
|
||||
int oddpagebit = 1 << leaf_shift;
|
||||
uint64_t vaddr2 = *vaddr ^ oddpagebit;
|
||||
if (*vaddr & oddpagebit) {
|
||||
*pw_entrylo1 = entry;
|
||||
} else {
|
||||
*pw_entrylo0 = entry;
|
||||
}
|
||||
if (get_physical_address(env, &paddr, &prot, vaddr2, MMU_DATA_LOAD,
|
||||
ACCESS_INT, cpu_mmu_index(env, false)) !=
|
||||
TLBRET_MATCH) {
|
||||
return 0;
|
||||
}
|
||||
if (!get_pte(env, vaddr2, leafentry_size, &entry)) {
|
||||
return 0;
|
||||
}
|
||||
entry = get_tlb_entry_layout(env, entry, leafentry_size, pf_ptew);
|
||||
if (*vaddr & oddpagebit) {
|
||||
*pw_entrylo0 = entry;
|
||||
} else {
|
||||
*pw_entrylo1 = entry;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
*vaddr = entry;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static bool page_table_walk_refill(CPUMIPSState *env, vaddr address, int rw,
|
||||
int mmu_idx)
|
||||
{
|
||||
int gdw = (env->CP0_PWSize >> CP0PS_GDW) & 0x3F;
|
||||
int udw = (env->CP0_PWSize >> CP0PS_UDW) & 0x3F;
|
||||
int mdw = (env->CP0_PWSize >> CP0PS_MDW) & 0x3F;
|
||||
int ptw = (env->CP0_PWSize >> CP0PS_PTW) & 0x3F;
|
||||
int ptew = (env->CP0_PWSize >> CP0PS_PTEW) & 0x3F;
|
||||
|
||||
/* Initial values */
|
||||
bool huge_page = false;
|
||||
bool hgpg_bdhit = false;
|
||||
bool hgpg_gdhit = false;
|
||||
bool hgpg_udhit = false;
|
||||
bool hgpg_mdhit = false;
|
||||
|
||||
int32_t pw_pagemask = 0;
|
||||
target_ulong pw_entryhi = 0;
|
||||
uint64_t pw_entrylo0 = 0;
|
||||
uint64_t pw_entrylo1 = 0;
|
||||
|
||||
/* Native pointer size */
|
||||
/*For the 32-bit architectures, this bit is fixed to 0.*/
|
||||
int native_shift = (((env->CP0_PWSize >> CP0PS_PS) & 1) == 0) ? 2 : 3;
|
||||
|
||||
/* Indices from PWField */
|
||||
int pf_gdw = (env->CP0_PWField >> CP0PF_GDW) & 0x3F;
|
||||
int pf_udw = (env->CP0_PWField >> CP0PF_UDW) & 0x3F;
|
||||
int pf_mdw = (env->CP0_PWField >> CP0PF_MDW) & 0x3F;
|
||||
int pf_ptw = (env->CP0_PWField >> CP0PF_PTW) & 0x3F;
|
||||
int pf_ptew = (env->CP0_PWField >> CP0PF_PTEW) & 0x3F;
|
||||
|
||||
/* Indices computed from faulting address */
|
||||
int gindex = (address >> pf_gdw) & ((1 << gdw) - 1);
|
||||
int uindex = (address >> pf_udw) & ((1 << udw) - 1);
|
||||
int mindex = (address >> pf_mdw) & ((1 << mdw) - 1);
|
||||
int ptindex = (address >> pf_ptw) & ((1 << ptw) - 1);
|
||||
|
||||
/* Other HTW configs */
|
||||
int hugepg = (env->CP0_PWCtl >> CP0PC_HUGEPG) & 0x1;
|
||||
|
||||
/* HTW Shift values (depend on entry size) */
|
||||
int directory_shift = (ptew > 1) ? -1 :
|
||||
(hugepg && (ptew == 1)) ? native_shift + 1 : native_shift;
|
||||
int leaf_shift = (ptew > 1) ? -1 :
|
||||
(ptew == 1) ? native_shift + 1 : native_shift;
|
||||
|
||||
/* Offsets into tables */
|
||||
int goffset = gindex << directory_shift;
|
||||
int uoffset = uindex << directory_shift;
|
||||
int moffset = mindex << directory_shift;
|
||||
int ptoffset0 = (ptindex >> 1) << (leaf_shift + 1);
|
||||
int ptoffset1 = ptoffset0 | (1 << (leaf_shift));
|
||||
|
||||
uint32_t leafentry_size = 1 << (leaf_shift + 3);
|
||||
|
||||
/* Starting address - Page Table Base */
|
||||
uint64_t vaddr = env->CP0_PWBase;
|
||||
|
||||
uint64_t dir_entry;
|
||||
uint64_t paddr;
|
||||
int prot;
|
||||
int m;
|
||||
|
||||
if (!(env->CP0_Config3 & (1 << CP0C3_PW))) {
|
||||
/* walker is unimplemented */
|
||||
return false;
|
||||
}
|
||||
if (!(env->CP0_PWCtl & (1 << CP0PC_PWEN))) {
|
||||
/* walker is disabled */
|
||||
return false;
|
||||
}
|
||||
if (!(gdw > 0 || udw > 0 || mdw > 0)) {
|
||||
/* no structure to walk */
|
||||
return false;
|
||||
}
|
||||
if ((directory_shift == -1) || (leaf_shift == -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Global Directory */
|
||||
if (gdw > 0) {
|
||||
vaddr |= goffset;
|
||||
switch (walk_directory(env, &vaddr, pf_gdw, &huge_page, &hgpg_gdhit,
|
||||
&pw_entrylo0, &pw_entrylo1))
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
goto refill;
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Upper directory */
|
||||
if (udw > 0) {
|
||||
vaddr |= uoffset;
|
||||
switch (walk_directory(env, &vaddr, pf_udw, &huge_page, &hgpg_udhit,
|
||||
&pw_entrylo0, &pw_entrylo1))
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
goto refill;
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Middle directory */
|
||||
if (mdw > 0) {
|
||||
vaddr |= moffset;
|
||||
switch (walk_directory(env, &vaddr, pf_mdw, &huge_page, &hgpg_mdhit,
|
||||
&pw_entrylo0, &pw_entrylo1))
|
||||
{
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
goto refill;
|
||||
case 2:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Leaf Level Page Table - First half of PTE pair */
|
||||
vaddr |= ptoffset0;
|
||||
if (get_physical_address(env, &paddr, &prot, vaddr, MMU_DATA_LOAD,
|
||||
ACCESS_INT, cpu_mmu_index(env, false)) !=
|
||||
TLBRET_MATCH) {
|
||||
return false;
|
||||
}
|
||||
if (!get_pte(env, vaddr, leafentry_size, &dir_entry)) {
|
||||
return false;
|
||||
}
|
||||
dir_entry = get_tlb_entry_layout(env, dir_entry, leafentry_size, pf_ptew);
|
||||
pw_entrylo0 = dir_entry;
|
||||
|
||||
/* Leaf Level Page Table - Second half of PTE pair */
|
||||
vaddr |= ptoffset1;
|
||||
if (get_physical_address(env, &paddr, &prot, vaddr, MMU_DATA_LOAD,
|
||||
ACCESS_INT, cpu_mmu_index(env, false)) !=
|
||||
TLBRET_MATCH) {
|
||||
return false;
|
||||
}
|
||||
if (!get_pte(env, vaddr, leafentry_size, &dir_entry)) {
|
||||
return false;
|
||||
}
|
||||
dir_entry = get_tlb_entry_layout(env, dir_entry, leafentry_size, pf_ptew);
|
||||
pw_entrylo1 = dir_entry;
|
||||
|
||||
refill:
|
||||
|
||||
m = (1 << pf_ptw) - 1;
|
||||
|
||||
if (huge_page) {
|
||||
switch (hgpg_bdhit << 3 | hgpg_gdhit << 2 | hgpg_udhit << 1 |
|
||||
hgpg_mdhit)
|
||||
{
|
||||
case 4:
|
||||
m = (1 << pf_gdw) - 1;
|
||||
if (pf_gdw & 1) {
|
||||
m >>= 1;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
m = (1 << pf_udw) - 1;
|
||||
if (pf_udw & 1) {
|
||||
m >>= 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
m = (1 << pf_mdw) - 1;
|
||||
if (pf_mdw & 1) {
|
||||
m >>= 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pw_pagemask = m >> 12;
|
||||
update_pagemask(env, pw_pagemask << 13, &pw_pagemask);
|
||||
pw_entryhi = (address & ~0x1fff) | (env->CP0_EntryHi & 0xFF);
|
||||
{
|
||||
target_ulong tmp_entryhi = env->CP0_EntryHi;
|
||||
int32_t tmp_pagemask = env->CP0_PageMask;
|
||||
uint64_t tmp_entrylo0 = env->CP0_EntryLo0;
|
||||
uint64_t tmp_entrylo1 = env->CP0_EntryLo1;
|
||||
|
||||
env->CP0_EntryHi = pw_entryhi;
|
||||
env->CP0_PageMask = pw_pagemask;
|
||||
env->CP0_EntryLo0 = pw_entrylo0;
|
||||
env->CP0_EntryLo1 = pw_entrylo1;
|
||||
|
||||
/*
|
||||
* The hardware page walker inserts a page into the TLB in a manner
|
||||
* identical to a TLBWR instruction as executed by the software refill
|
||||
* handler.
|
||||
*/
|
||||
r4k_helper_tlbwr(env);
|
||||
|
||||
env->CP0_EntryHi = tmp_entryhi;
|
||||
env->CP0_PageMask = tmp_pagemask;
|
||||
env->CP0_EntryLo0 = tmp_entrylo0;
|
||||
env->CP0_EntryLo1 = tmp_entrylo1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
|
||||
int mmu_idx)
|
||||
{
|
||||
@ -558,8 +894,7 @@ int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
|
||||
|
||||
/* data access */
|
||||
#if !defined(CONFIG_USER_ONLY)
|
||||
/* XXX: put correct access by using cpu_restore_state()
|
||||
correctly */
|
||||
/* XXX: put correct access by using cpu_restore_state() correctly */
|
||||
access_type = ACCESS_INT;
|
||||
ret = get_physical_address(env, &physical, &prot,
|
||||
address, rw, access_type, mmu_idx);
|
||||
@ -583,6 +918,32 @@ int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
|
||||
} else if (ret < 0)
|
||||
#endif
|
||||
{
|
||||
#if !defined(CONFIG_USER_ONLY)
|
||||
#if !defined(TARGET_MIPS64)
|
||||
if ((ret == TLBRET_NOMATCH) && (env->tlb->nb_tlb > 1)) {
|
||||
/*
|
||||
* Memory reads during hardware page table walking are performed
|
||||
* as if they were kernel-mode load instructions.
|
||||
*/
|
||||
int mode = (env->hflags & MIPS_HFLAG_KSU);
|
||||
bool ret_walker;
|
||||
env->hflags &= ~MIPS_HFLAG_KSU;
|
||||
ret_walker = page_table_walk_refill(env, address, rw, mmu_idx);
|
||||
env->hflags |= mode;
|
||||
if (ret_walker) {
|
||||
ret = get_physical_address(env, &physical, &prot,
|
||||
address, rw, access_type, mmu_idx);
|
||||
if (ret == TLBRET_MATCH) {
|
||||
tlb_set_page(cs, address & TARGET_PAGE_MASK,
|
||||
physical & TARGET_PAGE_MASK, prot | PAGE_EXEC,
|
||||
mmu_idx, TARGET_PAGE_SIZE);
|
||||
ret = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
raise_mmu_exception(env, address, rw, ret);
|
||||
ret = 1;
|
||||
}
|
||||
|
@ -211,6 +211,7 @@ uint64_t float_class_d(uint64_t arg, float_status *fst);
|
||||
|
||||
extern unsigned int ieee_rm[];
|
||||
int ieee_ex_to_mips(int xcpt);
|
||||
void update_pagemask(CPUMIPSState *env, target_ulong arg1, int32_t *pagemask);
|
||||
|
||||
static inline void restore_rounding_mode(CPUMIPSState *env)
|
||||
{
|
||||
|
@ -1400,7 +1400,7 @@ void helper_mtc0_context(CPUMIPSState *env, target_ulong arg1)
|
||||
env->CP0_Context = (env->CP0_Context & 0x007FFFFF) | (arg1 & ~0x007FFFFF);
|
||||
}
|
||||
|
||||
void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
|
||||
void update_pagemask(CPUMIPSState *env, target_ulong arg1, int32_t *pagemask)
|
||||
{
|
||||
uint64_t mask = arg1 >> (TARGET_PAGE_BITS + 1);
|
||||
if (!(env->insn_flags & ISA_MIPS32R6) || (arg1 == ~0) ||
|
||||
@ -1411,6 +1411,11 @@ void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
|
||||
}
|
||||
}
|
||||
|
||||
void helper_mtc0_pagemask(CPUMIPSState *env, target_ulong arg1)
|
||||
{
|
||||
update_pagemask(env, arg1, &env->CP0_PageMask);
|
||||
}
|
||||
|
||||
void helper_mtc0_pagegrain(CPUMIPSState *env, target_ulong arg1)
|
||||
{
|
||||
/* SmartMIPS not implemented */
|
||||
|
Loading…
Reference in New Issue
Block a user