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
|
#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 mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
|
||||||
int mmu_idx)
|
int mmu_idx)
|
||||||
{
|
{
|
||||||
@ -558,8 +894,7 @@ int mips_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, int rw,
|
|||||||
|
|
||||||
/* data access */
|
/* data access */
|
||||||
#if !defined(CONFIG_USER_ONLY)
|
#if !defined(CONFIG_USER_ONLY)
|
||||||
/* XXX: put correct access by using cpu_restore_state()
|
/* XXX: put correct access by using cpu_restore_state() correctly */
|
||||||
correctly */
|
|
||||||
access_type = ACCESS_INT;
|
access_type = ACCESS_INT;
|
||||||
ret = get_physical_address(env, &physical, &prot,
|
ret = get_physical_address(env, &physical, &prot,
|
||||||
address, rw, access_type, mmu_idx);
|
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)
|
} else if (ret < 0)
|
||||||
#endif
|
#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);
|
raise_mmu_exception(env, address, rw, ret);
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,7 @@ uint64_t float_class_d(uint64_t arg, float_status *fst);
|
|||||||
|
|
||||||
extern unsigned int ieee_rm[];
|
extern unsigned int ieee_rm[];
|
||||||
int ieee_ex_to_mips(int xcpt);
|
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)
|
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);
|
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);
|
uint64_t mask = arg1 >> (TARGET_PAGE_BITS + 1);
|
||||||
if (!(env->insn_flags & ISA_MIPS32R6) || (arg1 == ~0) ||
|
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)
|
void helper_mtc0_pagegrain(CPUMIPSState *env, target_ulong arg1)
|
||||||
{
|
{
|
||||||
/* SmartMIPS not implemented */
|
/* SmartMIPS not implemented */
|
||||||
|
Loading…
Reference in New Issue
Block a user