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 "host-utils.h"
|
||||
#include "sysemu.h"
|
||||
#include "bitops.h"
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
static inline int get_phys_addr(CPUARMState *env, uint32_t address,
|
||||
|
@ -2184,6 +2185,184 @@ do_fault:
|
|||
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,
|
||||
int access_type, int is_user,
|
||||
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;
|
||||
return get_phys_addr_mpu(env, address, access_type, is_user, phys_ptr,
|
||||
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)) {
|
||||
return get_phys_addr_v6(env, address, access_type, is_user, phys_ptr,
|
||||
prot, page_size);
|
||||
|
|
Loading…
Reference in New Issue