mirror of https://github.com/xemu-project/xemu.git
target-arm: Add support for long format translation table walks
Implement the actual table walk code for LPAE's long format translation tables. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
e42c4db3a3
commit
3dde962f39
|
@ -3,6 +3,7 @@
|
||||||
#include "helper.h"
|
#include "helper.h"
|
||||||
#include "host-utils.h"
|
#include "host-utils.h"
|
||||||
#include "sysemu.h"
|
#include "sysemu.h"
|
||||||
|
#include "bitops.h"
|
||||||
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
#ifndef CONFIG_USER_ONLY
|
||||||
static inline int get_phys_addr(CPUARMState *env, uint32_t address,
|
static inline int get_phys_addr(CPUARMState *env, uint32_t address,
|
||||||
|
@ -2184,6 +2185,184 @@ do_fault:
|
||||||
return code | (domain << 4);
|
return code | (domain << 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fault type for long-descriptor MMU fault reporting; this corresponds
|
||||||
|
* to bits [5..2] in the STATUS field in long-format DFSR/IFSR.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
translation_fault = 1,
|
||||||
|
access_fault = 2,
|
||||||
|
permission_fault = 3,
|
||||||
|
} MMUFaultType;
|
||||||
|
|
||||||
|
static int get_phys_addr_lpae(CPUARMState *env, uint32_t address,
|
||||||
|
int access_type, int is_user,
|
||||||
|
target_phys_addr_t *phys_ptr, int *prot,
|
||||||
|
target_ulong *page_size_ptr)
|
||||||
|
{
|
||||||
|
/* Read an LPAE long-descriptor translation table. */
|
||||||
|
MMUFaultType fault_type = translation_fault;
|
||||||
|
uint32_t level = 1;
|
||||||
|
uint32_t epd;
|
||||||
|
uint32_t tsz;
|
||||||
|
uint64_t ttbr;
|
||||||
|
int ttbr_select;
|
||||||
|
int n;
|
||||||
|
target_phys_addr_t descaddr;
|
||||||
|
uint32_t tableattrs;
|
||||||
|
target_ulong page_size;
|
||||||
|
uint32_t attrs;
|
||||||
|
|
||||||
|
/* Determine whether this address is in the region controlled by
|
||||||
|
* TTBR0 or TTBR1 (or if it is in neither region and should fault).
|
||||||
|
* This is a Non-secure PL0/1 stage 1 translation, so controlled by
|
||||||
|
* TTBCR/TTBR0/TTBR1 in accordance with ARM ARM DDI0406C table B-32:
|
||||||
|
*/
|
||||||
|
uint32_t t0sz = extract32(env->cp15.c2_control, 0, 3);
|
||||||
|
uint32_t t1sz = extract32(env->cp15.c2_control, 16, 3);
|
||||||
|
if (t0sz && !extract32(address, 32 - t0sz, t0sz)) {
|
||||||
|
/* there is a ttbr0 region and we are in it (high bits all zero) */
|
||||||
|
ttbr_select = 0;
|
||||||
|
} else if (t1sz && !extract32(~address, 32 - t1sz, t1sz)) {
|
||||||
|
/* there is a ttbr1 region and we are in it (high bits all one) */
|
||||||
|
ttbr_select = 1;
|
||||||
|
} else if (!t0sz) {
|
||||||
|
/* ttbr0 region is "everything not in the ttbr1 region" */
|
||||||
|
ttbr_select = 0;
|
||||||
|
} else if (!t1sz) {
|
||||||
|
/* ttbr1 region is "everything not in the ttbr0 region" */
|
||||||
|
ttbr_select = 1;
|
||||||
|
} else {
|
||||||
|
/* in the gap between the two regions, this is a Translation fault */
|
||||||
|
fault_type = translation_fault;
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note that QEMU ignores shareability and cacheability attributes,
|
||||||
|
* so we don't need to do anything with the SH, ORGN, IRGN fields
|
||||||
|
* in the TTBCR. Similarly, TTBCR:A1 selects whether we get the
|
||||||
|
* ASID from TTBR0 or TTBR1, but QEMU's TLB doesn't currently
|
||||||
|
* implement any ASID-like capability so we can ignore it (instead
|
||||||
|
* we will always flush the TLB any time the ASID is changed).
|
||||||
|
*/
|
||||||
|
if (ttbr_select == 0) {
|
||||||
|
ttbr = ((uint64_t)env->cp15.c2_base0_hi << 32) | env->cp15.c2_base0;
|
||||||
|
epd = extract32(env->cp15.c2_control, 7, 1);
|
||||||
|
tsz = t0sz;
|
||||||
|
} else {
|
||||||
|
ttbr = ((uint64_t)env->cp15.c2_base1_hi << 32) | env->cp15.c2_base1;
|
||||||
|
epd = extract32(env->cp15.c2_control, 23, 1);
|
||||||
|
tsz = t1sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (epd) {
|
||||||
|
/* Translation table walk disabled => Translation fault on TLB miss */
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the region is small enough we will skip straight to a 2nd level
|
||||||
|
* lookup. This affects the number of bits of the address used in
|
||||||
|
* combination with the TTBR to find the first descriptor. ('n' here
|
||||||
|
* matches the usage in the ARM ARM sB3.6.6, where bits [39..n] are
|
||||||
|
* from the TTBR, [n-1..3] from the vaddr, and [2..0] always zero).
|
||||||
|
*/
|
||||||
|
if (tsz > 1) {
|
||||||
|
level = 2;
|
||||||
|
n = 14 - tsz;
|
||||||
|
} else {
|
||||||
|
n = 5 - tsz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear the vaddr bits which aren't part of the within-region address,
|
||||||
|
* so that we don't have to special case things when calculating the
|
||||||
|
* first descriptor address.
|
||||||
|
*/
|
||||||
|
address &= (0xffffffffU >> tsz);
|
||||||
|
|
||||||
|
/* Now we can extract the actual base address from the TTBR */
|
||||||
|
descaddr = extract64(ttbr, 0, 40);
|
||||||
|
descaddr &= ~((1ULL << n) - 1);
|
||||||
|
|
||||||
|
tableattrs = 0;
|
||||||
|
for (;;) {
|
||||||
|
uint64_t descriptor;
|
||||||
|
|
||||||
|
descaddr |= ((address >> (9 * (4 - level))) & 0xff8);
|
||||||
|
descriptor = ldq_phys(descaddr);
|
||||||
|
if (!(descriptor & 1) ||
|
||||||
|
(!(descriptor & 2) && (level == 3))) {
|
||||||
|
/* Invalid, or the Reserved level 3 encoding */
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
descaddr = descriptor & 0xfffffff000ULL;
|
||||||
|
|
||||||
|
if ((descriptor & 2) && (level < 3)) {
|
||||||
|
/* Table entry. The top five bits are attributes which may
|
||||||
|
* propagate down through lower levels of the table (and
|
||||||
|
* which are all arranged so that 0 means "no effect", so
|
||||||
|
* we can gather them up by ORing in the bits at each level).
|
||||||
|
*/
|
||||||
|
tableattrs |= extract64(descriptor, 59, 5);
|
||||||
|
level++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Block entry at level 1 or 2, or page entry at level 3.
|
||||||
|
* These are basically the same thing, although the number
|
||||||
|
* of bits we pull in from the vaddr varies.
|
||||||
|
*/
|
||||||
|
page_size = (1 << (39 - (9 * level)));
|
||||||
|
descaddr |= (address & (page_size - 1));
|
||||||
|
/* Extract attributes from the descriptor and merge with table attrs */
|
||||||
|
attrs = extract64(descriptor, 2, 10)
|
||||||
|
| (extract64(descriptor, 52, 12) << 10);
|
||||||
|
attrs |= extract32(tableattrs, 0, 2) << 11; /* XN, PXN */
|
||||||
|
attrs |= extract32(tableattrs, 3, 1) << 5; /* APTable[1] => AP[2] */
|
||||||
|
/* The sense of AP[1] vs APTable[0] is reversed, as APTable[0] == 1
|
||||||
|
* means "force PL1 access only", which means forcing AP[1] to 0.
|
||||||
|
*/
|
||||||
|
if (extract32(tableattrs, 2, 1)) {
|
||||||
|
attrs &= ~(1 << 4);
|
||||||
|
}
|
||||||
|
/* Since we're always in the Non-secure state, NSTable is ignored. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Here descaddr is the final physical address, and attributes
|
||||||
|
* are all in attrs.
|
||||||
|
*/
|
||||||
|
fault_type = access_fault;
|
||||||
|
if ((attrs & (1 << 8)) == 0) {
|
||||||
|
/* Access flag */
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
fault_type = permission_fault;
|
||||||
|
if (is_user && !(attrs & (1 << 4))) {
|
||||||
|
/* Unprivileged access not enabled */
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
*prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
|
||||||
|
if (attrs & (1 << 12) || (!is_user && (attrs & (1 << 11)))) {
|
||||||
|
/* XN or PXN */
|
||||||
|
if (access_type == 2) {
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
*prot &= ~PAGE_EXEC;
|
||||||
|
}
|
||||||
|
if (attrs & (1 << 5)) {
|
||||||
|
/* Write access forbidden */
|
||||||
|
if (access_type == 1) {
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
*prot &= ~PAGE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*phys_ptr = descaddr;
|
||||||
|
*page_size_ptr = page_size;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
do_fault:
|
||||||
|
/* Long-descriptor format IFSR/DFSR value */
|
||||||
|
return (1 << 9) | (fault_type << 2) | level;
|
||||||
|
}
|
||||||
|
|
||||||
static int get_phys_addr_mpu(CPUARMState *env, uint32_t address,
|
static int get_phys_addr_mpu(CPUARMState *env, uint32_t address,
|
||||||
int access_type, int is_user,
|
int access_type, int is_user,
|
||||||
target_phys_addr_t *phys_ptr, int *prot)
|
target_phys_addr_t *phys_ptr, int *prot)
|
||||||
|
@ -2287,6 +2466,9 @@ static inline int get_phys_addr(CPUARMState *env, uint32_t address,
|
||||||
*page_size = TARGET_PAGE_SIZE;
|
*page_size = TARGET_PAGE_SIZE;
|
||||||
return get_phys_addr_mpu(env, address, access_type, is_user, phys_ptr,
|
return get_phys_addr_mpu(env, address, access_type, is_user, phys_ptr,
|
||||||
prot);
|
prot);
|
||||||
|
} else if (extended_addresses_enabled(env)) {
|
||||||
|
return get_phys_addr_lpae(env, address, access_type, is_user, phys_ptr,
|
||||||
|
prot, page_size);
|
||||||
} else if (env->cp15.c1_sys & (1 << 23)) {
|
} else if (env->cp15.c1_sys & (1 << 23)) {
|
||||||
return get_phys_addr_v6(env, address, access_type, is_user, phys_ptr,
|
return get_phys_addr_v6(env, address, access_type, is_user, phys_ptr,
|
||||||
prot, page_size);
|
prot, page_size);
|
||||||
|
|
Loading…
Reference in New Issue