cputlb: Fold TLB_RECHECK into TLB_INVALID_MASK

We had two different mechanisms to force a recheck of the tlb.

Before TLB_RECHECK was introduced, we had a PAGE_WRITE_INV bit
that would immediate set TLB_INVALID_MASK, which automatically
means that a second check of the tlb entry fails.

We can use the same mechanism to handle small pages.
Conserve TLB_* bits by removing TLB_RECHECK.

Backports commit 30d7e098d5c38644359820317fcf72e3e129ec53 from qemu
This commit is contained in:
Richard Henderson 2020-01-14 06:17:15 -05:00 committed by Lioncash
parent f7b61b95f0
commit 6c4a3fd06f
2 changed files with 70 additions and 123 deletions

View file

@ -379,11 +379,8 @@ void tlb_set_page_with_attrs(CPUState *cpu, target_ulong vaddr,
address = vaddr_page; address = vaddr_page;
if (size < TARGET_PAGE_SIZE) { if (size < TARGET_PAGE_SIZE) {
/* /* Repeat the MMU check and TLB fill on every access. */
* Slow-path the TLB entries; we will repeat the MMU check and TLB address |= TLB_INVALID_MASK;
* fill on every access.
*/
address |= TLB_RECHECK;
} }
if (attrs.byte_swap) { if (attrs.byte_swap) {
/* Force the access through the I/O slow path. */ /* Force the access through the I/O slow path. */
@ -495,10 +492,59 @@ static void tlb_fill(CPUState *cpu, target_ulong addr, int size,
assert(ok); assert(ok);
} }
/* NOTE: this function can trigger an exception */ /* Macro to call the above, with local variables from the use context. */
/* NOTE2: the returned address is not exactly the physical address: it #define VICTIM_TLB_HIT(TY, ADDR) \
* is actually a ram_addr_t (in system mode; the user mode emulation victim_tlb_hit(env, mmu_idx, index, offsetof(CPUTLBEntry, TY), \
* version of this function returns a guest virtual address). (ADDR) & TARGET_PAGE_MASK)
static inline target_ulong tlb_read_ofs(CPUTLBEntry *entry, size_t ofs)
{
#if TCG_OVERSIZED_GUEST
return *(target_ulong *)((uintptr_t)entry + ofs);
#else
/* ofs might correspond to .addr_write, so use atomic_read */
return atomic_read((target_ulong *)((uintptr_t)entry + ofs));
#endif
}
/* Return true if ADDR is present in the victim tlb, and has been copied
back to the main tlb. */
static bool victim_tlb_hit(CPUArchState *env, size_t mmu_idx, size_t index,
size_t elt_ofs, target_ulong page)
{
size_t vidx;
for (vidx = 0; vidx < CPU_VTLB_SIZE; ++vidx) {
CPUTLBEntry *vtlb = &env->tlb_v_table[mmu_idx][vidx];
/* elt_ofs might correspond to .addr_write, so use atomic_read */
target_ulong cmp = tlb_read_ofs(vtlb, elt_ofs);
if (cmp == page) {
/* Found entry in victim tlb, swap tlb and iotlb. */
CPUTLBEntry tmptlb, *tlb = &env->tlb_table[mmu_idx][index];
copy_tlb_helper_locked(&tmptlb, tlb);
copy_tlb_helper_locked(tlb, vtlb);
copy_tlb_helper_locked(vtlb, &tmptlb);
CPUIOTLBEntry tmpio, *io = &env->iotlb[mmu_idx][index];
CPUIOTLBEntry *vio = &env->iotlb_v[mmu_idx][vidx];
tmpio = *io; *io = *vio; *vio = tmpio;
return true;
}
}
return false;
}
/*
* Return a ram_addr_t for the virtual address for execution.
*
* Return -1 if we can't translate and execute from an entire page
* of RAM. This will force us to execute by loading and translating
* one insn at a time, without caching.
*
* NOTE: This function will trigger an exception if the page is
* not executable.
*/ */
tb_page_addr_t get_page_addr_code(CPUArchState *env, target_ulong addr) tb_page_addr_t get_page_addr_code(CPUArchState *env, target_ulong addr)
{ {
@ -519,32 +565,21 @@ tb_page_addr_t get_page_addr_code(CPUArchState *env, target_ulong addr)
if (env->invalid_error == UC_ERR_FETCH_PROT) { if (env->invalid_error == UC_ERR_FETCH_PROT) {
return RAM_ADDR_INVALID; return RAM_ADDR_INVALID;
} }
}
if (unlikely(env->tlb_table[mmu_idx][index].addr_code & TLB_RECHECK)) {
/*
* This is a TLB_RECHECK access, where the MMU protection
* covers a smaller range than a target page, and we must
* repeat the MMU check here. This tlb_fill() call might
* longjump out if this access should cause a guest exception.
*/
int index;
target_ulong tlb_addr;
tlb_fill(cpu, addr, 0, MMU_INST_FETCH, mmu_idx, 0);
if (!VICTIM_TLB_HIT(addr_code, addr)) {
tlb_fill(env_cpu(env), addr, 0, MMU_INST_FETCH, mmu_idx, 0);
index = tlb_index(env, mmu_idx, addr); index = tlb_index(env, mmu_idx, addr);
entry = tlb_entry(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr);
tlb_addr = env->tlb_table[mmu_idx][index].addr_code;
if (!(tlb_addr & ~(TARGET_PAGE_MASK | TLB_RECHECK))) { if (unlikely(entry->addr_code & TLB_INVALID_MASK)) {
/* RAM access. We can't handle this, so for now just stop */
cpu_abort(cpu, "Unable to handle guest executing from RAM within "
"a small MPU region at 0x" TARGET_FMT_lx, addr);
}
/* /*
* Fall through to handle IO accesses (which will almost certainly * The MMU protection covers a smaller range than a target
* also result in failure) * page, so we must redo the MMU check for every insn.
*/ */
return -1;
}
}
assert(tlb_hit(entry->addr_code, addr));
} }
iotlbentry = &env->iotlb[mmu_idx][index]; iotlbentry = &env->iotlb[mmu_idx][index];
@ -659,50 +694,6 @@ static void io_writex(CPUArchState *env, CPUIOTLBEntry *iotlbentry,
} }
} }
static inline target_ulong tlb_read_ofs(CPUTLBEntry *entry, size_t ofs)
{
#if TCG_OVERSIZED_GUEST
return *(target_ulong *)((uintptr_t)entry + ofs);
#else
/* ofs might correspond to .addr_write, so use atomic_read */
return atomic_read((target_ulong *)((uintptr_t)entry + ofs));
#endif
}
/* Return true if ADDR is present in the victim tlb, and has been copied
back to the main tlb. */
static bool victim_tlb_hit(CPUArchState *env, size_t mmu_idx, size_t index,
size_t elt_ofs, target_ulong page)
{
size_t vidx;
for (vidx = 0; vidx < CPU_VTLB_SIZE; ++vidx) {
CPUTLBEntry *vtlb = &env->tlb_v_table[mmu_idx][vidx];
/* elt_ofs might correspond to .addr_write, so use atomic_read */
target_ulong cmp = tlb_read_ofs(vtlb, elt_ofs);
if (cmp == page) {
/* Found entry in victim tlb, swap tlb and iotlb. */
CPUTLBEntry tmptlb, *tlb = &env->tlb_table[mmu_idx][index];
copy_tlb_helper_locked(&tmptlb, tlb);
copy_tlb_helper_locked(tlb, vtlb);
copy_tlb_helper_locked(vtlb, &tmptlb);
CPUIOTLBEntry tmpio, *io = &env->iotlb[mmu_idx][index];
CPUIOTLBEntry *vio = &env->iotlb_v[mmu_idx][vidx];
tmpio = *io; *io = *vio; *vio = tmpio;
return true;
}
}
return false;
}
/* Macro to call the above, with local variables from the use context. */
#define VICTIM_TLB_HIT(TY, ADDR) \
victim_tlb_hit(env, mmu_idx, index, offsetof(CPUTLBEntry, TY), \
(ADDR) & TARGET_PAGE_MASK)
/* Probe for whether the specified guest write access is permitted. /* Probe for whether the specified guest write access is permitted.
* If it is not permitted then an exception will be taken in the same * If it is not permitted then an exception will be taken in the same
* way as if this were a real write access (and we will not return). * way as if this were a real write access (and we will not return).
@ -824,7 +815,7 @@ static void *atomic_mmu_lookup(CPUArchState *env, target_ulong addr,
} }
/* Notice an IO access or a needs-MMU-lookup access */ /* Notice an IO access or a needs-MMU-lookup access */
if (unlikely(tlb_addr & (TLB_MMIO | TLB_RECHECK))) { if (unlikely(tlb_addr & TLB_MMIO)) {
/* There's really nothing that can be done to /* There's really nothing that can be done to
support this apart from stop-the-world. */ support this apart from stop-the-world. */
goto stop_the_world; goto stop_the_world;
@ -997,6 +988,7 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi,
entry = tlb_entry(env, mmu_idx, addr); entry = tlb_entry(env, mmu_idx, addr);
} }
tlb_addr = code_read ? entry->addr_code : entry->addr_read; tlb_addr = code_read ? entry->addr_code : entry->addr_read;
tlb_addr &= ~TLB_INVALID_MASK;
} }
/* Handle an IO access. */ /* Handle an IO access. */
@ -1005,26 +997,6 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi,
goto do_unaligned_access; goto do_unaligned_access;
} }
if (tlb_addr & TLB_RECHECK) {
/*
* This is a TLB_RECHECK access, where the MMU protection
* covers a smaller range than a target page, and we must
* repeat the MMU check here. This tlb_fill() call might
* longjump out if this access should cause a guest exception.
*/
tlb_fill(env_cpu(env), addr, size,
access_type, mmu_idx, retaddr);
index = tlb_index(env, mmu_idx, addr);
entry = tlb_entry(env, mmu_idx, addr);
tlb_addr = code_read ? entry->addr_code : entry->addr_read;
tlb_addr &= ~TLB_RECHECK;
if (!(tlb_addr & ~TARGET_PAGE_MASK)) {
/* RAM access */
goto do_aligned_access;
}
}
return io_readx(env, &env->iotlb[mmu_idx][index], return io_readx(env, &env->iotlb[mmu_idx][index],
mmu_idx, addr, retaddr, access_type, op); mmu_idx, addr, retaddr, access_type, op);
} }
@ -1054,7 +1026,6 @@ load_helper(CPUArchState *env, target_ulong addr, TCGMemOpIdx oi,
goto finished; goto finished;
} }
do_aligned_access:
haddr = (void *)((uintptr_t)addr + entry->addend); haddr = (void *)((uintptr_t)addr + entry->addend);
switch (op) { switch (op) {
case MO_UB: case MO_UB:
@ -1324,26 +1295,6 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val,
goto do_unaligned_access; goto do_unaligned_access;
} }
if (tlb_addr & TLB_RECHECK) {
/*
* This is a TLB_RECHECK access, where the MMU protection
* covers a smaller range than a target page, and we must
* repeat the MMU check here. This tlb_fill() call might
* longjump out if this access should cause a guest exception.
*/
tlb_fill(env_cpu(env), addr, size, MMU_DATA_STORE,
mmu_idx, retaddr);
index = tlb_index(env, mmu_idx, addr);
entry = tlb_entry(env, mmu_idx, addr);
tlb_addr = tlb_addr_write(entry);
tlb_addr &= ~TLB_RECHECK;
if (!(tlb_addr & ~TARGET_PAGE_MASK)) {
/* RAM access */
goto do_aligned_access;
}
}
io_writex(env, &env->iotlb[mmu_idx][index], mmu_idx, io_writex(env, &env->iotlb[mmu_idx][index], mmu_idx,
val, addr, retaddr, op); val, addr, retaddr, op);
return; return;
@ -1395,7 +1346,6 @@ store_helper(CPUArchState *env, target_ulong addr, uint64_t val,
return; return;
} }
do_aligned_access:
haddr = (void *)((uintptr_t)addr + entry->addend); haddr = (void *)((uintptr_t)addr + entry->addend);
switch (op) { switch (op) {
case MO_UB: case MO_UB:

View file

@ -330,14 +330,11 @@ CPUArchState *cpu_copy(CPUArchState *env);
#define TLB_NOTDIRTY (1 << (TARGET_PAGE_BITS - 2)) #define TLB_NOTDIRTY (1 << (TARGET_PAGE_BITS - 2))
/* Set if TLB entry is an IO callback. */ /* Set if TLB entry is an IO callback. */
#define TLB_MMIO (1 << (TARGET_PAGE_BITS - 3)) #define TLB_MMIO (1 << (TARGET_PAGE_BITS - 3))
/* Set if TLB entry must have MMU lookup repeated for every access */
#define TLB_RECHECK (1 << (TARGET_PAGE_BITS - 4))
/* Use this mask to check interception with an alignment mask /* Use this mask to check interception with an alignment mask
* in a TCG backend. * in a TCG backend.
*/ */
#define TLB_FLAGS_MASK (TLB_INVALID_MASK | TLB_NOTDIRTY | TLB_MMIO \ #define TLB_FLAGS_MASK (TLB_INVALID_MASK | TLB_NOTDIRTY | TLB_MMIO)
| TLB_RECHECK)
/** /**
* tlb_hit_page: return true if page aligned @addr is a hit against the * tlb_hit_page: return true if page aligned @addr is a hit against the