mirror of
https://github.com/dingusdev/dingusppc.git
synced 2026-10-24 10:02:18 +00:00
2031 lines
71 KiB
C++
2031 lines
71 KiB
C++
/*
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
Copyright (C) 2018-26 The DingusPPC Development Team
|
|
(See CREDITS.MD for more details)
|
|
|
|
(You may also contact divingkxt or powermax2286 on Discord)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** @file PowerPC Memory Management Unit emulation. */
|
|
|
|
#include <core/memaccess.h>
|
|
#include <devices/memctrl/memctrlbase.h>
|
|
#include <devices/common/mmiodevice.h>
|
|
#include "ppcemu.h"
|
|
#include "ppcmmu.h"
|
|
|
|
#include <array>
|
|
#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
|
|
|
|
/* pointer to exception handler to be called when a MMU exception is occurred. */
|
|
void (*mmu_exception_handler)(Except_Type exception_type, uint32_t srr1_bits);
|
|
|
|
/* pointers to BAT update functions. */
|
|
std::function<void(uint32_t bat_reg)> ibat_update;
|
|
std::function<void(uint32_t bat_reg)> dbat_update;
|
|
|
|
/** PowerPC-style MMU BAT arrays (NULL initialization isn't prescribed). */
|
|
PPC_BAT_entry ibat_array[4] = {{0}};
|
|
PPC_BAT_entry dbat_array[4] = {{0}};
|
|
|
|
#ifdef MMU_PROFILING
|
|
|
|
/* global variables for lightweight MMU profiling */
|
|
uint64_t dmem_reads_total = 0; // counts reads from data memory
|
|
uint64_t iomem_reads_total = 0; // counts I/O memory reads
|
|
uint64_t dmem_writes_total = 0; // counts writes to data memory
|
|
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
|
|
uint64_t unaligned_crossp_w = 0; // counts unaligned crosspage writes
|
|
|
|
#endif // MMU_PROFILING
|
|
|
|
#ifdef TLB_PROFILING
|
|
|
|
/* global variables for lightweight SoftTLB profiling */
|
|
uint64_t num_primary_itlb_hits = 0; // number of hits in the primary ITLB
|
|
uint64_t num_secondary_itlb_hits = 0; // number of hits in the secondary ITLB
|
|
uint64_t num_itlb_refills = 0; // number of ITLB refills
|
|
uint64_t num_primary_dtlb_hits = 0; // number of hits in the primary DTLB
|
|
uint64_t num_secondary_dtlb_hits = 0; // number of hits in the secondary DTLB
|
|
uint64_t num_dtlb_refills = 0; // number of DTLB refills
|
|
uint64_t num_entry_replacements = 0; // number of entry replacements
|
|
|
|
#endif // TLB_PROFILING
|
|
|
|
/** remember recently used physical memory regions for quicker translation. */
|
|
AddressMapEntry last_ptab_area;
|
|
|
|
/** Dummy pages for catching writes to physical read-only pages */
|
|
static std::array<uint64_t, 8192 / sizeof(uint64_t)> dummy_page;
|
|
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
template <class T>
|
|
static inline uint32_t mem_munge_constant() {
|
|
switch (sizeof(T)) {
|
|
case 1: return 7;
|
|
case 2: return 6;
|
|
case 4: return 4;
|
|
case 8: return 0;
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
static uint32_t mem_munge_address(uint32_t guest_va) {
|
|
if (sizeof(T) == sizeof(uint64_t)) {
|
|
// 64 bit access. do nothing, handle later.
|
|
return guest_va;
|
|
}
|
|
|
|
uint32_t align_offset = guest_va & (sizeof(T) - 1);
|
|
if (align_offset == 0)
|
|
return guest_va ^ mem_munge_constant<T>();
|
|
|
|
// align the address
|
|
guest_va &= ~(sizeof(T) - 1);
|
|
// munge it
|
|
guest_va ^= mem_munge_constant<T>();
|
|
// and subtract the offset
|
|
return guest_va - align_offset;
|
|
}
|
|
#endif
|
|
|
|
/** 601-style block address translation. */
|
|
static BATResult mpc601_block_address_translation(uint32_t la)
|
|
{
|
|
uint32_t pa; // translated physical address
|
|
uint8_t prot; // protection bits for the translated address
|
|
unsigned key;
|
|
|
|
bool bat_hit = false;
|
|
unsigned msr_pr = !!(ppc_state.msr & MSR::PR);
|
|
|
|
// I/O controller interface takes precedence over BAT in 601
|
|
// Report BAT miss if T bit is set in the corresponding SR
|
|
if (ppc_state.sr[(la >> 28) & 0x0F] & 0x80000000) {
|
|
return BATResult{false, 0, 0};
|
|
}
|
|
|
|
for (int bat_index = 0; bat_index < 4; bat_index++) {
|
|
PPC_BAT_entry* bat_entry = &ibat_array[bat_index];
|
|
|
|
if (bat_entry->valid && ((la & bat_entry->hi_mask) == bat_entry->bepi)) {
|
|
bat_hit = true;
|
|
|
|
key = (((bat_entry->access & 1) & msr_pr) |
|
|
(((bat_entry->access >> 1) & 1) & (msr_pr ^ 1)));
|
|
|
|
// remapping BAT access from 601-style to PowerPC-style
|
|
static uint8_t access_conv[8] = {2, 2, 2, 1, 0, 1, 2, 1};
|
|
|
|
prot = access_conv[(key << 2) | bat_entry->prot];
|
|
|
|
#ifdef MMU_PROFILING
|
|
bat_transl_total++;
|
|
#endif
|
|
|
|
// logical to physical translation
|
|
pa = bat_entry->phys_hi | (la & ~bat_entry->hi_mask);
|
|
return BATResult{bat_hit, prot, pa};
|
|
}
|
|
}
|
|
|
|
return BATResult{bat_hit, 0, 0};
|
|
}
|
|
|
|
/** PowerPC-style block address translation. */
|
|
template <const BATType type>
|
|
static BATResult ppc_block_address_translation(uint32_t la)
|
|
{
|
|
uint32_t pa = 0; // translated physical address
|
|
uint8_t prot = 0; // protection bits for the translated address
|
|
PPC_BAT_entry *bat_array;
|
|
|
|
bool bat_hit = false;
|
|
unsigned msr_pr = (ppc_state.msr & MSR::PR) != 0;
|
|
|
|
bat_array = (type == BATType::IBAT) ? ibat_array : dbat_array;
|
|
|
|
// Format: %XY
|
|
// X - supervisor access bit, Y - problem/user access bit
|
|
// Those bits are mutually exclusive
|
|
unsigned access_bits = ((!msr_pr) << 1) | msr_pr;
|
|
|
|
for (int bat_index = 0; bat_index < 4; bat_index++) {
|
|
PPC_BAT_entry* bat_entry = &bat_array[bat_index];
|
|
|
|
if ((bat_entry->access & access_bits) != 0 && ((la & bat_entry->hi_mask) == bat_entry->bepi)) {
|
|
bat_hit = true;
|
|
|
|
#ifdef MMU_PROFILING
|
|
bat_transl_total++;
|
|
#endif
|
|
// logical to physical translation
|
|
pa = bat_entry->phys_hi | (la & ~bat_entry->hi_mask);
|
|
prot = bat_entry->prot;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BATResult{bat_hit, prot, pa};
|
|
}
|
|
|
|
static inline uint8_t* calc_pteg_addr(uint32_t hash)
|
|
{
|
|
uint32_t sdr1_val, pteg_addr;
|
|
|
|
sdr1_val = ppc_state.spr[SPR::SDR1];
|
|
|
|
pteg_addr = (sdr1_val & 0xFFFF0000) |
|
|
(((sdr1_val & 0x1FF) << 16) & ((hash & 0x7FC00) << 6)) |
|
|
((hash & 0x3FF) << 6);
|
|
#if 0 && SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
|
if (ppc_state.is_LE)
|
|
pteg_addr ^= mem_munge_constant<uint32_t>();
|
|
#endif
|
|
|
|
if (pteg_addr >= last_ptab_area.start && pteg_addr <= last_ptab_area.end) {
|
|
return last_ptab_area.mem_ptr + (pteg_addr - last_ptab_area.start);
|
|
} else {
|
|
AddressMapEntry* entry = mem_ctrl_instance->find_range(pteg_addr);
|
|
if (entry && entry->type & (RT_ROM | RT_RAM)) {
|
|
last_ptab_area.start = entry->start;
|
|
last_ptab_area.end = entry->end;
|
|
last_ptab_area.mem_ptr = entry->mem_ptr;
|
|
return last_ptab_area.mem_ptr + (pteg_addr - last_ptab_area.start);
|
|
} else {
|
|
ABORT_F("SOS: no page table region was found at %08X!\n", pteg_addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool search_pteg(uint8_t* pteg_addr, uint8_t** ret_pte_addr, uint32_t vsid,
|
|
uint16_t page_index, uint8_t pteg_num)
|
|
{
|
|
/* construct PTE matching word */
|
|
uint32_t pte_check = 0x80000000 | (vsid << 7) | (pteg_num << 6) | (page_index >> 10);
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
bool swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
#endif
|
|
|
|
#ifdef MMU_INTEGRITY_CHECKS
|
|
/* PTEG integrity check that ensures that all matching PTEs have
|
|
identical RPN, WIMG and PP bits (PPC PEM 32-bit 7.6.2, rule 5). */
|
|
uint32_t pte_word2_check;
|
|
bool match_found = false;
|
|
|
|
for (int i = 0; i < 8; i++, pteg_addr += 8) {
|
|
if (pte_check == READ_DWORD_BE_A(pteg_addr)) {
|
|
if (match_found) {
|
|
if ((READ_DWORD_BE_A(pteg_addr) & 0xFFFFF07B) != pte_word2_check) {
|
|
ABORT_F("Multiple PTEs with different RPN/WIMG/PP found!\n");
|
|
}
|
|
} else {
|
|
/* isolate RPN, WIMG and PP fields */
|
|
pte_word2_check = READ_DWORD_BE_A(pteg_addr) & 0xFFFFF07B;
|
|
*ret_pte_addr = pteg_addr;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
for (int i = 0; i < 8; i++, pteg_addr += 8) {
|
|
if (pte_check == (
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
swap ? (READ_DWORD_LE_A(pteg_addr)) :
|
|
#endif
|
|
(READ_DWORD_BE_A(pteg_addr))
|
|
)) {
|
|
*ret_pte_addr = pteg_addr;
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
static PATResult page_address_translation(uint32_t la, bool is_instr_fetch,
|
|
unsigned msr_pr, int is_write)
|
|
{
|
|
uint32_t sr_val, page_index, pteg_hash1, vsid, pte_word2;
|
|
unsigned key, pp;
|
|
uint8_t* pte_addr;
|
|
|
|
sr_val = ppc_state.sr[(la >> 28) & 0x0F];
|
|
if (sr_val & 0x80000000) {
|
|
// check for 601-specific memory-forced I/O segments
|
|
if (((sr_val >> 20) & 0x1FF) == 0x7F) {
|
|
return PATResult{
|
|
(la & 0x0FFFFFFF) | (sr_val << 28),
|
|
0, // prot = read/write
|
|
1 // no C bit updates
|
|
};
|
|
} else {
|
|
ABORT_F("Direct-store segments not supported, LA=0x%X\n", la);
|
|
}
|
|
}
|
|
|
|
/* instruction fetch from a no-execute segment will cause ISI exception */
|
|
if ((sr_val & 0x10000000) && is_instr_fetch) {
|
|
mmu_exception_handler(Except_Type::EXC_ISI, 0x10000000);
|
|
}
|
|
|
|
page_index = (la >> 12) & 0xFFFF;
|
|
pteg_hash1 = (sr_val & 0x7FFFF) ^ page_index;
|
|
vsid = sr_val & 0x0FFFFFF;
|
|
|
|
if (!search_pteg(calc_pteg_addr(pteg_hash1), &pte_addr, vsid, page_index, 0)) {
|
|
if (!search_pteg(calc_pteg_addr(~pteg_hash1), &pte_addr, vsid, page_index, 1)) {
|
|
if (is_instr_fetch) {
|
|
mmu_exception_handler(Except_Type::EXC_ISI, 0x40000000);
|
|
} else {
|
|
ppc_state.spr[SPR::DSISR] = 0x40000000 | (is_write << 25);
|
|
ppc_state.spr[SPR::DAR] = la;
|
|
mmu_exception_handler(Except_Type::EXC_DSI, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
uintptr_t pte_addr2S = (uintptr_t)pte_addr;
|
|
#if 0 && SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
|
if (ppc_state.is_LE) {
|
|
pte_addr2S ^= mem_munge_constant<uint32_t>();
|
|
pte_addr2S += 4;
|
|
pte_addr2S ^= mem_munge_constant<uint32_t>();
|
|
} else
|
|
#endif
|
|
{
|
|
pte_addr2S += 4;
|
|
}
|
|
|
|
uint8_t* pte_addr2 = (uint8_t*)pte_addr2S;
|
|
bool swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
pte_word2 = swap ? (READ_DWORD_LE_A(pte_addr2)) : (READ_DWORD_BE_A(pte_addr2));
|
|
#else
|
|
pte_word2 = READ_DWORD_BE_A(pte_addr + 4);
|
|
#endif
|
|
|
|
key = (((sr_val >> 29) & 1) & msr_pr) | (((sr_val >> 30) & 1) & (msr_pr ^ 1));
|
|
|
|
/* check page access */
|
|
pp = pte_word2 & 3;
|
|
|
|
// the following scenarios cause DSI/ISI exception:
|
|
// any access with key = 1 and PP = %00
|
|
// write access with key = 1 and PP = %01
|
|
// write access with PP = %11
|
|
if ((key && (!pp || (pp == 1 && is_write))) || (pp == 3 && is_write)) {
|
|
if (is_instr_fetch) {
|
|
mmu_exception_handler(Except_Type::EXC_ISI, 0x08000000);
|
|
} else {
|
|
ppc_state.spr[SPR::DSISR] = 0x08000000 | (is_write << 25);
|
|
ppc_state.spr[SPR::DAR] = la;
|
|
mmu_exception_handler(Except_Type::EXC_DSI, 0);
|
|
}
|
|
}
|
|
|
|
/* update R and C bits */
|
|
/* For simplicity, R is set on each access, C is set only for writes */
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (swap) {
|
|
pte_addr2[1] |= 0x01;
|
|
if (is_write) {
|
|
pte_addr2[0] |= 0x80;
|
|
}
|
|
} else {
|
|
pte_addr2[2] |= 0x01;
|
|
if (is_write) {
|
|
pte_addr2[3] |= 0x80;
|
|
}
|
|
}
|
|
#else
|
|
pte_addr[6] |= 0x01;
|
|
if (is_write)
|
|
pte_addr[7] |= 0x80;
|
|
#endif
|
|
|
|
/* return physical address, access protection and C status */
|
|
return PATResult{
|
|
((pte_word2 & 0xFFFFF000) | (la & 0x00000FFF)),
|
|
static_cast<uint8_t>((key << 2) | pp),
|
|
static_cast<uint8_t>(pte_word2 & 0x80)
|
|
};
|
|
}
|
|
|
|
MapDmaResult mmu_map_dma_mem(uint32_t addr, uint32_t size, bool allow_mmio, bool is_dbg) {
|
|
MMIODevice *devobj = nullptr;
|
|
uint8_t *host_va = nullptr;
|
|
uint32_t dev_base = 0;
|
|
bool is_writable;
|
|
AddressMapEntry *cur_dma_rgn;
|
|
AddressMapEntry *next_dma_rgn;
|
|
|
|
cur_dma_rgn = mem_ctrl_instance->find_range(addr);
|
|
if (!cur_dma_rgn) {
|
|
if (is_dbg) goto fail;
|
|
ABORT_F("SOS: DMA access to unmapped physical memory 0x%08X..0x%08X!",
|
|
addr, addr + size - 1
|
|
);
|
|
}
|
|
|
|
if (addr + size - 1 > cur_dma_rgn->end) {
|
|
if (!is_dbg) {
|
|
if (cur_dma_rgn->type & (RT_ROM | RT_RAM))
|
|
LOG_F(WARNING, "this region: 0x%08X..0x%08X (host: 0x%08llX..0x%08llX)",
|
|
cur_dma_rgn->start, cur_dma_rgn->end,
|
|
uint64_t(cur_dma_rgn->mem_ptr),
|
|
uint64_t(cur_dma_rgn->mem_ptr + cur_dma_rgn->end - cur_dma_rgn->start)
|
|
);
|
|
else
|
|
LOG_F(ERROR, "this region: 0x%08X..0x%08X",
|
|
cur_dma_rgn->start, cur_dma_rgn->end
|
|
);
|
|
}
|
|
next_dma_rgn = mem_ctrl_instance->find_range(cur_dma_rgn->end + 1);
|
|
if (!is_dbg && next_dma_rgn) {
|
|
if (next_dma_rgn->type & (RT_ROM | RT_RAM))
|
|
LOG_F(WARNING, "next region: 0x%08X..0x%08X (host: 0x%08llX..0x%08llX)",
|
|
next_dma_rgn->start, next_dma_rgn->end,
|
|
uint64_t(next_dma_rgn->mem_ptr),
|
|
uint64_t(next_dma_rgn->mem_ptr + next_dma_rgn->end - next_dma_rgn->start)
|
|
);
|
|
else
|
|
LOG_F(ERROR, "next region: 0x%08X..0x%08X",
|
|
next_dma_rgn->start, next_dma_rgn->end
|
|
);
|
|
}
|
|
if (next_dma_rgn &&
|
|
(cur_dma_rgn->type & (RT_ROM | RT_RAM)) &&
|
|
((cur_dma_rgn->type & (RT_ROM | RT_RAM)) == (next_dma_rgn->type & (RT_ROM | RT_RAM))) &&
|
|
(next_dma_rgn->mem_ptr == cur_dma_rgn->mem_ptr + cur_dma_rgn->end - cur_dma_rgn->start + 1) &&
|
|
(addr + size - 1 <= next_dma_rgn->end)
|
|
) {
|
|
if (!is_dbg)
|
|
LOG_F(INFO, "DMA to physical memory 0x%08X..0x%08X is OK!"
|
|
" The regions are the same type and adjacent in host and guest spaces.",
|
|
addr, addr + size - 1
|
|
);
|
|
} else {
|
|
if (is_dbg) goto fail;
|
|
ABORT_F("SOS: DMA access to unmapped physical memory 0x%08X..0x%08X because size extends outside region!",
|
|
addr, addr + size - 1
|
|
);
|
|
}
|
|
}
|
|
|
|
if ((cur_dma_rgn->type & RT_MMIO) && !allow_mmio) {
|
|
if (is_dbg) goto fail;
|
|
ABORT_F("SOS: DMA access to a MMIO region 0x%08X..0x%08X (%s) for physical memory 0x%08X..0x%08X is not allowed.",
|
|
cur_dma_rgn->start, cur_dma_rgn->end, cur_dma_rgn->devobj->get_name().c_str(), addr, addr + size - 1
|
|
);
|
|
}
|
|
|
|
if (cur_dma_rgn->type & (RT_ROM | RT_RAM)) {
|
|
host_va = cur_dma_rgn->mem_ptr + (addr - cur_dma_rgn->start);
|
|
is_writable = cur_dma_rgn->type & RT_RAM;
|
|
} else { // RT_MMIO
|
|
devobj = cur_dma_rgn->devobj;
|
|
dev_base = cur_dma_rgn->start;
|
|
is_writable = true; // all MMIO devices must provide a write method
|
|
}
|
|
|
|
return MapDmaResult{cur_dma_rgn->type, is_writable, host_va, devobj, dev_base};
|
|
fail:
|
|
return MapDmaResult{RT_NONE, false, nullptr, nullptr, 0};
|
|
}
|
|
|
|
constexpr uint32_t TLB_SIZE = 4096;
|
|
constexpr uint32_t TLB2_WAYS = 4;
|
|
constexpr uint32_t TLB_INVALID_TAG = 0xFFFFFFFF;
|
|
|
|
enum TLBFlags : uint16_t {
|
|
PAGE_MEM = 1 << 0, // memory page backed by host memory
|
|
PAGE_IO = 1 << 1, // memory mapped I/O page
|
|
PAGE_NOPHYS = 1 << 2, // no physical storage for this page (unmapped)
|
|
TLBE_FROM_BAT = 1 << 3, // TLB entry has been translated with BAT
|
|
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;
|
|
uint16_t lru_bits;
|
|
union {
|
|
struct { // for memory pages
|
|
int64_t host_va_offs_r;
|
|
int64_t host_va_offs_w;
|
|
};
|
|
struct { // for MMIO pages
|
|
AddressMapEntry* rgn_desc;
|
|
int64_t dev_base_va;
|
|
};
|
|
};
|
|
uint32_t phys_tag;
|
|
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);
|
|
}
|
|
|
|
static void tlb_invalidate_tracked_entries(std::vector<TLBEntry *> &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 <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;
|
|
|
|
tlb_invalidate_tracked_entries(*tracked_entries, *pending_sources);
|
|
*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;
|
|
static std::array<TLBEntry, TLB_SIZE> itlb1_mode3;
|
|
|
|
// secondary ITLB for all MMU modes
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> itlb2_mode1;
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> itlb2_mode2;
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> itlb2_mode3;
|
|
|
|
// primary DTLB for all MMU modes
|
|
static std::array<TLBEntry, TLB_SIZE> dtlb1_mode1;
|
|
static std::array<TLBEntry, TLB_SIZE> dtlb1_mode2;
|
|
static std::array<TLBEntry, TLB_SIZE> dtlb1_mode3;
|
|
|
|
// secondary DTLB for all MMU modes
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> dtlb2_mode1;
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> dtlb2_mode2;
|
|
static std::array<TLBEntry, TLB_SIZE*TLB2_WAYS> dtlb2_mode3;
|
|
|
|
TLBEntry *pCurITLB1; // current primary ITLB
|
|
TLBEntry *pCurITLB2; // current secondary ITLB
|
|
TLBEntry *pCurDTLB1; // current primary DTLB
|
|
TLBEntry *pCurDTLB2; // current secondary DTLB
|
|
|
|
uint32_t tlb_size_mask = TLB_SIZE - 1;
|
|
|
|
// fake TLB entry for handling of unmapped memory accesses
|
|
uint64_t UnmappedVal = -1ULL;
|
|
TLBEntry UnmappedMem = {TLB_INVALID_TAG, TLBFlags::PAGE_NOPHYS, 0, {{0}}};
|
|
|
|
uint8_t CurITLBMode = {0xFF}; // current ITLB mode
|
|
uint8_t CurDTLBMode = {0xFF}; // current DTLB mode
|
|
|
|
void mmu_change_mode()
|
|
{
|
|
uint8_t mmu_mode;
|
|
|
|
// switch ITLB tables first
|
|
mmu_mode = ((!!(ppc_state.msr & MSR::IR)) << 1) | !!(ppc_state.msr & MSR::PR);
|
|
|
|
if (CurITLBMode != mmu_mode) {
|
|
switch (mmu_mode) {
|
|
case 1: // user mode can't disable translations
|
|
mmu_mode = 0;
|
|
case 0: // real address mode
|
|
pCurITLB1 = &itlb1_mode1[0];
|
|
pCurITLB2 = &itlb2_mode1[0];
|
|
break;
|
|
case 2: // supervisor mode with instruction translation enabled
|
|
pCurITLB1 = &itlb1_mode2[0];
|
|
pCurITLB2 = &itlb2_mode2[0];
|
|
break;
|
|
case 3: // user mode with instruction translation enabled
|
|
pCurITLB1 = &itlb1_mode3[0];
|
|
pCurITLB2 = &itlb2_mode3[0];
|
|
break;
|
|
}
|
|
CurITLBMode = mmu_mode;
|
|
}
|
|
|
|
// then switch DTLB tables
|
|
mmu_mode = ((!!(ppc_state.msr & MSR::DR)) << 1) | !!(ppc_state.msr & MSR::PR);
|
|
|
|
if (CurDTLBMode != mmu_mode) {
|
|
switch (mmu_mode) {
|
|
case 1: // user mode can't disable translations
|
|
mmu_mode = 0;
|
|
case 0: // real address mode
|
|
pCurDTLB1 = &dtlb1_mode1[0];
|
|
pCurDTLB2 = &dtlb2_mode1[0];
|
|
break;
|
|
case 2: // supervisor mode with data translation enabled
|
|
pCurDTLB1 = &dtlb1_mode2[0];
|
|
pCurDTLB2 = &dtlb2_mode2[0];
|
|
break;
|
|
case 3: // user mode with data translation enabled
|
|
pCurDTLB1 = &dtlb1_mode3[0];
|
|
pCurDTLB2 = &dtlb2_mode3[0];
|
|
break;
|
|
}
|
|
CurDTLBMode = mmu_mode;
|
|
}
|
|
}
|
|
|
|
template <uint32_t way>
|
|
static inline void tlb2_touch_way(TLBEntry *tlb_entry)
|
|
{
|
|
static_assert(way < TLB2_WAYS);
|
|
|
|
// Each pair is one branch of the four-way tree-based LRU. Mark the
|
|
// selected way as most recently used, its sibling as the next replacement
|
|
// candidate, and the other pair as less recently used.
|
|
if constexpr (way < 2) {
|
|
tlb_entry[0].lru_bits = way == 0 ? 0x3 : 0x2;
|
|
tlb_entry[1].lru_bits = way == 1 ? 0x3 : 0x2;
|
|
tlb_entry[2].lru_bits &= 0x1;
|
|
tlb_entry[3].lru_bits &= 0x1;
|
|
} else {
|
|
tlb_entry[0].lru_bits &= 0x1;
|
|
tlb_entry[1].lru_bits &= 0x1;
|
|
tlb_entry[2].lru_bits = way == 2 ? 0x3 : 0x2;
|
|
tlb_entry[3].lru_bits = way == 3 ? 0x3 : 0x2;
|
|
}
|
|
}
|
|
|
|
template <const TLBType tlb_type>
|
|
static TLBEntry* tlb2_target_entry(uint32_t gp_va)
|
|
{
|
|
TLBEntry *tlb_entry;
|
|
|
|
if (tlb_type == TLBType::ITLB) {
|
|
tlb_entry = &pCurITLB2[((gp_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask) * TLB2_WAYS];
|
|
} else {
|
|
tlb_entry = &pCurDTLB2[((gp_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask) * TLB2_WAYS];
|
|
}
|
|
|
|
// select the target from invalid blocks first
|
|
if (tlb_entry[0].tag == TLB_INVALID_TAG) {
|
|
tlb2_touch_way<0>(tlb_entry);
|
|
return tlb_entry;
|
|
} else if (tlb_entry[1].tag == TLB_INVALID_TAG) {
|
|
tlb2_touch_way<1>(tlb_entry);
|
|
return &tlb_entry[1];
|
|
} else if (tlb_entry[2].tag == TLB_INVALID_TAG) {
|
|
tlb2_touch_way<2>(tlb_entry);
|
|
return &tlb_entry[2];
|
|
} else if (tlb_entry[3].tag == TLB_INVALID_TAG) {
|
|
tlb2_touch_way<3>(tlb_entry);
|
|
return &tlb_entry[3];
|
|
} else { // no free entries, replace an existing one according with the hLRU policy
|
|
#ifdef TLB_PROFILING
|
|
num_entry_replacements++;
|
|
#endif
|
|
if (tlb_entry[0].lru_bits == 0) {
|
|
tlb2_touch_way<0>(tlb_entry);
|
|
return tlb_entry;
|
|
} else if (tlb_entry[1].lru_bits == 0) {
|
|
tlb2_touch_way<1>(tlb_entry);
|
|
return &tlb_entry[1];
|
|
} else if (tlb_entry[2].lru_bits == 0) {
|
|
tlb2_touch_way<2>(tlb_entry);
|
|
return &tlb_entry[2];
|
|
} else {
|
|
tlb2_touch_way<3>(tlb_entry);
|
|
return &tlb_entry[3];
|
|
}
|
|
}
|
|
}
|
|
|
|
static TLBEntry* itlb2_refill(uint32_t guest_va)
|
|
{
|
|
BATResult bat_res;
|
|
uint32_t phys_addr;
|
|
TLBEntry *tlb_entry;
|
|
uint16_t flags = 0;
|
|
|
|
/* instruction address translation if enabled */
|
|
if (ppc_state.msr & MSR::IR) {
|
|
// attempt block address translation first
|
|
if (is_601) {
|
|
bat_res = mpc601_block_address_translation(guest_va);
|
|
} else {
|
|
bat_res = ppc_block_address_translation<BATType::IBAT>(guest_va);
|
|
}
|
|
if (bat_res.hit) {
|
|
// check block protection
|
|
// only PP = 0 (no access) causes ISI exception
|
|
if (!bat_res.prot) {
|
|
mmu_exception_handler(Except_Type::EXC_ISI, 0x08000000);
|
|
}
|
|
phys_addr = bat_res.phys;
|
|
flags |= TLBFlags::TLBE_FROM_BAT; // tell the world we come from
|
|
} else {
|
|
// page address translation
|
|
PATResult pat_res = page_address_translation(guest_va, true, !!(ppc_state.msr & MSR::PR), 0);
|
|
phys_addr = pat_res.phys;
|
|
flags = TLBFlags::TLBE_FROM_PAT; // tell the world we come from
|
|
}
|
|
} else { // instruction translation disabled
|
|
phys_addr = guest_va;
|
|
}
|
|
|
|
// look up host virtual address
|
|
AddressMapEntry* rgn_desc = mem_ctrl_instance->find_range(phys_addr);
|
|
if (rgn_desc) {
|
|
if (rgn_desc->type & RT_MMIO) {
|
|
ABORT_F("Instruction fetch from MMIO region at 0x%08X!\n", phys_addr);
|
|
}
|
|
// refill the secondary TLB
|
|
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 & 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);
|
|
}
|
|
|
|
return tlb_entry;
|
|
}
|
|
|
|
static TLBEntry* dtlb2_refill(uint32_t guest_va, int is_write, bool is_dbg = false)
|
|
{
|
|
BATResult bat_res;
|
|
uint32_t phys_addr;
|
|
uint16_t flags = 0;
|
|
TLBEntry *tlb_entry;
|
|
|
|
const uint32_t tag = guest_va & ~0xFFFUL;
|
|
|
|
/* data address translation if enabled */
|
|
if (ppc_state.msr & MSR::DR) {
|
|
// attempt block address translation first
|
|
if (is_601) {
|
|
bat_res = mpc601_block_address_translation(guest_va);
|
|
} else {
|
|
bat_res = ppc_block_address_translation<BATType::DBAT>(guest_va);
|
|
}
|
|
if (bat_res.hit) {
|
|
// check block protection
|
|
if (!bat_res.prot || ((bat_res.prot & 1) && is_write)) {
|
|
if (!is_dbg)
|
|
LOG_F(9, "BAT DSI exception in TLB2 refill!");
|
|
if (!is_dbg)
|
|
LOG_F(9, "Attempt to write to read-only region, LA=0x%08X, PC=0x%08X!", guest_va, ppc_state.pc);
|
|
ppc_state.spr[SPR::DSISR] = 0x08000000 | (is_write << 25);
|
|
ppc_state.spr[SPR::DAR] = guest_va;
|
|
mmu_exception_handler(Except_Type::EXC_DSI, 0);
|
|
}
|
|
phys_addr = bat_res.phys;
|
|
flags = TLBFlags::PTE_SET_C; // prevent PTE.C updates for BAT
|
|
flags |= TLBFlags::TLBE_FROM_BAT; // tell the world we come from
|
|
if (bat_res.prot == 2) {
|
|
flags |= TLBFlags::PAGE_WRITABLE;
|
|
}
|
|
} else {
|
|
// page address translation
|
|
PATResult pat_res = page_address_translation(guest_va, false, !!(ppc_state.msr & MSR::PR), is_write);
|
|
phys_addr = pat_res.phys;
|
|
flags = TLBFlags::TLBE_FROM_PAT; // tell the world we come from
|
|
if (pat_res.prot <= 2 || pat_res.prot == 6) {
|
|
flags |= TLBFlags::PAGE_WRITABLE;
|
|
}
|
|
if (is_write || pat_res.pte_c_status) {
|
|
// C-bit of the PTE is already set so the TLB logic
|
|
// doesn't need to update it anymore
|
|
flags |= TLBFlags::PTE_SET_C;
|
|
}
|
|
}
|
|
} else { // data translation disabled
|
|
phys_addr = guest_va;
|
|
flags = TLBFlags::PTE_SET_C; // no PTE.C updates in real addressing mode
|
|
flags |= TLBFlags::PAGE_WRITABLE; // assume physical pages are writable
|
|
}
|
|
|
|
// look up host virtual address
|
|
AddressMapEntry* rgn_desc = mem_ctrl_instance->find_range(phys_addr);
|
|
if (rgn_desc) {
|
|
// refill the secondary TLB
|
|
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 & 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 & 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) {
|
|
// redirect writes to the dummy page for ROM regions
|
|
tlb_entry->host_va_offs_w = (int64_t)&dummy_page - tag;
|
|
} else {
|
|
tlb_entry->host_va_offs_w = tlb_entry->host_va_offs_r;
|
|
}
|
|
}
|
|
tlb_entry->phys_tag = phys_addr & ~0xFFFUL;
|
|
track_translated_entry<TLBType::DTLB>(tlb_entry);
|
|
return tlb_entry;
|
|
} else {
|
|
if (!is_dbg) {
|
|
static uint32_t last_phys_addr = -1;
|
|
static uint32_t first_phys_addr = -1;
|
|
if (phys_addr != last_phys_addr + 4) {
|
|
if (last_phys_addr != -1 && last_phys_addr != first_phys_addr) {
|
|
LOG_F(WARNING, " ... phys_addr=0x%08X", last_phys_addr);
|
|
}
|
|
first_phys_addr = phys_addr;
|
|
LOG_F(WARNING, "Access to unmapped physical memory, phys_addr=0x%08X", first_phys_addr);
|
|
}
|
|
last_phys_addr = phys_addr;
|
|
}
|
|
return &UnmappedMem;
|
|
}
|
|
}
|
|
|
|
template <const TLBType tlb_type>
|
|
static inline TLBEntry* lookup_secondary_tlb(uint32_t guest_va, uint32_t tag) {
|
|
TLBEntry *tlb_entry;
|
|
|
|
if (tlb_type == TLBType::ITLB) {
|
|
tlb_entry = &pCurITLB2[((guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask) * TLB2_WAYS];
|
|
} else {
|
|
tlb_entry = &pCurDTLB2[((guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask) * TLB2_WAYS];
|
|
}
|
|
|
|
if (tlb_entry[0].tag == tag) {
|
|
tlb2_touch_way<0>(tlb_entry);
|
|
} else if (tlb_entry[1].tag == tag) {
|
|
tlb2_touch_way<1>(tlb_entry);
|
|
tlb_entry = &tlb_entry[1];
|
|
} else if (tlb_entry[2].tag == tag) {
|
|
tlb2_touch_way<2>(tlb_entry);
|
|
tlb_entry = &tlb_entry[2];
|
|
} else if (tlb_entry[3].tag == tag) {
|
|
tlb2_touch_way<3>(tlb_entry);
|
|
tlb_entry = &tlb_entry[3];
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
return tlb_entry;
|
|
}
|
|
|
|
struct TLBLookupResult {
|
|
TLBEntry *primary_entry;
|
|
TLBEntry *matched_entry; // nullptr if both TLBs missed
|
|
bool primary_hit;
|
|
};
|
|
|
|
template <const TLBType tlb_type>
|
|
static inline TLBLookupResult lookup_tlb(uint32_t guest_va, uint32_t tag)
|
|
{
|
|
TLBEntry *primary_entry;
|
|
if constexpr (tlb_type == TLBType::ITLB) {
|
|
primary_entry = &pCurITLB1[(guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask];
|
|
} else {
|
|
primary_entry = &pCurDTLB1[(guest_va >> PPC_PAGE_SIZE_BITS) & tlb_size_mask];
|
|
}
|
|
|
|
if (primary_entry->tag == tag) {
|
|
return TLBLookupResult{primary_entry, primary_entry, true};
|
|
}
|
|
return TLBLookupResult{
|
|
primary_entry,
|
|
lookup_secondary_tlb<tlb_type>(guest_va, tag),
|
|
false
|
|
};
|
|
}
|
|
|
|
// Returns true when the PTE.C bit was updated. Callers writing through a
|
|
// primary entry use this to mirror the update into the secondary TLB.
|
|
static inline bool prepare_dtlb_write(TLBEntry *tlb_entry, uint32_t guest_va)
|
|
{
|
|
if (!(tlb_entry->flags & TLBFlags::PAGE_WRITABLE)) {
|
|
ppc_state.spr[SPR::DSISR] = 0x08000000 | (1 << 25);
|
|
ppc_state.spr[SPR::DAR] = guest_va;
|
|
mmu_exception_handler(Except_Type::EXC_DSI, 0);
|
|
}
|
|
|
|
if (tlb_entry->flags & TLBFlags::PTE_SET_C) {
|
|
return false;
|
|
}
|
|
|
|
// Perform full page address translation to update the PTE.C bit.
|
|
page_address_translation(guest_va, false, !!(ppc_state.msr & MSR::PR), true);
|
|
tlb_entry->flags |= TLBFlags::PTE_SET_C;
|
|
return true;
|
|
}
|
|
|
|
void mmu_dcbz(uint32_t opcode, uint32_t guest_va)
|
|
{
|
|
const uint32_t tag = guest_va & ~0xFFFUL;
|
|
TLBEntry *tlb_entry = lookup_tlb<TLBType::DTLB>(guest_va, tag).matched_entry;
|
|
if (tlb_entry == nullptr) {
|
|
// perform full address translation and refill the secondary TLB
|
|
tlb_entry = dtlb2_refill(guest_va, 1);
|
|
}
|
|
|
|
// Check if this was in a MMIO region, in which case we avoid doing the
|
|
// write, so that this intermediate state is not visible to the device
|
|
// (usually a dcbz is done to prepare a cache line as a write buffer, and
|
|
// the real values are written later).
|
|
if (tlb_entry->flags & TLBFlags::PAGE_IO) {
|
|
// Still do the same checks that mmu_write_vmem would have done to
|
|
// disallow writes to read-only regions.
|
|
prepare_dtlb_write(tlb_entry, guest_va);
|
|
return;
|
|
}
|
|
|
|
// the following is not especially efficient but necessary
|
|
// to make BlockZero under Mac OS 8.x and later to work
|
|
mmu_write_vmem<uint64_t>(opcode, guest_va + 0, 0);
|
|
mmu_write_vmem<uint64_t>(opcode, guest_va + 8, 0);
|
|
mmu_write_vmem<uint64_t>(opcode, guest_va + 16, 0);
|
|
mmu_write_vmem<uint64_t>(opcode, guest_va + 24, 0);
|
|
}
|
|
|
|
uint8_t *mmu_translate_imem(uint32_t vaddr, uint32_t *paddr)
|
|
{
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
|
if (ppc_state.is_LE) {
|
|
vaddr ^= mem_munge_constant<uint32_t>();
|
|
}
|
|
#endif
|
|
|
|
TLBEntry *tlb1_entry, *tlb2_entry;
|
|
uint8_t *host_va;
|
|
|
|
#ifdef MMU_PROFILING
|
|
exec_reads_total++;
|
|
#endif
|
|
|
|
const uint32_t tag = vaddr & ~0xFFFUL;
|
|
|
|
TLBLookupResult tlb_lookup = lookup_tlb<TLBType::ITLB>(vaddr, tag);
|
|
tlb1_entry = tlb_lookup.primary_entry;
|
|
if (tlb_lookup.primary_hit) { // primary ITLB hit -> fast path
|
|
#ifdef TLB_PROFILING
|
|
num_primary_itlb_hits++;
|
|
#endif
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + vaddr);
|
|
} else {
|
|
tlb2_entry = tlb_lookup.matched_entry;
|
|
if (tlb2_entry == nullptr) {
|
|
#ifdef TLB_PROFILING
|
|
num_itlb_refills++;
|
|
#endif
|
|
// secondary ITLB miss ->
|
|
// perform full address translation and refill the secondary ITLB
|
|
tlb2_entry = itlb2_refill(vaddr);
|
|
}
|
|
#ifdef TLB_PROFILING
|
|
else {
|
|
num_secondary_itlb_hits++;
|
|
}
|
|
#endif
|
|
// refill the primary ITLB
|
|
promote_tlb_entry<TLBType::ITLB>(tlb1_entry, tlb2_entry);
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + vaddr);
|
|
}
|
|
|
|
if (paddr)
|
|
*paddr = tlb1_entry->phys_tag | (vaddr & 0xFFFUL);
|
|
|
|
return host_va;
|
|
}
|
|
|
|
void tlb_flush_all_pat()
|
|
{
|
|
#ifdef MMU_PROFILING
|
|
pat_flushes_total++;
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
PPC_BAT_entry *ibat_entry, *dbat_entry;
|
|
uint32_t bsm, hi_mask;
|
|
int upper_reg_num;
|
|
|
|
upper_reg_num = bat_reg & 0xFFFFFFFE;
|
|
|
|
ibat_entry = &ibat_array[(bat_reg - 528) >> 1];
|
|
dbat_entry = &dbat_array[(bat_reg - 528) >> 1];
|
|
|
|
if (ppc_state.spr[bat_reg | 1] & 0x40) {
|
|
bsm = ppc_state.spr[upper_reg_num + 1] & 0x3F;
|
|
hi_mask = ~((bsm << 17) | 0x1FFFF);
|
|
|
|
ibat_entry->valid = true;
|
|
ibat_entry->access = (ppc_state.spr[upper_reg_num] >> 2) & 3;
|
|
ibat_entry->prot = ppc_state.spr[upper_reg_num] & 3;
|
|
ibat_entry->hi_mask = hi_mask;
|
|
ibat_entry->phys_hi = ppc_state.spr[upper_reg_num + 1] & hi_mask;
|
|
ibat_entry->bepi = ppc_state.spr[upper_reg_num] & hi_mask;
|
|
|
|
// copy IBAT entry to DBAT entry
|
|
*dbat_entry = *ibat_entry;
|
|
} else {
|
|
// disable the corresponding BAT pairs
|
|
ibat_entry->valid = false;
|
|
dbat_entry->valid = false;
|
|
}
|
|
|
|
// 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*/)
|
|
{
|
|
// ppc_exception_handler(Except_Type::EXC_PROGRAM, Exc_Cause::ILLEGAL_OP);
|
|
}
|
|
|
|
static void ppc_ibat_update(uint32_t bat_reg)
|
|
{
|
|
int upper_reg_num;
|
|
uint32_t bl, hi_mask;
|
|
PPC_BAT_entry* bat_entry;
|
|
|
|
upper_reg_num = bat_reg & 0xFFFFFFFE;
|
|
|
|
bat_entry = &ibat_array[(bat_reg - 528) >> 1];
|
|
bl = (ppc_state.spr[upper_reg_num] >> 2) & 0x7FF;
|
|
hi_mask = ~((bl << 17) | 0x1FFFF);
|
|
|
|
bat_entry->access = ppc_state.spr[upper_reg_num] & 3;
|
|
bat_entry->prot = ppc_state.spr[upper_reg_num + 1] & 3;
|
|
bat_entry->hi_mask = hi_mask;
|
|
bat_entry->phys_hi = ppc_state.spr[upper_reg_num + 1] & hi_mask;
|
|
bat_entry->bepi = ppc_state.spr[upper_reg_num] & hi_mask;
|
|
|
|
// 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)
|
|
{
|
|
int upper_reg_num;
|
|
uint32_t bl, hi_mask;
|
|
PPC_BAT_entry* bat_entry;
|
|
|
|
upper_reg_num = bat_reg & 0xFFFFFFFE;
|
|
|
|
bat_entry = &dbat_array[(bat_reg - 536) >> 1];
|
|
bl = (ppc_state.spr[upper_reg_num] >> 2) & 0x7FF;
|
|
hi_mask = ~((bl << 17) | 0x1FFFF);
|
|
|
|
bat_entry->access = ppc_state.spr[upper_reg_num] & 3;
|
|
bat_entry->prot = ppc_state.spr[upper_reg_num + 1] & 3;
|
|
bat_entry->hi_mask = hi_mask;
|
|
bat_entry->phys_hi = ppc_state.spr[upper_reg_num + 1] & hi_mask;
|
|
bat_entry->bepi = ppc_state.spr[upper_reg_num] & hi_mask;
|
|
|
|
// 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 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
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
#define ARGS_SWAP_MUNGED , bool needs_swap, bool munged
|
|
#define SWAP_MUNGED , needs_swap, munged
|
|
#else
|
|
#define ARGS_SWAP_MUNGED , bool munged
|
|
#define SWAP_MUNGED , munged
|
|
#endif
|
|
#else
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
#define ARGS_SWAP_MUNGED , bool needs_swap, bool munged
|
|
#define SWAP_MUNGED , needs_swap, munged
|
|
#else
|
|
#define ARGS_SWAP_MUNGED
|
|
#define SWAP_MUNGED
|
|
#endif
|
|
#endif
|
|
|
|
// Forward declarations.
|
|
template <class T>
|
|
static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va ARGS_SWAP_MUNGED);
|
|
template <class T>
|
|
static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value ARGS_SWAP_MUNGED);
|
|
|
|
template <class T>
|
|
inline T mmu_read_vmem(uint32_t opcode, uint32_t guest_va)
|
|
{
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
bool munged = false;
|
|
#endif
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
|
if (ppc_state.is_LE) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged = true;
|
|
}
|
|
#endif
|
|
|
|
TLBEntry *tlb1_entry, *tlb2_entry;
|
|
uint8_t *host_va;
|
|
|
|
const uint32_t tag = guest_va & ~0xFFFUL;
|
|
|
|
// look up guest virtual address in the primary and secondary TLBs
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
bool needs_swap = false;
|
|
#endif
|
|
TLBLookupResult tlb_lookup = lookup_tlb<TLBType::DTLB>(guest_va, tag);
|
|
tlb1_entry = tlb_lookup.primary_entry;
|
|
if (tlb_lookup.primary_hit) { // primary TLB hit -> fast path
|
|
#ifdef TLB_PROFILING
|
|
num_primary_dtlb_hits++;
|
|
#endif
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + guest_va);
|
|
} else {
|
|
tlb2_entry = tlb_lookup.matched_entry;
|
|
if (tlb2_entry == nullptr) {
|
|
#ifdef TLB_PROFILING
|
|
num_dtlb_refills++;
|
|
#endif
|
|
// secondary TLB miss ->
|
|
// perform full address translation and refill the secondary TLB
|
|
tlb2_entry = dtlb2_refill(guest_va, 0);
|
|
if (tlb2_entry->flags & PAGE_NOPHYS) {
|
|
return (T)UnmappedVal;
|
|
}
|
|
}
|
|
#ifdef TLB_PROFILING
|
|
else {
|
|
num_secondary_dtlb_hits++;
|
|
}
|
|
#endif
|
|
|
|
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
|
// refill the primary TLB
|
|
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_r + guest_va);
|
|
} else { // otherwise, it's an access to a memory-mapped device
|
|
#ifdef MMU_PROFILING
|
|
iomem_reads_total++;
|
|
#endif
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(tlb2_entry->rgn_desc);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
|
|
if (sizeof(T) == 8) {
|
|
if (guest_va & 3)
|
|
ppc_alignment_exception(opcode, guest_va);
|
|
|
|
uint32_t valueLow = tlb2_entry->rgn_desc->devobj->read(
|
|
tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va - tlb2_entry->dev_base_va),
|
|
4);
|
|
|
|
uint32_t valueHigh = tlb2_entry->rgn_desc->devobj->read(
|
|
tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va + 4 - tlb2_entry->dev_base_va),
|
|
4);
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap) {
|
|
uint32_t temp = valueHigh;
|
|
valueHigh = BYTESWAP_32(valueLow);
|
|
valueLow = BYTESWAP_32(temp);
|
|
}
|
|
#endif
|
|
|
|
return (
|
|
((T)(valueLow) << 32) | valueHigh
|
|
);
|
|
}
|
|
else {
|
|
T value = (
|
|
tlb2_entry->rgn_desc->devobj->read(tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va - tlb2_entry->dev_base_va),
|
|
sizeof(T))
|
|
);
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap && sizeof(T) > 1) {
|
|
value = BYTESWAP_SIZED(value, sizeof(T));
|
|
}
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MMU_PROFILING
|
|
dmem_reads_total++;
|
|
#endif
|
|
|
|
// handle unaligned memory accesses
|
|
if (sizeof(T) > 1 && (guest_va & (sizeof(T) - 1))) {
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (munged)
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
#endif
|
|
return read_unaligned<T>(opcode, guest_va, host_va SWAP_MUNGED);
|
|
}
|
|
|
|
// handle aligned memory accesses
|
|
switch(sizeof(T)) {
|
|
case 1:
|
|
return *host_va;
|
|
case 2:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_WORD_LE_A(host_va)) :
|
|
#endif
|
|
(READ_WORD_BE_A(host_va));
|
|
case 4:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_DWORD_LE_A(host_va)) :
|
|
#endif
|
|
(READ_DWORD_BE_A(host_va));
|
|
case 8:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_QWORD_LE_A(host_va)) :
|
|
#endif
|
|
(READ_QWORD_BE_A(host_va));
|
|
}
|
|
}
|
|
|
|
// explicitely instantiate all required mmu_read_vmem variants
|
|
template uint8_t mmu_read_vmem<uint8_t>(uint32_t opcode, uint32_t guest_va);
|
|
template uint16_t mmu_read_vmem<uint16_t>(uint32_t opcode, uint32_t guest_va);
|
|
template uint32_t mmu_read_vmem<uint32_t>(uint32_t opcode, uint32_t guest_va);
|
|
template uint64_t mmu_read_vmem<uint64_t>(uint32_t opcode, uint32_t guest_va);
|
|
|
|
template <class T>
|
|
inline void mmu_write_vmem(uint32_t opcode, uint32_t guest_va, T value)
|
|
{
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
bool munged = false;
|
|
#endif
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE
|
|
if (ppc_state.is_LE) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged = true;
|
|
}
|
|
#endif
|
|
|
|
TLBEntry *tlb1_entry, *tlb2_entry;
|
|
uint8_t *host_va;
|
|
|
|
const uint32_t tag = guest_va & ~0xFFFUL;
|
|
|
|
// look up guest virtual address in the primary and secondary TLBs
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
bool needs_swap = false;
|
|
#endif
|
|
TLBLookupResult tlb_lookup = lookup_tlb<TLBType::DTLB>(guest_va, tag);
|
|
tlb1_entry = tlb_lookup.primary_entry;
|
|
if (tlb_lookup.primary_hit) { // primary TLB hit -> fast path
|
|
#ifdef TLB_PROFILING
|
|
num_primary_dtlb_hits++;
|
|
#endif
|
|
if (prepare_dtlb_write(tlb1_entry, guest_va)) {
|
|
// don't forget to update the secondary TLB as well
|
|
tlb2_entry = lookup_secondary_tlb<TLBType::DTLB>(guest_va, tag);
|
|
if (tlb2_entry != nullptr) {
|
|
tlb2_entry->flags |= TLBFlags::PTE_SET_C;
|
|
}
|
|
}
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_w + guest_va);
|
|
} else {
|
|
tlb2_entry = tlb_lookup.matched_entry;
|
|
if (tlb2_entry == nullptr) {
|
|
#ifdef TLB_PROFILING
|
|
num_dtlb_refills++;
|
|
#endif
|
|
// secondary TLB miss ->
|
|
// perform full address translation and refill the secondary TLB
|
|
tlb2_entry = dtlb2_refill(guest_va, 1);
|
|
if (tlb2_entry->flags & PAGE_NOPHYS) {
|
|
return;
|
|
}
|
|
}
|
|
#ifdef TLB_PROFILING
|
|
else {
|
|
num_secondary_dtlb_hits++;
|
|
}
|
|
#endif
|
|
prepare_dtlb_write(tlb2_entry, guest_va);
|
|
|
|
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
|
// refill the primary TLB
|
|
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(false);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
|
|
host_va = (uint8_t *)(tlb1_entry->host_va_offs_w + guest_va);
|
|
} else { // otherwise, it's an access to a memory-mapped device
|
|
#ifdef MMU_PROFILING
|
|
iomem_writes_total++;
|
|
#endif
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap = mem_ctrl_instance->needs_swap_endian(tlb2_entry->rgn_desc);
|
|
if (needs_swap) {
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
munged ^= 1;
|
|
}
|
|
#endif
|
|
|
|
if (sizeof(T) == 8) {
|
|
if (guest_va & 3)
|
|
ppc_alignment_exception(opcode, guest_va);
|
|
|
|
uint32_t valueLow, valueHigh;
|
|
valueLow = value >> 32;
|
|
valueHigh = (uint32_t)value;
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap) {
|
|
valueHigh = BYTESWAP_32(valueLow);
|
|
valueLow = (uint32_t)value;
|
|
valueLow = BYTESWAP_32(valueLow);
|
|
}
|
|
#endif
|
|
|
|
tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va - tlb2_entry->dev_base_va),
|
|
valueLow, 4);
|
|
tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va + 4 - tlb2_entry->dev_base_va),
|
|
valueHigh, 4);
|
|
} else {
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap && sizeof(T) > 1) {
|
|
value = BYTESWAP_SIZED(value, sizeof(T));
|
|
}
|
|
#endif
|
|
|
|
tlb2_entry->rgn_desc->devobj->write(tlb2_entry->rgn_desc->start,
|
|
static_cast<uint32_t>(guest_va - tlb2_entry->dev_base_va),
|
|
value, sizeof(T));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef MMU_PROFILING
|
|
dmem_writes_total++;
|
|
#endif
|
|
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
// swap now if needed
|
|
if (needs_swap && sizeof(T) > 1) {
|
|
value = BYTESWAP_SIZED(value, sizeof(T));
|
|
}
|
|
#endif
|
|
|
|
// handle unaligned memory accesses
|
|
if (sizeof(T) > 1 && (guest_va & (sizeof(T) - 1))) {
|
|
// unmunge the guest_va if it was munged
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (munged)
|
|
guest_va = mem_munge_address<T>(guest_va);
|
|
#endif
|
|
write_unaligned<T>(opcode, guest_va, host_va, value SWAP_MUNGED);
|
|
return;
|
|
}
|
|
|
|
// handle aligned memory accesses
|
|
switch(sizeof(T)) {
|
|
case 1:
|
|
*host_va = value;
|
|
break;
|
|
case 2:
|
|
WRITE_WORD_BE_A(host_va, value);
|
|
break;
|
|
case 4:
|
|
WRITE_DWORD_BE_A(host_va, value);
|
|
break;
|
|
case 8:
|
|
WRITE_QWORD_BE_A(host_va, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// explicitely instantiate all required mmu_write_vmem variants
|
|
template void mmu_write_vmem<uint8_t> (uint32_t opcode, uint32_t guest_va, uint8_t value);
|
|
template void mmu_write_vmem<uint16_t>(uint32_t opcode, uint32_t guest_va, uint16_t value);
|
|
template void mmu_write_vmem<uint32_t>(uint32_t opcode, uint32_t guest_va, uint32_t value);
|
|
template void mmu_write_vmem<uint64_t>(uint32_t opcode, uint32_t guest_va, uint64_t value);
|
|
|
|
template <class T>
|
|
static T read_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va ARGS_SWAP_MUNGED)
|
|
{
|
|
if ((sizeof(T) == 8) && (guest_va & 3)) {
|
|
#ifndef PPC_TESTS
|
|
ppc_alignment_exception(opcode, guest_va);
|
|
#endif
|
|
}
|
|
|
|
T result = 0;
|
|
|
|
// is it a misaligned cross-page read?
|
|
if ((sizeof(T) > 1) && ((guest_va & 0xFFF) + sizeof(T)) > 0x1000) {
|
|
#ifdef MMU_PROFILING
|
|
unaligned_crossp_r++;
|
|
#endif
|
|
// Break such a memory access into multiple, bytewise accesses.
|
|
// Because such accesses suffer a performance penalty, they will be
|
|
// presumably very rare so don't waste time optimizing the code below.
|
|
for (int i = 0; i < sizeof(T); guest_va++, i++) {
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap) {
|
|
T value = mmu_read_vmem<uint8_t>(opcode, guest_va);
|
|
value <<= (i * 8);
|
|
result |= value;
|
|
} else
|
|
#endif
|
|
{
|
|
result = (result << 8) | mmu_read_vmem<uint8_t>(opcode, guest_va);
|
|
}
|
|
}
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
} else if (sizeof(T) == sizeof(uint64_t) && munged) {
|
|
// Munged host address for an unaligned 64-bit read.
|
|
// Check for cross-page read, to read the upper 32 bits correctly.
|
|
if (((guest_va & 0xFFF) + 12) > 0x1000) {
|
|
// Add the pre-munged address, as munging is a no-op for uint64_t, but not for uint32_t.
|
|
result = mmu_read_vmem<uint32_t>(opcode, guest_va + mem_munge_address<uint32_t>(8));
|
|
} else {
|
|
result =
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_DWORD_LE_U(host_va + 8)) :
|
|
#endif
|
|
(READ_DWORD_BE_U(host_va + 8));
|
|
}
|
|
result <<= 32;
|
|
result |=
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_DWORD_LE_U(host_va)) :
|
|
#endif
|
|
(READ_DWORD_BE_U(host_va));
|
|
#endif
|
|
} else {
|
|
#ifdef MMU_PROFILING
|
|
unaligned_reads++;
|
|
#endif
|
|
switch(sizeof(T)) {
|
|
case 1:
|
|
return *host_va;
|
|
case 2:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_WORD_LE_U(host_va)) :
|
|
#endif
|
|
(READ_WORD_BE_U(host_va));
|
|
case 4:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_DWORD_LE_U(host_va)) :
|
|
#endif
|
|
(READ_DWORD_BE_U(host_va));
|
|
case 8:
|
|
return
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
needs_swap ? (READ_QWORD_LE_U(host_va)) :
|
|
#endif
|
|
(READ_QWORD_BE_U(host_va));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// explicitely instantiate all required read_unaligned variants
|
|
template uint16_t read_unaligned<uint16_t>(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED);
|
|
template uint32_t read_unaligned<uint32_t>(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED);
|
|
template uint64_t read_unaligned<uint64_t>(uint32_t opcode, uint32_t guest_va, uint8_t* host_va ARGS_SWAP_MUNGED);
|
|
|
|
template <class T>
|
|
static void write_unaligned(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, T value ARGS_SWAP_MUNGED)
|
|
{
|
|
if ((sizeof(T) == 8) && (guest_va & 3)) {
|
|
#ifndef PPC_TESTS
|
|
ppc_alignment_exception(opcode, guest_va);
|
|
#endif
|
|
}
|
|
|
|
// is it a misaligned cross-page write?
|
|
if ((sizeof(T) > 1) && ((guest_va & 0xFFF) + sizeof(T)) > 0x1000) {
|
|
#ifdef MMU_PROFILING
|
|
unaligned_crossp_w++;
|
|
#endif
|
|
// Break such a memory access into multiple, bytewise accesses.
|
|
// Because such accesses suffer a performance penalty, they will be
|
|
// presumably very rare so don't waste time optimizing the code below.
|
|
|
|
uint32_t shift = (sizeof(T) - 1) * 8;
|
|
|
|
for (int i = 0; i < sizeof(T); shift -= 8, guest_va++, i++) {
|
|
mmu_write_vmem<uint8_t>(opcode, guest_va, (value >> shift) & 0xFF);
|
|
}
|
|
#if SUPPORTS_PPC_LITTLE_ENDIAN_MODE || SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
} else if (sizeof(T) == sizeof(uint64_t) && munged) {
|
|
// Munged host address for an unaligned 64-bit write.
|
|
// Check for cross-page write, to write the upper 32 bits correctly.
|
|
uint32_t value32 = (value >> 32);
|
|
if (((guest_va & 0xFFF) + 12) > 0x1000) {
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
// The value is endianness swapped already if needs_swap is true, so swap it back in that case.
|
|
if (needs_swap) {
|
|
value32 = (uint32_t)value;
|
|
value32 = BYTESWAP_32(value32);
|
|
}
|
|
#endif
|
|
// Add the pre-munged address, as munging is a no-op for uint64_t, but not for uint32_t.
|
|
mmu_write_vmem<uint32_t>(opcode, guest_va + mem_munge_address<uint32_t>(8), value32);
|
|
} else {
|
|
// Not cross-page, so just write via host address.
|
|
WRITE_DWORD_BE_U(host_va + 8, value32);
|
|
}
|
|
// Write the lower 32 bits.
|
|
#if SUPPORTS_MEMORY_CTRL_ENDIAN_MODE
|
|
if (needs_swap) {
|
|
value32 = (value >> 32);
|
|
} else
|
|
#endif
|
|
{
|
|
value32 = (uint32_t)value;
|
|
}
|
|
WRITE_DWORD_BE_U(host_va, value32);
|
|
#endif
|
|
} else {
|
|
#ifdef MMU_PROFILING
|
|
unaligned_writes++;
|
|
#endif
|
|
switch(sizeof(T)) {
|
|
case 1:
|
|
*host_va = value;
|
|
break;
|
|
case 2:
|
|
WRITE_WORD_BE_U(host_va, value);
|
|
break;
|
|
case 4:
|
|
WRITE_DWORD_BE_U(host_va, value);
|
|
break;
|
|
case 8:
|
|
WRITE_QWORD_BE_U(host_va, value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// explicitely instantiate all required write_unaligned variants
|
|
template void write_unaligned<uint16_t>(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint16_t value ARGS_SWAP_MUNGED);
|
|
template void write_unaligned<uint32_t>(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint32_t value ARGS_SWAP_MUNGED);
|
|
template void write_unaligned<uint64_t>(uint32_t opcode, uint32_t guest_va, uint8_t *host_va, uint64_t value ARGS_SWAP_MUNGED);
|
|
|
|
|
|
/* MMU profiling. */
|
|
#ifdef MMU_PROFILING
|
|
|
|
#include "utils/profiler.h"
|
|
#include <memory>
|
|
|
|
class MMUProfile : public BaseProfile {
|
|
public:
|
|
MMUProfile() : BaseProfile("PPC_MMU") {}
|
|
|
|
void populate_variables(std::vector<ProfileVar>& vars) {
|
|
vars.clear();
|
|
|
|
vars.push_back({.name = "Data Memory Reads Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = dmem_reads_total});
|
|
|
|
vars.push_back({.name = "I/O Memory Reads Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = iomem_reads_total});
|
|
|
|
vars.push_back({.name = "Data Memory Writes Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = dmem_writes_total});
|
|
|
|
vars.push_back({.name = "I/O Memory Writes Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = iomem_writes_total});
|
|
|
|
vars.push_back({.name = "Reads from Executable Memory",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = exec_reads_total});
|
|
|
|
vars.push_back({.name = "BAT Translations Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = bat_transl_total});
|
|
|
|
vars.push_back({.name = "Page Table Translations Total",
|
|
.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});
|
|
|
|
vars.push_back({.name = "Unaligned Writes Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = unaligned_writes});
|
|
|
|
vars.push_back({.name = "Unaligned Crosspage Reads Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = unaligned_crossp_r});
|
|
|
|
vars.push_back({.name = "Unaligned Crosspage Writes Total",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = unaligned_crossp_w});
|
|
}
|
|
|
|
void reset() {
|
|
dmem_reads_total = 0;
|
|
iomem_reads_total = 0;
|
|
dmem_writes_total = 0;
|
|
iomem_writes_total = 0;
|
|
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;
|
|
unaligned_crossp_w = 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
/* SoftTLB profiling. */
|
|
#ifdef TLB_PROFILING
|
|
|
|
#include "utils/profiler.h"
|
|
#include <memory>
|
|
|
|
class TLBProfile : public BaseProfile {
|
|
public:
|
|
TLBProfile() : BaseProfile("PPC:MMU:TLB") {}
|
|
|
|
void populate_variables(std::vector<ProfileVar>& vars) {
|
|
vars.clear();
|
|
|
|
vars.push_back({.name = "Number of hits in the primary ITLB",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_primary_itlb_hits});
|
|
|
|
vars.push_back({.name = "Number of hits in the secondary ITLB",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_secondary_itlb_hits});
|
|
|
|
vars.push_back({.name = "Number of ITLB refills",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_itlb_refills});
|
|
|
|
vars.push_back({.name = "Number of hits in the primary DTLB",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_primary_dtlb_hits});
|
|
|
|
vars.push_back({.name = "Number of hits in the secondary DTLB",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_secondary_dtlb_hits});
|
|
|
|
vars.push_back({.name = "Number of DTLB refills",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_dtlb_refills});
|
|
|
|
vars.push_back({.name = "Number of replaced TLB entries",
|
|
.format = ProfileVarFmt::DEC,
|
|
.value = num_entry_replacements});
|
|
}
|
|
|
|
void reset() {
|
|
num_primary_dtlb_hits = 0;
|
|
num_secondary_dtlb_hits = 0;
|
|
num_dtlb_refills = 0;
|
|
num_entry_replacements = 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
uint64_t mem_read_dbg(uint32_t virt_addr, uint32_t size) {
|
|
uint32_t save_dsisr, save_dar;
|
|
uint64_t ret_val;
|
|
|
|
/* save MMU-related CPU state */
|
|
save_dsisr = ppc_state.spr[SPR::DSISR];
|
|
save_dar = ppc_state.spr[SPR::DAR];
|
|
mmu_exception_handler = dbg_exception_handler;
|
|
|
|
try {
|
|
switch (size) {
|
|
case 1:
|
|
ret_val = mmu_read_vmem<uint8_t>(NO_OPCODE, virt_addr);
|
|
break;
|
|
case 2:
|
|
ret_val = mmu_read_vmem<uint16_t>(NO_OPCODE, virt_addr);
|
|
break;
|
|
case 4:
|
|
ret_val = mmu_read_vmem<uint32_t>(NO_OPCODE, virt_addr);
|
|
break;
|
|
case 8:
|
|
ret_val = mmu_read_vmem<uint64_t>(NO_OPCODE, virt_addr);
|
|
break;
|
|
default:
|
|
ret_val = mmu_read_vmem<uint8_t>(NO_OPCODE, virt_addr);
|
|
}
|
|
}
|
|
catch (std::invalid_argument& exc) {
|
|
/* restore MMU-related CPU state */
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
|
|
/* rethrow MMU exception */
|
|
throw exc;
|
|
}
|
|
catch (...) {
|
|
/* restore MMU-related CPU state */
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
|
|
/* rethrow MMU exception */
|
|
throw(false);
|
|
}
|
|
|
|
/* restore MMU-related CPU state */
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
void mem_write_dbg(uint32_t virt_addr, uint64_t value, int size) {
|
|
uint32_t save_dsisr, save_dar;
|
|
uint64_t ret_val;
|
|
|
|
// save MMU-related CPU state
|
|
save_dsisr = ppc_state.spr[SPR::DSISR];
|
|
save_dar = ppc_state.spr[SPR::DAR];
|
|
mmu_exception_handler = dbg_exception_handler;
|
|
|
|
try {
|
|
switch (size) {
|
|
case 1:
|
|
mmu_write_vmem<uint8_t>(NO_OPCODE, virt_addr, value);
|
|
break;
|
|
case 2:
|
|
mmu_write_vmem<uint16_t>(NO_OPCODE, virt_addr, value);
|
|
break;
|
|
case 4:
|
|
mmu_write_vmem<uint32_t>(NO_OPCODE, virt_addr, uint32_t(value));
|
|
break;
|
|
case 8:
|
|
mmu_write_vmem<uint64_t>(NO_OPCODE, virt_addr, value);
|
|
break;
|
|
default:
|
|
mmu_write_vmem<uint8_t>(NO_OPCODE, virt_addr, value);
|
|
}
|
|
} catch (std::invalid_argument& exc) {
|
|
// restore MMU-related CPU state
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
|
|
// rethrow MMU exception
|
|
throw exc;
|
|
}
|
|
|
|
// restore MMU-related CPU state
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
}
|
|
|
|
bool mmu_translate_dbg(uint32_t guest_va, uint32_t &guest_pa) {
|
|
uint32_t save_dsisr, save_dar;
|
|
bool is_mapped;
|
|
|
|
/* save MMU-related CPU state */
|
|
save_dsisr = ppc_state.spr[SPR::DSISR];
|
|
save_dar = ppc_state.spr[SPR::DAR];
|
|
mmu_exception_handler = dbg_exception_handler;
|
|
|
|
try {
|
|
TLBEntry *tlb1_entry, *tlb2_entry;
|
|
|
|
const uint32_t tag = guest_va & ~0xFFFUL;
|
|
|
|
TLBLookupResult tlb_lookup = lookup_tlb<TLBType::DTLB>(guest_va, tag);
|
|
tlb1_entry = tlb_lookup.primary_entry;
|
|
|
|
do {
|
|
if (!tlb_lookup.primary_hit) {
|
|
tlb2_entry = tlb_lookup.matched_entry;
|
|
if (tlb2_entry == nullptr) {
|
|
// secondary TLB miss ->
|
|
// perform full address translation and refill the secondary TLB
|
|
tlb2_entry = dtlb2_refill(guest_va, 0, true);
|
|
if (tlb2_entry->flags & PAGE_NOPHYS) {
|
|
is_mapped = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tlb2_entry->flags & TLBFlags::PAGE_MEM) { // is it a real memory region?
|
|
// refill the primary TLB
|
|
promote_tlb_entry<TLBType::DTLB>(tlb1_entry, tlb2_entry);
|
|
}
|
|
else {
|
|
tlb1_entry = tlb2_entry;
|
|
}
|
|
}
|
|
guest_pa = tlb1_entry->phys_tag | (guest_va & 0xFFFUL);
|
|
is_mapped = true;
|
|
} while (0);
|
|
}
|
|
catch (std::invalid_argument& exc) {
|
|
LOG_F(WARNING, "Unmapped address 0x%08X", guest_va);
|
|
is_mapped = false;
|
|
}
|
|
catch (...) {
|
|
LOG_F(WARNING, "Unmapped address 0x%08X", guest_va);
|
|
is_mapped = false;
|
|
}
|
|
|
|
/* restore MMU-related CPU state */
|
|
mmu_exception_handler = ppc_exception_handler;
|
|
ppc_state.spr[SPR::DSISR] = save_dsisr;
|
|
ppc_state.spr[SPR::DAR] = save_dar;
|
|
|
|
return is_mapped;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
static void invalidate_tlb_entries(std::array<TLBEntry, N> &tlb) {
|
|
for (auto &tlb_el : tlb) {
|
|
tlb_el.tag = TLB_INVALID_TAG;
|
|
tlb_el.flags = 0;
|
|
tlb_el.lru_bits = 0;
|
|
tlb_el.host_va_offs_r = 0;
|
|
tlb_el.host_va_offs_w = 0;
|
|
tlb_el.phys_tag = 0;
|
|
tlb_el.reserved = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (is_601) {
|
|
// use 601-style unified BATs
|
|
ibat_update = &mpc601_bat_update;
|
|
dbat_update = &mpc601_dbat_update;
|
|
} else {
|
|
// use PPC-style BATs
|
|
ibat_update = &ppc_ibat_update;
|
|
dbat_update = &ppc_dbat_update;
|
|
}
|
|
|
|
int reg;
|
|
for (reg = 528; reg <= 535; reg++)
|
|
ibat_update(reg);
|
|
for (reg = 536; reg <= 543; reg++)
|
|
dbat_update(reg);
|
|
|
|
// invalidate all IDTLB entries
|
|
invalidate_tlb_entries(itlb1_mode1);
|
|
invalidate_tlb_entries(itlb1_mode2);
|
|
invalidate_tlb_entries(itlb1_mode3);
|
|
invalidate_tlb_entries(itlb2_mode1);
|
|
invalidate_tlb_entries(itlb2_mode2);
|
|
invalidate_tlb_entries(itlb2_mode3);
|
|
// invalidate all DTLB entries
|
|
invalidate_tlb_entries(dtlb1_mode1);
|
|
invalidate_tlb_entries(dtlb1_mode2);
|
|
invalidate_tlb_entries(dtlb1_mode3);
|
|
invalidate_tlb_entries(dtlb2_mode1);
|
|
invalidate_tlb_entries(dtlb2_mode2);
|
|
invalidate_tlb_entries(dtlb2_mode3);
|
|
|
|
mmu_change_mode();
|
|
|
|
#ifdef MMU_PROFILING
|
|
gProfilerObj->register_profile("PPC:MMU",
|
|
std::unique_ptr<BaseProfile>(new MMUProfile()));
|
|
#endif
|
|
|
|
#ifdef TLB_PROFILING
|
|
gProfilerObj->register_profile("PPC:MMU:TLB",
|
|
std::unique_ptr<BaseProfile>(new TLBProfile()));
|
|
#endif
|
|
}
|