target/mips: Implement segmentation control

Implement the optional segmentation control feature in the virtual to
physical address translation code.

The fixed legacy segment and xkphys handling is replaced with a dynamic
layout based on the segmentation control registers (which should be set
up even when the feature is not exposed to the guest).

Backports commit 480e79aedd322fcfac17052caff21626ea7c78e2 from qemu
This commit is contained in:
James Hogan 2018-03-04 01:06:02 -05:00 committed by Lioncash
parent ddbea9422c
commit 1ef8c8bd48
No known key found for this signature in database
GPG Key ID: 4E3C3CC1031BA9C7

View File

@ -107,15 +107,107 @@ int r4k_map_address (CPUMIPSState *env, hwaddr *physical, int *prot,
return TLBRET_NOMATCH; return TLBRET_NOMATCH;
} }
static int is_seg_am_mapped(unsigned int am, bool eu, int mmu_idx)
{
/*
* Interpret access control mode and mmu_idx.
* AdE? TLB?
* AM K S U E K S U E
* UK 0 0 1 1 0 0 - - 0
* MK 1 0 1 1 0 1 - - !eu
* MSK 2 0 0 1 0 1 1 - !eu
* MUSK 3 0 0 0 0 1 1 1 !eu
* MUSUK 4 0 0 0 0 0 1 1 0
* USK 5 0 0 1 0 0 0 - 0
* - 6 - - - - - - - -
* UUSK 7 0 0 0 0 0 0 0 0
*/
int32_t adetlb_mask;
switch (mmu_idx) {
case 3 /* ERL */:
/* If EU is set, always unmapped */
if (eu) {
return 0;
}
/* fall through */
case MIPS_HFLAG_KM:
/* Never AdE, TLB mapped if AM={1,2,3} */
adetlb_mask = 0x70000000;
goto check_tlb;
case MIPS_HFLAG_SM:
/* AdE if AM={0,1}, TLB mapped if AM={2,3,4} */
adetlb_mask = 0xc0380000;
goto check_ade;
case MIPS_HFLAG_UM:
/* AdE if AM={0,1,2,5}, TLB mapped if AM={3,4} */
adetlb_mask = 0xe4180000;
/* fall through */
check_ade:
/* does this AM cause AdE in current execution mode */
if ((adetlb_mask << am) < 0) {
return TLBRET_BADADDR;
}
adetlb_mask <<= 8;
/* fall through */
check_tlb:
/* is this AM mapped in current execution mode */
return ((adetlb_mask << am) < 0);
default:
assert(0);
return TLBRET_BADADDR;
};
}
static int get_seg_physical_address(CPUMIPSState *env, hwaddr *physical,
int *prot, target_ulong real_address,
int rw, int access_type, int mmu_idx,
unsigned int am, bool eu,
target_ulong segmask,
hwaddr physical_base)
{
int mapped = is_seg_am_mapped(am, eu, mmu_idx);
if (mapped < 0) {
/* is_seg_am_mapped can report TLBRET_BADADDR */
return mapped;
} else if (mapped) {
/* The segment is TLB mapped */
return env->tlb->map_address(env, physical, prot, real_address, rw,
access_type);
} else {
/* The segment is unmapped */
*physical = physical_base | (real_address & segmask);
*prot = PAGE_READ | PAGE_WRITE;
return TLBRET_MATCH;
}
}
static int get_segctl_physical_address(CPUMIPSState *env, hwaddr *physical,
int *prot, target_ulong real_address,
int rw, int access_type, int mmu_idx,
uint16_t segctl, target_ulong segmask)
{
unsigned int am = (segctl & CP0SC_AM_MASK) >> CP0SC_AM;
bool eu = (segctl >> CP0SC_EU) & 1;
hwaddr pa = ((hwaddr)segctl & CP0SC_PA_MASK) << 20;
return get_seg_physical_address(env, physical, prot, real_address, rw,
access_type, mmu_idx, am, eu, segmask,
pa & ~(hwaddr)segmask);
}
static int get_physical_address (CPUMIPSState *env, hwaddr *physical, static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
int *prot, target_ulong real_address, int *prot, target_ulong real_address,
int rw, int access_type, int mmu_idx) int rw, int access_type, int mmu_idx)
{ {
#if defined(TARGET_MIPS64)
/* User mode can only access useg/xuseg */ /* User mode can only access useg/xuseg */
int user_mode = mmu_idx == MIPS_HFLAG_UM; int user_mode = mmu_idx == MIPS_HFLAG_UM;
int supervisor_mode = mmu_idx == MIPS_HFLAG_SM; int supervisor_mode = mmu_idx == MIPS_HFLAG_SM;
int kernel_mode = !user_mode && !supervisor_mode; int kernel_mode = !user_mode && !supervisor_mode;
#if defined(TARGET_MIPS64)
int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0; int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0; int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0;
int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0; int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
@ -135,12 +227,16 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
if (address <= USEG_LIMIT) { if (address <= USEG_LIMIT) {
/* useg */ /* useg */
if (env->CP0_Status & (1 << CP0St_ERL)) { uint16_t segctl;
*physical = address & 0xFFFFFFFF;
*prot = PAGE_READ | PAGE_WRITE; if (address >= 0x40000000UL) {
segctl = env->CP0_SegCtl2;
} else { } else {
ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type); segctl = env->CP0_SegCtl2 >> 16;
} }
ret = get_segctl_physical_address(env, physical, prot, real_address, rw,
access_type, mmu_idx, segctl,
0x3FFFFFFF);
#if defined(TARGET_MIPS64) #if defined(TARGET_MIPS64)
} else if (address < 0x4000000000000000ULL) { } else if (address < 0x4000000000000000ULL) {
/* xuseg */ /* xuseg */
@ -159,10 +255,33 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
} }
} else if (address < 0xC000000000000000ULL) { } else if (address < 0xC000000000000000ULL) {
/* xkphys */ /* xkphys */
if (kernel_mode && KX && if ((address & 0x07FFFFFFFFFFFFFFULL) <= env->PAMask) {
(address & 0x07FFFFFFFFFFFFFFULL) <= env->PAMask) { /* KX/SX/UX bit to check for each xkphys EVA access mode */
*physical = address & env->PAMask; static const uint8_t am_ksux[8] = {
*prot = PAGE_READ | PAGE_WRITE; (1u << CP0St_KX),
(1u << CP0St_KX),
(1u << CP0St_SX),
(1u << CP0St_UX),
(1u << CP0St_UX),
(1u << CP0St_SX),
(1u << CP0St_KX),
(1u << CP0St_UX),
};
unsigned int am = CP0SC_AM_UK;
unsigned int xr = (env->CP0_SegCtl2 & CP0SC2_XR_MASK) >> CP0SC2_XR;
if (xr & (1 << ((address >> 59) & 0x7))) {
am = (env->CP0_SegCtl1 & CP0SC1_XAM_MASK) >> CP0SC1_XAM;
}
/* Does CP0_Status.KX/SX/UX permit the access mode (am) */
if (env->CP0_Status & am_ksux[am]) {
ret = get_seg_physical_address(env, physical, prot,
real_address, rw, access_type,
mmu_idx, am, false, env->PAMask,
0);
} else {
ret = TLBRET_BADADDR;
}
} else { } else {
ret = TLBRET_BADADDR; ret = TLBRET_BADADDR;
} }
@ -177,35 +296,25 @@ static int get_physical_address (CPUMIPSState *env, hwaddr *physical,
#endif #endif
} else if (address < (int32_t)KSEG1_BASE) { } else if (address < (int32_t)KSEG1_BASE) {
/* kseg0 */ /* kseg0 */
if (kernel_mode) { ret = get_segctl_physical_address(env, physical, prot, real_address, rw,
*physical = address - (int32_t)KSEG0_BASE; access_type, mmu_idx,
*prot = PAGE_READ | PAGE_WRITE; env->CP0_SegCtl1 >> 16, 0x1FFFFFFF);
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG2_BASE) { } else if (address < (int32_t)KSEG2_BASE) {
/* kseg1 */ /* kseg1 */
if (kernel_mode) { ret = get_segctl_physical_address(env, physical, prot, real_address, rw,
*physical = address - (int32_t)KSEG1_BASE; access_type, mmu_idx,
*prot = PAGE_READ | PAGE_WRITE; env->CP0_SegCtl1, 0x1FFFFFFF);
} else {
ret = TLBRET_BADADDR;
}
} else if (address < (int32_t)KSEG3_BASE) { } else if (address < (int32_t)KSEG3_BASE) {
/* sseg (kseg2) */ /* sseg (kseg2) */
if (supervisor_mode || kernel_mode) { ret = get_segctl_physical_address(env, physical, prot, real_address, rw,
ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type); access_type, mmu_idx,
} else { env->CP0_SegCtl0 >> 16, 0x1FFFFFFF);
ret = TLBRET_BADADDR;
}
} else { } else {
/* kseg3 */ /* kseg3 */
/* XXX: debug segment is not emulated */ /* XXX: debug segment is not emulated */
if (kernel_mode) { ret = get_segctl_physical_address(env, physical, prot, real_address, rw,
ret = env->tlb->map_address(env, physical, prot, real_address, rw, access_type); access_type, mmu_idx,
} else { env->CP0_SegCtl0, 0x1FFFFFFF);
ret = TLBRET_BADADDR;
}
} }
return ret; return ret;
@ -712,15 +821,17 @@ void mips_cpu_do_interrupt(CPUState *cs)
#if defined(TARGET_MIPS64) #if defined(TARGET_MIPS64)
int R = env->CP0_BadVAddr >> 62; int R = env->CP0_BadVAddr >> 62;
int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0; int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0;
int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0; int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
if (((R == 0 && UX) || (R == 1 && SX) || (R == 3 && KX)) && if ((R != 0 || UX) && (R != 3 || KX) &&
(!(env->insn_flags & (INSN_LOONGSON2E | INSN_LOONGSON2F)))) (!(env->insn_flags & (INSN_LOONGSON2E | INSN_LOONGSON2F)))) {
offset = 0x080; offset = 0x080;
else } else {
#endif #endif
offset = 0x000; offset = 0x000;
#if defined(TARGET_MIPS64)
}
#endif
} }
goto set_EPC; goto set_EPC;
case EXCP_TLBS: case EXCP_TLBS:
@ -731,15 +842,17 @@ void mips_cpu_do_interrupt(CPUState *cs)
#if defined(TARGET_MIPS64) #if defined(TARGET_MIPS64)
int R = env->CP0_BadVAddr >> 62; int R = env->CP0_BadVAddr >> 62;
int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0; int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0;
int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0; int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
if (((R == 0 && UX) || (R == 1 && SX) || (R == 3 && KX)) && if ((R != 0 || UX) && (R != 3 || KX) &&
(!(env->insn_flags & (INSN_LOONGSON2E | INSN_LOONGSON2F)))) (!(env->insn_flags & (INSN_LOONGSON2E | INSN_LOONGSON2F)))) {
offset = 0x080; offset = 0x080;
else } else {
#endif #endif
offset = 0x000; offset = 0x000;
#if defined(TARGET_MIPS64)
}
#endif
} }
goto set_EPC; goto set_EPC;
case EXCP_AdEL: case EXCP_AdEL: