target/arm: Implement FEAT_HAFDBS, access flag portion
Perform the atomic update for hardware management of the access flag. Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20221024051851.3074715-13-richard.henderson@linaro.org [PMM: Fix accidental PROT_WRITE to PAGE_WRITE; add missing main-loop.h include] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
34a57faeab
commit
71943a1e90
@ -33,6 +33,7 @@ the following architecture extensions:
|
|||||||
- FEAT_FlagM (Flag manipulation instructions v2)
|
- FEAT_FlagM (Flag manipulation instructions v2)
|
||||||
- FEAT_FlagM2 (Enhancements to flag manipulation instructions)
|
- FEAT_FlagM2 (Enhancements to flag manipulation instructions)
|
||||||
- FEAT_GTG (Guest translation granule size)
|
- FEAT_GTG (Guest translation granule size)
|
||||||
|
- FEAT_HAFDBS (Hardware management of the access flag and dirty bit state)
|
||||||
- FEAT_HCX (Support for the HCRX_EL2 register)
|
- FEAT_HCX (Support for the HCRX_EL2 register)
|
||||||
- FEAT_HPDS (Hierarchical permission disables)
|
- FEAT_HPDS (Hierarchical permission disables)
|
||||||
- FEAT_I8MM (AArch64 Int8 matrix multiplication instructions)
|
- FEAT_I8MM (AArch64 Int8 matrix multiplication instructions)
|
||||||
|
@ -1165,6 +1165,7 @@ static void aarch64_max_initfn(Object *obj)
|
|||||||
cpu->isar.id_aa64mmfr0 = t;
|
cpu->isar.id_aa64mmfr0 = t;
|
||||||
|
|
||||||
t = cpu->isar.id_aa64mmfr1;
|
t = cpu->isar.id_aa64mmfr1;
|
||||||
|
t = FIELD_DP64(t, ID_AA64MMFR1, HAFDBS, 1); /* FEAT_HAFDBS, AF only */
|
||||||
t = FIELD_DP64(t, ID_AA64MMFR1, VMIDBITS, 2); /* FEAT_VMID16 */
|
t = FIELD_DP64(t, ID_AA64MMFR1, VMIDBITS, 2); /* FEAT_VMID16 */
|
||||||
t = FIELD_DP64(t, ID_AA64MMFR1, VH, 1); /* FEAT_VHE */
|
t = FIELD_DP64(t, ID_AA64MMFR1, VH, 1); /* FEAT_VHE */
|
||||||
t = FIELD_DP64(t, ID_AA64MMFR1, HPDS, 1); /* FEAT_HPDS */
|
t = FIELD_DP64(t, ID_AA64MMFR1, HPDS, 1); /* FEAT_HPDS */
|
||||||
|
177
target/arm/ptw.c
177
target/arm/ptw.c
@ -9,6 +9,7 @@
|
|||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "qemu/log.h"
|
#include "qemu/log.h"
|
||||||
#include "qemu/range.h"
|
#include "qemu/range.h"
|
||||||
|
#include "qemu/main-loop.h"
|
||||||
#include "exec/exec-all.h"
|
#include "exec/exec-all.h"
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "internals.h"
|
#include "internals.h"
|
||||||
@ -21,7 +22,9 @@ typedef struct S1Translate {
|
|||||||
bool in_secure;
|
bool in_secure;
|
||||||
bool in_debug;
|
bool in_debug;
|
||||||
bool out_secure;
|
bool out_secure;
|
||||||
|
bool out_rw;
|
||||||
bool out_be;
|
bool out_be;
|
||||||
|
hwaddr out_virt;
|
||||||
hwaddr out_phys;
|
hwaddr out_phys;
|
||||||
void *out_host;
|
void *out_host;
|
||||||
} S1Translate;
|
} S1Translate;
|
||||||
@ -219,6 +222,8 @@ static bool S1_ptw_translate(CPUARMState *env, S1Translate *ptw,
|
|||||||
uint8_t pte_attrs;
|
uint8_t pte_attrs;
|
||||||
bool pte_secure;
|
bool pte_secure;
|
||||||
|
|
||||||
|
ptw->out_virt = addr;
|
||||||
|
|
||||||
if (unlikely(ptw->in_debug)) {
|
if (unlikely(ptw->in_debug)) {
|
||||||
/*
|
/*
|
||||||
* From gdbstub, do not use softmmu so that we don't modify the
|
* From gdbstub, do not use softmmu so that we don't modify the
|
||||||
@ -247,6 +252,7 @@ static bool S1_ptw_translate(CPUARMState *env, S1Translate *ptw,
|
|||||||
pte_secure = is_secure;
|
pte_secure = is_secure;
|
||||||
}
|
}
|
||||||
ptw->out_host = NULL;
|
ptw->out_host = NULL;
|
||||||
|
ptw->out_rw = false;
|
||||||
} else {
|
} else {
|
||||||
CPUTLBEntryFull *full;
|
CPUTLBEntryFull *full;
|
||||||
int flags;
|
int flags;
|
||||||
@ -261,6 +267,7 @@ static bool S1_ptw_translate(CPUARMState *env, S1Translate *ptw,
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
ptw->out_phys = full->phys_addr;
|
ptw->out_phys = full->phys_addr;
|
||||||
|
ptw->out_rw = full->prot & PAGE_WRITE;
|
||||||
pte_attrs = full->pte_attrs;
|
pte_attrs = full->pte_attrs;
|
||||||
pte_secure = full->attrs.secure;
|
pte_secure = full->attrs.secure;
|
||||||
}
|
}
|
||||||
@ -304,14 +311,16 @@ static uint32_t arm_ldl_ptw(CPUARMState *env, S1Translate *ptw,
|
|||||||
ARMMMUFaultInfo *fi)
|
ARMMMUFaultInfo *fi)
|
||||||
{
|
{
|
||||||
CPUState *cs = env_cpu(env);
|
CPUState *cs = env_cpu(env);
|
||||||
|
void *host = ptw->out_host;
|
||||||
uint32_t data;
|
uint32_t data;
|
||||||
|
|
||||||
if (likely(ptw->out_host)) {
|
if (likely(host)) {
|
||||||
/* Page tables are in RAM, and we have the host address. */
|
/* Page tables are in RAM, and we have the host address. */
|
||||||
|
data = qatomic_read((uint32_t *)host);
|
||||||
if (ptw->out_be) {
|
if (ptw->out_be) {
|
||||||
data = ldl_be_p(ptw->out_host);
|
data = be32_to_cpu(data);
|
||||||
} else {
|
} else {
|
||||||
data = ldl_le_p(ptw->out_host);
|
data = le32_to_cpu(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Page tables are in MMIO. */
|
/* Page tables are in MMIO. */
|
||||||
@ -337,15 +346,25 @@ static uint64_t arm_ldq_ptw(CPUARMState *env, S1Translate *ptw,
|
|||||||
ARMMMUFaultInfo *fi)
|
ARMMMUFaultInfo *fi)
|
||||||
{
|
{
|
||||||
CPUState *cs = env_cpu(env);
|
CPUState *cs = env_cpu(env);
|
||||||
|
void *host = ptw->out_host;
|
||||||
uint64_t data;
|
uint64_t data;
|
||||||
|
|
||||||
if (likely(ptw->out_host)) {
|
if (likely(host)) {
|
||||||
/* Page tables are in RAM, and we have the host address. */
|
/* Page tables are in RAM, and we have the host address. */
|
||||||
|
#ifdef CONFIG_ATOMIC64
|
||||||
|
data = qatomic_read__nocheck((uint64_t *)host);
|
||||||
if (ptw->out_be) {
|
if (ptw->out_be) {
|
||||||
data = ldq_be_p(ptw->out_host);
|
data = be64_to_cpu(data);
|
||||||
} else {
|
} else {
|
||||||
data = ldq_le_p(ptw->out_host);
|
data = le64_to_cpu(data);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (ptw->out_be) {
|
||||||
|
data = ldq_be_p(host);
|
||||||
|
} else {
|
||||||
|
data = ldq_le_p(host);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
/* Page tables are in MMIO. */
|
/* Page tables are in MMIO. */
|
||||||
MemTxAttrs attrs = { .secure = ptw->out_secure };
|
MemTxAttrs attrs = { .secure = ptw->out_secure };
|
||||||
@ -366,6 +385,91 @@ static uint64_t arm_ldq_ptw(CPUARMState *env, S1Translate *ptw,
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint64_t arm_casq_ptw(CPUARMState *env, uint64_t old_val,
|
||||||
|
uint64_t new_val, S1Translate *ptw,
|
||||||
|
ARMMMUFaultInfo *fi)
|
||||||
|
{
|
||||||
|
uint64_t cur_val;
|
||||||
|
void *host = ptw->out_host;
|
||||||
|
|
||||||
|
if (unlikely(!host)) {
|
||||||
|
fi->type = ARMFault_UnsuppAtomicUpdate;
|
||||||
|
fi->s1ptw = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Raising a stage2 Protection fault for an atomic update to a read-only
|
||||||
|
* page is delayed until it is certain that there is a change to make.
|
||||||
|
*/
|
||||||
|
if (unlikely(!ptw->out_rw)) {
|
||||||
|
int flags;
|
||||||
|
void *discard;
|
||||||
|
|
||||||
|
env->tlb_fi = fi;
|
||||||
|
flags = probe_access_flags(env, ptw->out_virt, MMU_DATA_STORE,
|
||||||
|
arm_to_core_mmu_idx(ptw->in_ptw_idx),
|
||||||
|
true, &discard, 0);
|
||||||
|
env->tlb_fi = NULL;
|
||||||
|
|
||||||
|
if (unlikely(flags & TLB_INVALID_MASK)) {
|
||||||
|
assert(fi->type != ARMFault_None);
|
||||||
|
fi->s2addr = ptw->out_virt;
|
||||||
|
fi->stage2 = true;
|
||||||
|
fi->s1ptw = true;
|
||||||
|
fi->s1ns = !ptw->in_secure;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In case CAS mismatches and we loop, remember writability. */
|
||||||
|
ptw->out_rw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_ATOMIC64
|
||||||
|
if (ptw->out_be) {
|
||||||
|
old_val = cpu_to_be64(old_val);
|
||||||
|
new_val = cpu_to_be64(new_val);
|
||||||
|
cur_val = qatomic_cmpxchg__nocheck((uint64_t *)host, old_val, new_val);
|
||||||
|
cur_val = be64_to_cpu(cur_val);
|
||||||
|
} else {
|
||||||
|
old_val = cpu_to_le64(old_val);
|
||||||
|
new_val = cpu_to_le64(new_val);
|
||||||
|
cur_val = qatomic_cmpxchg__nocheck((uint64_t *)host, old_val, new_val);
|
||||||
|
cur_val = le64_to_cpu(cur_val);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/*
|
||||||
|
* We can't support the full 64-bit atomic cmpxchg on the host.
|
||||||
|
* Because this is only used for FEAT_HAFDBS, which is only for AA64,
|
||||||
|
* we know that TCG_OVERSIZED_GUEST is set, which means that we are
|
||||||
|
* running in round-robin mode and could only race with dma i/o.
|
||||||
|
*/
|
||||||
|
#ifndef TCG_OVERSIZED_GUEST
|
||||||
|
# error "Unexpected configuration"
|
||||||
|
#endif
|
||||||
|
bool locked = qemu_mutex_iothread_locked();
|
||||||
|
if (!locked) {
|
||||||
|
qemu_mutex_lock_iothread();
|
||||||
|
}
|
||||||
|
if (ptw->out_be) {
|
||||||
|
cur_val = ldq_be_p(host);
|
||||||
|
if (cur_val == old_val) {
|
||||||
|
stq_be_p(host, new_val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur_val = ldq_le_p(host);
|
||||||
|
if (cur_val == old_val) {
|
||||||
|
stq_le_p(host, new_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!locked) {
|
||||||
|
qemu_mutex_unlock_iothread();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return cur_val;
|
||||||
|
}
|
||||||
|
|
||||||
static bool get_level1_table_address(CPUARMState *env, ARMMMUIdx mmu_idx,
|
static bool get_level1_table_address(CPUARMState *env, ARMMMUIdx mmu_idx,
|
||||||
uint32_t *table, uint32_t address)
|
uint32_t *table, uint32_t address)
|
||||||
{
|
{
|
||||||
@ -1058,7 +1162,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
|
|||||||
uint32_t el = regime_el(env, mmu_idx);
|
uint32_t el = regime_el(env, mmu_idx);
|
||||||
uint64_t descaddrmask;
|
uint64_t descaddrmask;
|
||||||
bool aarch64 = arm_el_is_aa64(env, el);
|
bool aarch64 = arm_el_is_aa64(env, el);
|
||||||
uint64_t descriptor;
|
uint64_t descriptor, new_descriptor;
|
||||||
bool nstable;
|
bool nstable;
|
||||||
|
|
||||||
/* TODO: This code does not support shareability levels. */
|
/* TODO: This code does not support shareability levels. */
|
||||||
@ -1272,7 +1376,9 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
|
|||||||
if (fi->type != ARMFault_None) {
|
if (fi->type != ARMFault_None) {
|
||||||
goto do_fault;
|
goto do_fault;
|
||||||
}
|
}
|
||||||
|
new_descriptor = descriptor;
|
||||||
|
|
||||||
|
restart_atomic_update:
|
||||||
if (!(descriptor & 1) || (!(descriptor & 2) && (level == 3))) {
|
if (!(descriptor & 1) || (!(descriptor & 2) && (level == 3))) {
|
||||||
/* Invalid, or the Reserved level 3 encoding */
|
/* Invalid, or the Reserved level 3 encoding */
|
||||||
goto do_translation_fault;
|
goto do_translation_fault;
|
||||||
@ -1318,17 +1424,36 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
|
|||||||
* to give a correct page or table address, the address field
|
* to give a correct page or table address, the address field
|
||||||
* in a block descriptor is smaller; so we need to explicitly
|
* in a block descriptor is smaller; so we need to explicitly
|
||||||
* clear the lower bits here before ORing in the low vaddr bits.
|
* clear the lower bits here before ORing in the low vaddr bits.
|
||||||
|
*
|
||||||
|
* Afterward, descaddr is the final physical address.
|
||||||
*/
|
*/
|
||||||
page_size = (1ULL << ((stride * (4 - level)) + 3));
|
page_size = (1ULL << ((stride * (4 - level)) + 3));
|
||||||
descaddr &= ~(hwaddr)(page_size - 1);
|
descaddr &= ~(hwaddr)(page_size - 1);
|
||||||
descaddr |= (address & (page_size - 1));
|
descaddr |= (address & (page_size - 1));
|
||||||
|
|
||||||
|
if (likely(!ptw->in_debug)) {
|
||||||
|
/*
|
||||||
|
* Access flag.
|
||||||
|
* If HA is enabled, prepare to update the descriptor below.
|
||||||
|
* Otherwise, pass the access fault on to software.
|
||||||
|
*/
|
||||||
|
if (!(descriptor & (1 << 10))) {
|
||||||
|
if (param.ha) {
|
||||||
|
new_descriptor |= 1 << 10; /* AF */
|
||||||
|
} else {
|
||||||
|
fi->type = ARMFault_AccessFlag;
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extract attributes from the descriptor, and apply table descriptors.
|
* Extract attributes from the (modified) descriptor, and apply
|
||||||
* Stage 2 table descriptors do not include any attribute fields.
|
* table descriptors. Stage 2 table descriptors do not include
|
||||||
* HPD disables all the table attributes except NSTable.
|
* any attribute fields. HPD disables all the table attributes
|
||||||
|
* except NSTable.
|
||||||
*/
|
*/
|
||||||
attrs = descriptor & (MAKE_64BIT_MASK(2, 10) | MAKE_64BIT_MASK(50, 14));
|
attrs = new_descriptor & (MAKE_64BIT_MASK(2, 10) | MAKE_64BIT_MASK(50, 14));
|
||||||
if (!regime_is_stage2(mmu_idx)) {
|
if (!regime_is_stage2(mmu_idx)) {
|
||||||
attrs |= nstable << 5; /* NS */
|
attrs |= nstable << 5; /* NS */
|
||||||
if (!param.hpd) {
|
if (!param.hpd) {
|
||||||
@ -1342,18 +1467,7 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Here descaddr is the final physical address, and attributes
|
|
||||||
* are all in attrs.
|
|
||||||
*/
|
|
||||||
if ((attrs & (1 << 10)) == 0) {
|
|
||||||
/* Access flag */
|
|
||||||
fi->type = ARMFault_AccessFlag;
|
|
||||||
goto do_fault;
|
|
||||||
}
|
|
||||||
|
|
||||||
ap = extract32(attrs, 6, 2);
|
ap = extract32(attrs, 6, 2);
|
||||||
|
|
||||||
if (regime_is_stage2(mmu_idx)) {
|
if (regime_is_stage2(mmu_idx)) {
|
||||||
ns = mmu_idx == ARMMMUIdx_Stage2;
|
ns = mmu_idx == ARMMMUIdx_Stage2;
|
||||||
xn = extract64(attrs, 53, 2);
|
xn = extract64(attrs, 53, 2);
|
||||||
@ -1370,6 +1484,25 @@ static bool get_phys_addr_lpae(CPUARMState *env, S1Translate *ptw,
|
|||||||
goto do_fault;
|
goto do_fault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If FEAT_HAFDBS has made changes, update the PTE. */
|
||||||
|
if (new_descriptor != descriptor) {
|
||||||
|
new_descriptor = arm_casq_ptw(env, descriptor, new_descriptor, ptw, fi);
|
||||||
|
if (fi->type != ARMFault_None) {
|
||||||
|
goto do_fault;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* I_YZSVV says that if the in-memory descriptor has changed,
|
||||||
|
* then we must use the information in that new value
|
||||||
|
* (which might include a different output address, different
|
||||||
|
* attributes, or generate a fault).
|
||||||
|
* Restart the handling of the descriptor value from scratch.
|
||||||
|
*/
|
||||||
|
if (new_descriptor != descriptor) {
|
||||||
|
descriptor = new_descriptor;
|
||||||
|
goto restart_atomic_update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ns) {
|
if (ns) {
|
||||||
/*
|
/*
|
||||||
* The NS bit will (as required by the architecture) have no effect if
|
* The NS bit will (as required by the architecture) have no effect if
|
||||||
|
Loading…
Reference in New Issue
Block a user