From 2f85aaa7cbc4efc6a9e5ae02add3c4cc7921a45c Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Wed, 16 Sep 2026 10:43:02 -0700 Subject: [PATCH] ppcmmu: Flush all PAT-derived TLB entries for the tlbie instruction This fixes booting Mac OS 8.6 hanging at the Happy Mac on the iMac G3. During boot, as part of installing the nanokernel, the OS attempts to do a full TLB flush via a series of `tlbie` instructions. We were trying to respect the effective address that's specified in the instruction, but the semantics of how it should be interpreted are different on the 750 CPU - the [user manual](https://www.nxp.com/docs/en/reference-manual/MPC750UM.pdf) says: > The `tlbie` instruction invalidates all TLB entries indexed by the EA, > and operates on both the instruction and data TLBs simultaneously > invalidating four TLB entries. The index corresponds to bits 14-19 of the EA. We were not respecting the bits 14-19 part (`TLB_VPS_MASK` matched bits 4-19), and thus not matching all expected entries. The stale entries would remain, leading to unexpected behavior. Rather than trying to more precisely model the 750 behavior, we instead flush all PAT-derived TLB entries. This matches QEMU's approach (see qemu/qemu@3dcfb74fd4e4ab31508c80e6965a0cd477510234) and is allowed by [the PowerPC specification](http://refspecs.linux-foundation.org/PPC_hrm.2005mar31.pdf): > For example, the tlbie instruction may be implemented to purge all TLB > entries in a congruence class (that is, all TLB entries indexed by the > specified effective address which can include corresponding entries in > data and instruction TLBs) **or the entire TLB**. As of #219 a full flush is not that expensive, thus this does not materially affect performance. The goal of the guest OS is a full TLB flush anyway (hence the sweep of `tlbie` instructions), so we don't end up doing too many extra lookups after they're done. For #184 --- cpu/ppc/ppcmmu.cpp | 78 +++++++++++++++++------------------------- cpu/ppc/ppcmmu.h | 2 +- cpu/ppc/ppcopcodes.cpp | 8 ++++- 3 files changed, 39 insertions(+), 49 deletions(-) diff --git a/cpu/ppc/ppcmmu.cpp b/cpu/ppc/ppcmmu.cpp index 1f16ccdbc8..702517ce7a 100644 --- a/cpu/ppc/ppcmmu.cpp +++ b/cpu/ppc/ppcmmu.cpp @@ -57,6 +57,7 @@ uint64_t iomem_writes_total = 0; // counts I/O memory writes uint64_t exec_reads_total = 0; // counts reads from executable memory uint64_t bat_transl_total = 0; // counts BAT translations uint64_t ptab_transl_total = 0; // counts page table translations +uint64_t pat_flushes_total = 0; // counts full page-translation TLB flushes uint64_t unaligned_reads = 0; // counts unaligned reads uint64_t unaligned_writes = 0; // counts unaligned writes uint64_t unaligned_crossp_r = 0; // counts unaligned crosspage reads @@ -462,7 +463,6 @@ MapDmaResult mmu_map_dma_mem(uint32_t addr, uint32_t size, bool allow_mmio, bool constexpr uint32_t TLB_SIZE = 4096; constexpr uint32_t TLB2_WAYS = 4; constexpr uint32_t TLB_INVALID_TAG = 0xFFFFFFFF; -constexpr uint32_t TLB_VPS_MASK = 0x0FFFF000; // mask for TLB invalidation enum TLBFlags : uint16_t { PAGE_MEM = 1 << 0, // memory page backed by host memory @@ -520,6 +520,24 @@ static void track_translated_entry(TLBEntry *tlb_entry) gTrackedDEntries.push_back(tlb_entry); } +static void tlb_invalidate_tracked_entries(std::vector &tracked_entries, + uint16_t sources) +{ + size_t retained_count = 0; + for (TLBEntry *tlb_entry : tracked_entries) { + uint16_t source = tlb_entry->flags & TLBE_FROM_TRANSLATION; + if (source & 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); +} + template static void tlb_invalidate_tracked_entries() { @@ -537,19 +555,7 @@ static void tlb_invalidate_tracked_entries() 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); + tlb_invalidate_tracked_entries(*tracked_entries, *pending_sources); *pending_sources = 0; } @@ -1035,41 +1041,14 @@ uint8_t *mmu_translate_imem(uint32_t vaddr, uint32_t *paddr) return host_va; } -static void tlb_flush_primary_entry(std::array &tlb1, uint32_t tag) -{ - TLBEntry *tlb_entry = &tlb1[(tag >> PPC_PAGE_SIZE_BITS) & tlb_size_mask]; - if (tlb_entry->tag != TLB_INVALID_TAG && (tlb_entry->tag & TLB_VPS_MASK) == tag) { - tlb_entry->tag = TLB_INVALID_TAG; - //LOG_F(INFO, "Invalidated primary TLB entry at 0x%X", tag); - } -} - -static void tlb_flush_secondary_entry(std::array &tlb2, uint32_t tag) +void tlb_flush_all_pat() { - TLBEntry *tlb_entry = &tlb2[((tag >> PPC_PAGE_SIZE_BITS) & tlb_size_mask) * TLB2_WAYS]; - for (int i = 0; i < TLB2_WAYS; i++) { - if (tlb_entry[i].tag != TLB_INVALID_TAG && (tlb_entry[i].tag & TLB_VPS_MASK) == tag) { - tlb_entry[i].tag = TLB_INVALID_TAG; - //LOG_F(INFO, "Invalidated secondary TLB entry at 0x%X", tag); - } - } -} +#ifdef MMU_PROFILING + pat_flushes_total++; +#endif -void tlb_flush_entry(uint32_t ea) -{ - const uint32_t tag = ea & TLB_VPS_MASK; - tlb_flush_primary_entry(itlb1_mode1, tag); - tlb_flush_secondary_entry(itlb2_mode1, tag); - tlb_flush_primary_entry(itlb1_mode2, tag); - tlb_flush_secondary_entry(itlb2_mode2, tag); - tlb_flush_primary_entry(itlb1_mode3, tag); - tlb_flush_secondary_entry(itlb2_mode3, tag); - tlb_flush_primary_entry(dtlb1_mode1, tag); - tlb_flush_secondary_entry(dtlb2_mode1, tag); - tlb_flush_primary_entry(dtlb1_mode2, tag); - tlb_flush_secondary_entry(dtlb2_mode2, tag); - tlb_flush_primary_entry(dtlb1_mode3, tag); - tlb_flush_secondary_entry(dtlb2_mode3, tag); + tlb_invalidate_tracked_entries(gTrackedIEntries, TLBE_FROM_PAT); + tlb_invalidate_tracked_entries(gTrackedDEntries, TLBE_FROM_PAT); } static void mpc601_bat_update(uint32_t bat_reg) @@ -1738,6 +1717,10 @@ class MMUProfile : public BaseProfile { .format = ProfileVarFmt::DEC, .value = ptab_transl_total}); + vars.push_back({.name = "Full PAT TLB Flushes Total", + .format = ProfileVarFmt::DEC, + .value = pat_flushes_total}); + vars.push_back({.name = "Unaligned Reads Total", .format = ProfileVarFmt::DEC, .value = unaligned_reads}); @@ -1763,6 +1746,7 @@ class MMUProfile : public BaseProfile { exec_reads_total = 0; bat_transl_total = 0; ptab_transl_total = 0; + pat_flushes_total = 0; unaligned_reads = 0; unaligned_writes = 0; unaligned_crossp_r = 0; diff --git a/cpu/ppc/ppcmmu.h b/cpu/ppc/ppcmmu.h index fa987fe385..fd09c7c067 100644 --- a/cpu/ppc/ppcmmu.h +++ b/cpu/ppc/ppcmmu.h @@ -93,7 +93,7 @@ extern MapDmaResult mmu_map_dma_mem(uint32_t addr, uint32_t size, bool allow_mmi extern void mmu_change_mode(void); extern void mmu_pat_ctx_changed(); -extern void tlb_flush_entry(uint32_t ea); +extern void tlb_flush_all_pat(); extern void mmu_dcbz(uint32_t opcode, uint32_t guest_va); extern uint64_t mem_read_dbg(uint32_t virt_addr, uint32_t size); diff --git a/cpu/ppc/ppcopcodes.cpp b/cpu/ppc/ppcopcodes.cpp index 8a62153f92..140fb70e7b 100644 --- a/cpu/ppc/ppcopcodes.cpp +++ b/cpu/ppc/ppcopcodes.cpp @@ -2130,7 +2130,13 @@ void dppc_interpreter::ppc_tlbie(uint32_t opcode) { return; } - tlb_flush_entry(ppc_state.gpr[(opcode >> 11) & 0x1F]); + // Ideally we would get the effective address via ppc_state.gpr[(opcode >> 11) & 0x1F] + // and use that to identify entries to flush. But that is CPU-dependent (e.g. + // on the 750 only bits 14-19 are used to find matching entries) and does not + // map cleanly to the layout of our virtual TLB. The PowerPC specification + // allows an implementation to be more lax and flush the entire TLB, so we + // do that instead. + tlb_flush_all_pat(); } void dppc_interpreter::ppc_tlbia(uint32_t opcode) {