mirror of
https://github.com/dingusdev/dingusppc.git
synced 2026-10-24 10:02:18 +00:00
ppcmmu: Track translated entries for invalidation
XNU frequently changes BAT and PAT mappings for physical-memory access. Each resulting context synchronization can walk tens of thousands of TLB entries, which consumes a lot of time. Record the instruction and data TLB slots populated by BAT or PAT and invalidate only those slots. Pending invalidations are coalesced by source, and BAT updates cover PAT-derived entries because a new BAT can shadow a page mapping. Saves another 0.7 secondss in the time it takes to boot Mac OS X 10.3 to the WindowServer (compared to the generational PAT invalidation).
This commit is contained in:
1 parent
855f43c2eb
commit
13ccd42e16
2 files changed
+140
-131
No files matched your search
+118
-131
@@ -31,6 +31,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include <cinttypes>
|
||||
#include <loguru.hpp>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
//#define MMU_PROFILING // uncomment this to enable MMU profiling
|
||||
//#define TLB_PROFILING // uncomment this to enable SoftTLB profiling
|
||||
@@ -471,8 +472,12 @@ enum TLBFlags : uint16_t {
|
||||
TLBE_FROM_PAT = 1 << 4, // TLB entry has been translated with PAT
|
||||
PAGE_WRITABLE = 1 << 5, // page is writable
|
||||
PTE_SET_C = 1 << 6, // tells if C bit of the PTE needs to be updated
|
||||
TLBE_CTX_TRACKED = 1 << 7, // entry pointer is in gTrackedIEntries or gTrackedDEntries
|
||||
};
|
||||
|
||||
constexpr uint16_t TLBE_FROM_TRANSLATION =
|
||||
TLBFlags::TLBE_FROM_BAT | TLBFlags::TLBE_FROM_PAT;
|
||||
|
||||
typedef struct TLBEntry {
|
||||
uint32_t tag;
|
||||
uint16_t flags;
|
||||
@@ -491,6 +496,91 @@ typedef struct TLBEntry {
|
||||
uint32_t reserved;
|
||||
} TLBEntry;
|
||||
|
||||
// Track slots populated by either page or block address translation. Context
|
||||
// changes can then invalidate only populated slots without adding a generation
|
||||
// check to every TLB lookup.
|
||||
static std::vector<TLBEntry *> gTrackedIEntries;
|
||||
static std::vector<TLBEntry *> gTrackedDEntries;
|
||||
|
||||
// TLBE_FROM_* sources awaiting invalidation at the next context sync.
|
||||
static uint16_t gPendingIInvalidationSources = 0;
|
||||
static uint16_t gPendingDInvalidationSources = 0;
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
static void track_translated_entry(TLBEntry *tlb_entry)
|
||||
{
|
||||
if (!(tlb_entry->flags & TLBE_FROM_TRANSLATION) ||
|
||||
(tlb_entry->flags & TLBFlags::TLBE_CTX_TRACKED))
|
||||
return;
|
||||
|
||||
tlb_entry->flags |= TLBFlags::TLBE_CTX_TRACKED;
|
||||
if (tlb_type == TLBType::ITLB)
|
||||
gTrackedIEntries.push_back(tlb_entry);
|
||||
else
|
||||
gTrackedDEntries.push_back(tlb_entry);
|
||||
}
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
static void tlb_invalidate_tracked_entries()
|
||||
{
|
||||
uint16_t *pending_sources;
|
||||
std::vector<TLBEntry *> *tracked_entries;
|
||||
|
||||
if (tlb_type == TLBType::ITLB) {
|
||||
pending_sources = &gPendingIInvalidationSources;
|
||||
tracked_entries = &gTrackedIEntries;
|
||||
} else {
|
||||
pending_sources = &gPendingDInvalidationSources;
|
||||
tracked_entries = &gTrackedDEntries;
|
||||
}
|
||||
|
||||
if (!*pending_sources)
|
||||
return;
|
||||
|
||||
size_t retained_count = 0;
|
||||
for (TLBEntry *tlb_entry : *tracked_entries) {
|
||||
uint16_t source = tlb_entry->flags & TLBE_FROM_TRANSLATION;
|
||||
if (source & *pending_sources) {
|
||||
tlb_entry->tag = TLB_INVALID_TAG;
|
||||
tlb_entry->flags &= ~TLBFlags::TLBE_CTX_TRACKED;
|
||||
} else if (source) {
|
||||
(*tracked_entries)[retained_count++] = tlb_entry;
|
||||
} else {
|
||||
tlb_entry->flags &= ~TLBFlags::TLBE_CTX_TRACKED;
|
||||
}
|
||||
}
|
||||
tracked_entries->resize(retained_count);
|
||||
*pending_sources = 0;
|
||||
}
|
||||
|
||||
static void schedule_tlb_invalidation(TLBType tlb_type, uint16_t sources_to_invalidate)
|
||||
{
|
||||
uint16_t *pending_sources;
|
||||
|
||||
if (tlb_type == TLBType::ITLB)
|
||||
pending_sources = &gPendingIInvalidationSources;
|
||||
else
|
||||
pending_sources = &gPendingDInvalidationSources;
|
||||
|
||||
if (!*pending_sources) {
|
||||
if (tlb_type == TLBType::ITLB)
|
||||
add_ctx_sync_action(&tlb_invalidate_tracked_entries<TLBType::ITLB>);
|
||||
else
|
||||
add_ctx_sync_action(&tlb_invalidate_tracked_entries<TLBType::DTLB>);
|
||||
}
|
||||
*pending_sources |= sources_to_invalidate;
|
||||
}
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
static void promote_tlb_entry(TLBEntry *tlb1_entry, const TLBEntry *tlb2_entry)
|
||||
{
|
||||
uint16_t tracked = tlb1_entry->flags & TLBFlags::TLBE_CTX_TRACKED;
|
||||
*tlb1_entry = *tlb2_entry;
|
||||
tlb1_entry->flags &= ~TLBFlags::TLBE_CTX_TRACKED;
|
||||
tlb1_entry->flags |= tracked;
|
||||
track_translated_entry<tlb_type>(tlb1_entry);
|
||||
}
|
||||
|
||||
// primary ITLB for all MMU modes
|
||||
static std::array<TLBEntry, TLB_SIZE> itlb1_mode1;
|
||||
static std::array<TLBEntry, TLB_SIZE> itlb1_mode2;
|
||||
@@ -684,10 +774,12 @@ static TLBEntry* itlb2_refill(uint32_t guest_va)
|
||||
const uint32_t tag = guest_va & ~0xFFFUL;
|
||||
tlb_entry = tlb2_target_entry<TLBType::ITLB>(tag);
|
||||
tlb_entry->tag = tag;
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_MEM;
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_MEM |
|
||||
(tlb_entry->flags & TLBFlags::TLBE_CTX_TRACKED);
|
||||
tlb_entry->host_va_offs_r = (int64_t)rgn_desc->mem_ptr - guest_va +
|
||||
(phys_addr - rgn_desc->start);
|
||||
tlb_entry->phys_tag = phys_addr & ~0xFFFUL;
|
||||
track_translated_entry<TLBType::ITLB>(tlb_entry);
|
||||
} else {
|
||||
ABORT_F("Instruction fetch from unmapped memory at 0x%08X!\n", phys_addr);
|
||||
}
|
||||
@@ -756,11 +848,13 @@ static TLBEntry* dtlb2_refill(uint32_t guest_va, int is_write, bool is_dbg = fal
|
||||
tlb_entry = tlb2_target_entry<TLBType::DTLB>(tag);
|
||||
tlb_entry->tag = tag;
|
||||
if (rgn_desc->type & RT_MMIO) { // MMIO region
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_IO;
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_IO |
|
||||
(tlb_entry->flags & TLBFlags::TLBE_CTX_TRACKED);
|
||||
tlb_entry->rgn_desc = rgn_desc;
|
||||
tlb_entry->dev_base_va = guest_va - (phys_addr - rgn_desc->start);
|
||||
} else { // memory region backed by host memory
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_MEM;
|
||||
tlb_entry->flags = flags | TLBFlags::PAGE_MEM |
|
||||
(tlb_entry->flags & TLBFlags::TLBE_CTX_TRACKED);
|
||||
tlb_entry->host_va_offs_r = (int64_t)rgn_desc->mem_ptr - guest_va +
|
||||
(phys_addr - rgn_desc->start);
|
||||
if (rgn_desc->type == RT_ROM) {
|
||||
@@ -771,6 +865,7 @@ static TLBEntry* dtlb2_refill(uint32_t guest_va, int is_write, bool is_dbg = fal
|
||||
}
|
||||
}
|
||||
tlb_entry->phys_tag = phys_addr & ~0xFFFUL;
|
||||
track_translated_entry<TLBType::DTLB>(tlb_entry);
|
||||
return tlb_entry;
|
||||
} else {
|
||||
if (!is_dbg) {
|
||||
@@ -930,10 +1025,7 @@ uint8_t *mmu_translate_imem(uint32_t vaddr, uint32_t *paddr)
|
||||
}
|
||||
#endif
|
||||
// refill the primary ITLB
|
||||
tlb1_entry->tag = tag;
|
||||
tlb1_entry->flags = tlb2_entry->flags;
|
||||
tlb1_entry->host_va_offs_r = tlb2_entry->host_va_offs_r;
|
||||
tlb1_entry->phys_tag = tlb2_entry->phys_tag;
|
||||
promote_tlb_entry<TLBType::ITLB>(tlb1_entry, tlb2_entry);
|
||||
host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + vaddr);
|
||||
}
|
||||
|
||||
@@ -980,100 +1072,6 @@ void tlb_flush_entry(uint32_t ea)
|
||||
tlb_flush_secondary_entry(dtlb2_mode3, tag);
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
static void tlb_flush_entries(std::array<TLBEntry, N> &tlb, TLBFlags type) {
|
||||
for (auto &tlb_el : tlb) {
|
||||
if (tlb_el.tag != TLB_INVALID_TAG && tlb_el.flags & type) {
|
||||
tlb_el.tag = TLB_INVALID_TAG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
void tlb_flush_entries(TLBFlags type)
|
||||
{
|
||||
// Mode 1 is real addressing and thus can't contain any PAT entries by definition.
|
||||
bool flush_mode1 = type != TLBE_FROM_PAT;
|
||||
if (tlb_type == TLBType::ITLB) {
|
||||
if (flush_mode1) {
|
||||
tlb_flush_entries(itlb1_mode1, type);
|
||||
}
|
||||
tlb_flush_entries(itlb1_mode2, type);
|
||||
tlb_flush_entries(itlb1_mode3, type);
|
||||
if (flush_mode1) {
|
||||
tlb_flush_entries(itlb2_mode1, type);
|
||||
}
|
||||
tlb_flush_entries(itlb2_mode2, type);
|
||||
tlb_flush_entries(itlb2_mode3, type);
|
||||
} else {
|
||||
if (flush_mode1) {
|
||||
tlb_flush_entries(dtlb1_mode1, type);
|
||||
}
|
||||
tlb_flush_entries(dtlb1_mode2, type);
|
||||
tlb_flush_entries(dtlb1_mode3, type);
|
||||
if (flush_mode1) {
|
||||
tlb_flush_entries(dtlb2_mode1, type);
|
||||
}
|
||||
tlb_flush_entries(dtlb2_mode2, type);
|
||||
tlb_flush_entries(dtlb2_mode3, type);
|
||||
}
|
||||
}
|
||||
|
||||
static bool gTLBFlushIBatEntries = false;
|
||||
static bool gTLBFlushDBatEntries = false;
|
||||
static bool gTLBFlushIPatEntries = false;
|
||||
static bool gTLBFlushDPatEntries = false;
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
void tlb_flush_bat_entries()
|
||||
{
|
||||
if (tlb_type == TLBType::ITLB) {
|
||||
if (!gTLBFlushIBatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::ITLB>(TLBE_FROM_BAT);
|
||||
gTLBFlushIBatEntries = false;
|
||||
} else {
|
||||
if (!gTLBFlushDBatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::DTLB>(TLBE_FROM_BAT);
|
||||
gTLBFlushDBatEntries = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
void tlb_flush_pat_entries()
|
||||
{
|
||||
if (tlb_type == TLBType::ITLB) {
|
||||
if (!gTLBFlushIPatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::ITLB>(TLBE_FROM_PAT);
|
||||
gTLBFlushIPatEntries = false;
|
||||
} else {
|
||||
if (!gTLBFlushDPatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::DTLB>(TLBE_FROM_PAT);
|
||||
gTLBFlushDPatEntries = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <const TLBType tlb_type>
|
||||
void tlb_flush_all_entries()
|
||||
{
|
||||
if (tlb_type == TLBType::ITLB) {
|
||||
if (!gTLBFlushIBatEntries && !gTLBFlushIPatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::ITLB>((TLBFlags)(TLBE_FROM_BAT | TLBE_FROM_PAT));
|
||||
gTLBFlushIBatEntries = false;
|
||||
gTLBFlushIPatEntries = false;
|
||||
} else {
|
||||
if (!gTLBFlushDBatEntries && !gTLBFlushDPatEntries)
|
||||
return;
|
||||
tlb_flush_entries<TLBType::DTLB>((TLBFlags)(TLBE_FROM_BAT | TLBE_FROM_PAT));
|
||||
gTLBFlushDBatEntries = false;
|
||||
gTLBFlushDPatEntries = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mpc601_bat_update(uint32_t bat_reg)
|
||||
{
|
||||
PPC_BAT_entry *ibat_entry, *dbat_entry;
|
||||
@@ -1104,15 +1102,9 @@ static void mpc601_bat_update(uint32_t bat_reg)
|
||||
dbat_entry->valid = false;
|
||||
}
|
||||
|
||||
// MPC601 has unified BATs so we're going to flush both ITLB and DTLB
|
||||
if (!gTLBFlushIBatEntries || !gTLBFlushIPatEntries || !gTLBFlushDBatEntries || !gTLBFlushDPatEntries) {
|
||||
gTLBFlushIBatEntries = true;
|
||||
gTLBFlushIPatEntries = true;
|
||||
gTLBFlushDBatEntries = true;
|
||||
gTLBFlushDPatEntries = true;
|
||||
add_ctx_sync_action(&tlb_flush_all_entries<TLBType::ITLB>);
|
||||
add_ctx_sync_action(&tlb_flush_all_entries<TLBType::DTLB>);
|
||||
}
|
||||
// MPC601 has unified BATs, so they affect both translation contexts.
|
||||
schedule_tlb_invalidation(TLBType::ITLB, TLBE_FROM_TRANSLATION);
|
||||
schedule_tlb_invalidation(TLBType::DTLB, TLBE_FROM_TRANSLATION);
|
||||
}
|
||||
|
||||
static void mpc601_dbat_update(uint32_t /*bat_reg*/)
|
||||
@@ -1138,11 +1130,8 @@ static void ppc_ibat_update(uint32_t bat_reg)
|
||||
bat_entry->phys_hi = ppc_state.spr[upper_reg_num + 1] & hi_mask;
|
||||
bat_entry->bepi = ppc_state.spr[upper_reg_num] & hi_mask;
|
||||
|
||||
if (!gTLBFlushIBatEntries || !gTLBFlushIPatEntries) {
|
||||
gTLBFlushIBatEntries = true;
|
||||
gTLBFlushIPatEntries = true;
|
||||
add_ctx_sync_action(&tlb_flush_all_entries<TLBType::ITLB>);
|
||||
}
|
||||
// A new IBAT can shadow an existing page translation.
|
||||
schedule_tlb_invalidation(TLBType::ITLB, TLBE_FROM_TRANSLATION);
|
||||
}
|
||||
|
||||
static void ppc_dbat_update(uint32_t bat_reg)
|
||||
@@ -1163,23 +1152,16 @@ static void ppc_dbat_update(uint32_t bat_reg)
|
||||
bat_entry->phys_hi = ppc_state.spr[upper_reg_num + 1] & hi_mask;
|
||||
bat_entry->bepi = ppc_state.spr[upper_reg_num] & hi_mask;
|
||||
|
||||
if (!gTLBFlushDBatEntries || !gTLBFlushDPatEntries) {
|
||||
gTLBFlushDBatEntries = true;
|
||||
gTLBFlushDPatEntries = true;
|
||||
add_ctx_sync_action(&tlb_flush_all_entries<TLBType::DTLB>);
|
||||
}
|
||||
// A new DBAT can shadow an existing page translation.
|
||||
schedule_tlb_invalidation(TLBType::DTLB, TLBE_FROM_TRANSLATION);
|
||||
}
|
||||
|
||||
void mmu_pat_ctx_changed()
|
||||
{
|
||||
// Page address translation context changed so we need to flush
|
||||
// all PAT entries from both ITLB and DTLB
|
||||
if (!gTLBFlushIPatEntries || !gTLBFlushDPatEntries) {
|
||||
gTLBFlushIPatEntries = true;
|
||||
gTLBFlushDPatEntries = true;
|
||||
add_ctx_sync_action(&tlb_flush_pat_entries<TLBType::ITLB>);
|
||||
add_ctx_sync_action(&tlb_flush_pat_entries<TLBType::DTLB>);
|
||||
}
|
||||
// Page address translation context changed so invalidate all PAT entries
|
||||
// from both ITLB and DTLB.
|
||||
schedule_tlb_invalidation(TLBType::ITLB, TLBFlags::TLBE_FROM_PAT);
|
||||
schedule_tlb_invalidation(TLBType::DTLB, TLBFlags::TLBE_FROM_PAT);
|
||||
}
|
||||
|
||||
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
||||
@@ -1265,7 +1247,7 @@ inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va)
|
||||
|
||||
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
||||
// refill the primary TLB
|
||||
*tlb1_entry = *tlb2_entry;
|
||||
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
||||
|
||||
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
||||
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
||||
@@ -1442,7 +1424,7 @@ inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value)
|
||||
|
||||
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
||||
// refill the primary TLB
|
||||
*tlb1_entry = *tlb2_entry;
|
||||
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
||||
|
||||
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
||||
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
||||
@@ -1968,7 +1950,7 @@ bool mmu_translate_dbg(uint32_t guest_va, uint32_t &guest_pa) {
|
||||
|
||||
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
||||
// refill the primary TLB
|
||||
*tlb1_entry = *tlb2_entry;
|
||||
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
||||
}
|
||||
else {
|
||||
tlb1_entry = tlb2_entry;
|
||||
@@ -2010,6 +1992,11 @@ static void invalidate_tlb_entries(std::array<TLBEntry, N> &tlb) {
|
||||
|
||||
void ppc_mmu_init()
|
||||
{
|
||||
gPendingIInvalidationSources = 0;
|
||||
gPendingDInvalidationSources = 0;
|
||||
gTrackedIEntries.clear();
|
||||
gTrackedDEntries.clear();
|
||||
|
||||
last_ptab_area = {0xFFFFFFFF, 0xFFFFFFFF, 0, 0, nullptr, nullptr};
|
||||
|
||||
mmu_exception_handler = ppc_exception_handler;
|
||||
|
||||
@@ -105,3 +105,25 @@ else:
|
||||
refill_primary_tlb(VA, PA)
|
||||
perform_memory_access(PA, ART)
|
||||
```
|
||||
|
||||
## Translation context invalidation
|
||||
|
||||
Guest operating systems can change BAT registers and page address translation
|
||||
state very frequently. XNU, for example, temporarily installs a DBAT mapping in
|
||||
some physical-memory access routines and restores the previous mapping shortly
|
||||
afterwards. DingusPPC applies the resulting TLB invalidation at the next context
|
||||
synchronization (`isync`, `rfi`, or `sc`), as the processor does. A BAT update
|
||||
must invalidate cached PAT translations as well as BAT translations because a
|
||||
new BAT can shadow a page mapping.
|
||||
|
||||
Scanning every primary and secondary TLB array at each synchronization is
|
||||
expensive. Instead, DingusPPC records pointers to slots populated by BAT or PAT
|
||||
translation in separate instruction and data vectors. Pending invalidations are
|
||||
combined by translation source, and synchronization visits only those tracked
|
||||
slots.
|
||||
|
||||
Generation counters offer constant-time invalidation, but require an additional
|
||||
generation check on every TLB lookup. Tracking populated slots keeps the common
|
||||
hit path as a direct tag comparison. Its cost is paid at context synchronization
|
||||
and is proportional to the number of translated slots populated since the last
|
||||
invalidation.
|
||||
Reference in new issue
Block a user