IOS LLE: optional ARM926 MMU aborts and CP15 privilege (StarOS MMU-001 F1)
ARMCore::SetMMUFaultsEnabled(true) makes the Starlet core deliver the faults of the ARM926EJ-S MMU instead of the identity fallback: - translation (section 0x5, page 0x7), domain (0x9, 0xb) and permission (0xd, 0xf) faults from the descriptors, the DACR (no access and the reserved value fault; manager skips AP) and AP with the S and R bits; ARMv5 has no execute permission, so a fetch needs read permission; - data aborts (vector 0x10, LR = instruction + 8 in both states) set the FSR and the FAR (the MVA); prefetch aborts (vector 0x0c, LR + 4) set the IFSR (c5 opcode2 1) and leave the FAR unchanged; - alignment faults (0x1) when control bit A is set; - base-restored data aborts: the first faulting access ends the instruction's bus traffic and its register results are undone; words stored before the fault stay written; - LDRT/STRT/LDRBT/STRBT check User permissions in any mode; - the TLB keeps each granule's domain and AP, never caches a faulting walk, and c8 single-entry operations drop only the page that maps the MVA, so an entry stays stale until the guest invalidates it; - MRC/MCR to CP15 from User mode is an undefined instruction. The JIT stays usable: with the option on, every block entry goes through C++, which checks the fetch under the current mode and DACR, and only instructions that touch neither memory, CP15, the PSRs nor the exception model run natively; the rest use the interpreter. Off by default. The IOS LLE path keeps its identity fallback, its savestate format (the option and the IFSR are not serialized) and its idle-loop fast-forwarding; the only change there is that the data accessors read one abort flag that stays clear. 20 StarletARMCoreMMUFaults tests, each run in the interpreter and the JIT; the existing Starlet tests pass unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f9193539fb
commit
9b493a1e16
5 files changed
+1052
-34
No files matched your search
@@ -26,8 +26,29 @@ namespace
|
||||
{
|
||||
constexpr u32 CP15_CONTROL_BIG_ENDIAN = 1U << 7;
|
||||
constexpr u32 CP15_CONTROL_MMU = 1U << 0;
|
||||
constexpr u32 CP15_CONTROL_ALIGNMENT = 1U << 1;
|
||||
constexpr u32 CP15_CONTROL_SYSTEM_PROTECTION = 1U << 8;
|
||||
constexpr u32 CP15_CONTROL_ROM_PROTECTION = 1U << 9;
|
||||
constexpr u32 STARLET_EXCEPTION_VECTOR_BASE = 0xffff0000;
|
||||
|
||||
// Fault Status Register status values (ARM926EJ-S TRM, c5). The domain goes in bits 7:4 except
|
||||
// for alignment and section translation faults, whose domain is not valid (written as 0 here).
|
||||
constexpr u32 FSR_ALIGNMENT = 0x1;
|
||||
constexpr u32 FSR_TRANSLATION_SECTION = 0x5;
|
||||
constexpr u32 FSR_TRANSLATION_PAGE = 0x7;
|
||||
constexpr u32 FSR_DOMAIN_SECTION = 0x9;
|
||||
constexpr u32 FSR_DOMAIN_PAGE = 0xb;
|
||||
constexpr u32 FSR_PERMISSION_SECTION = 0xd;
|
||||
constexpr u32 FSR_PERMISSION_PAGE = 0xf;
|
||||
|
||||
// TLBEntry::protection, filled with MMU faults enabled.
|
||||
constexpr u32 PROTECTION_PAGE = 1U << 6;
|
||||
|
||||
constexpr u32 MakeProtection(u32 domain, u32 access_permission, bool page, u32 size_log2)
|
||||
{
|
||||
return domain | (access_permission << 4) | (page ? PROTECTION_PAGE : 0) | (size_log2 << 8);
|
||||
}
|
||||
|
||||
constexpr u32 ModeValue(ARMCore::Mode mode)
|
||||
{
|
||||
return static_cast<u32>(mode);
|
||||
@@ -205,6 +226,16 @@ u32 ARMCore::TranslateVirtualAddress(u32 address) const
|
||||
if ((m_cp15.control & CP15_CONTROL_MMU) == 0)
|
||||
return address;
|
||||
|
||||
if (m_mmu_faults)
|
||||
{
|
||||
// Callers that cannot fault here (a fetch after its permission check, the JIT's decoder)
|
||||
// share the checked TLB. A faulting translation is neither cached nor followed.
|
||||
const u32 modified_address = ModifiedVirtualAddress(address);
|
||||
u32 fault_status = 0;
|
||||
const TLBEntry* const entry = LookupTLBChecked(modified_address, &fault_status);
|
||||
return entry ? entry->physical_page | (modified_address & 0x3ff) : modified_address;
|
||||
}
|
||||
|
||||
// ARM926 FCSE remaps the low 32 MiB through the process identifier before
|
||||
// translation.
|
||||
const u32 modified_address =
|
||||
@@ -235,6 +266,11 @@ u32 ARMCore::TranslateVirtualAddressForJit(u32 address, const u8** first_descrip
|
||||
*second_descriptor_value = 0;
|
||||
if ((m_cp15.control & CP15_CONTROL_MMU) == 0)
|
||||
return address;
|
||||
// With MMU faults enabled the JIT dispatches every block through ARMJitX64::Dispatch, which has
|
||||
// just checked this fetch. Refilling the entry from a fresh walk here would hide a stale TLB
|
||||
// entry that the guest failed to invalidate, so reuse the checked TLB instead.
|
||||
if (m_mmu_faults)
|
||||
return TranslateVirtualAddress(address);
|
||||
|
||||
const u32 modified_address =
|
||||
address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address;
|
||||
@@ -339,6 +375,272 @@ void ARMCore::InvalidateTLB()
|
||||
#endif
|
||||
}
|
||||
|
||||
void ARMCore::SetMMUFaultsEnabled(bool enabled)
|
||||
{
|
||||
m_mmu_faults = enabled;
|
||||
m_abort_pending = false;
|
||||
m_user_access = false;
|
||||
m_waiting_for_memory_poll = false;
|
||||
// Translations and translated code cached under the other model must not survive the switch.
|
||||
InvalidateTLB();
|
||||
InvalidateInstructionCache();
|
||||
}
|
||||
|
||||
u32 ARMCore::ModifiedVirtualAddress(u32 address) const
|
||||
{
|
||||
return address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address;
|
||||
}
|
||||
|
||||
ARMCore::CheckedWalk ARMCore::WalkPageTablesChecked(u32 modified_address) const
|
||||
{
|
||||
// ARM926EJ-S first- and second-level descriptors. Domain and AP come from the descriptor that
|
||||
// maps the address: the first level's domain field, then the AP subpage of a large (16 KiB)
|
||||
// or small (1 KiB) page, or the single AP of a section or tiny page.
|
||||
CheckedWalk walk;
|
||||
const u32 first_level = ReadPhysical32((m_cp15.translation_table_base & 0xffffc000) |
|
||||
((modified_address >> 18) & 0x3ffc));
|
||||
const u32 domain = (first_level >> 5) & 0xf;
|
||||
const auto map_page = [&](u32 second_level_address, bool fine) {
|
||||
const u32 second_level = ReadPhysical32(second_level_address);
|
||||
switch (second_level & 3)
|
||||
{
|
||||
case 1: // 64 KiB large page.
|
||||
walk.physical_address = (second_level & 0xffff0000) | (modified_address & 0xffff);
|
||||
walk.protection = MakeProtection(
|
||||
domain, (second_level >> (4 + 2 * ((modified_address >> 14) & 3))) & 3, true, 16);
|
||||
return;
|
||||
case 2: // 4 KiB small page.
|
||||
walk.physical_address = (second_level & 0xfffff000) | (modified_address & 0xfff);
|
||||
walk.protection = MakeProtection(
|
||||
domain, (second_level >> (4 + 2 * ((modified_address >> 10) & 3))) & 3, true, 12);
|
||||
return;
|
||||
case 3:
|
||||
if (fine) // 1 KiB tiny page.
|
||||
{
|
||||
walk.physical_address = (second_level & 0xfffffc00) | (modified_address & 0x3ff);
|
||||
walk.protection = MakeProtection(domain, (second_level >> 4) & 3, true, 10);
|
||||
return;
|
||||
}
|
||||
// A coarse table has no tiny pages: fail closed.
|
||||
[[fallthrough]];
|
||||
default:
|
||||
walk.fault_status = FSR_TRANSLATION_PAGE | (domain << 4);
|
||||
return;
|
||||
}
|
||||
};
|
||||
switch (first_level & 3)
|
||||
{
|
||||
case 1: // Coarse second-level table.
|
||||
map_page((first_level & 0xfffffc00) | ((modified_address >> 10) & 0x3fc), false);
|
||||
break;
|
||||
case 2: // 1 MiB section.
|
||||
walk.physical_address = (first_level & 0xfff00000) | (modified_address & 0x000fffff);
|
||||
walk.protection = MakeProtection(domain, (first_level >> 10) & 3, false, 20);
|
||||
break;
|
||||
case 3: // Fine second-level table.
|
||||
map_page((first_level & 0xfffff000) | ((modified_address >> 8) & 0xffc), true);
|
||||
break;
|
||||
default:
|
||||
walk.fault_status = FSR_TRANSLATION_SECTION;
|
||||
break;
|
||||
}
|
||||
return walk;
|
||||
}
|
||||
|
||||
const ARMCore::TLBEntry* ARMCore::LookupTLBChecked(u32 modified_address, u32* fault_status) const
|
||||
{
|
||||
const u32 virtual_page = modified_address >> 10;
|
||||
TLBEntry& entry = m_tlb[GetTLBIndex(virtual_page)];
|
||||
if (entry.generation == m_tlb_generation && entry.virtual_page == virtual_page)
|
||||
return &entry;
|
||||
|
||||
const CheckedWalk walk = WalkPageTablesChecked(modified_address);
|
||||
if (walk.fault_status != 0)
|
||||
{
|
||||
// The TLB never holds a translation that faulted: the next access walks again.
|
||||
*fault_status = walk.fault_status;
|
||||
return nullptr;
|
||||
}
|
||||
entry.virtual_page = virtual_page;
|
||||
entry.physical_page = walk.physical_address & ~0x3ffU;
|
||||
entry.generation = m_tlb_generation;
|
||||
entry.protection = walk.protection;
|
||||
return &entry;
|
||||
}
|
||||
|
||||
u32 ARMCore::PermissionFault(const TLBEntry& entry, AccessKind kind, bool user) const
|
||||
{
|
||||
// The DACR is read at every access, as on ARM926; AP comes from the TLB entry, so changing a
|
||||
// descriptor's AP takes effect only after the guest invalidates that entry.
|
||||
const u32 domain = entry.protection & 0xf;
|
||||
const bool page = (entry.protection & PROTECTION_PAGE) != 0;
|
||||
switch ((m_cp15.domain_access_control >> (2 * domain)) & 3)
|
||||
{
|
||||
case 3: // Manager: accesses are not checked against AP.
|
||||
return 0;
|
||||
case 1: // Client: AP below.
|
||||
break;
|
||||
default: // No access, and the reserved value 0b10 (fail closed).
|
||||
return (page ? FSR_DOMAIN_PAGE : FSR_DOMAIN_SECTION) | (domain << 4);
|
||||
}
|
||||
|
||||
// ARMv5 has no execute permission: a fetch needs read permission (no XN, no W^X).
|
||||
const bool write = kind == AccessKind::Write;
|
||||
bool allowed;
|
||||
switch ((entry.protection >> 4) & 3)
|
||||
{
|
||||
case 3: // Read/write for both.
|
||||
allowed = true;
|
||||
break;
|
||||
case 2: // Privileged read/write, User read-only.
|
||||
allowed = !user || !write;
|
||||
break;
|
||||
case 1: // Privileged read/write, User none.
|
||||
allowed = !user;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// AP 00 depends on the control register: S alone grants privileged reads, R alone grants
|
||||
// reads to both. Neither grants nothing; both together are reserved (fail closed).
|
||||
const bool system = (m_cp15.control & CP15_CONTROL_SYSTEM_PROTECTION) != 0;
|
||||
const bool rom = (m_cp15.control & CP15_CONTROL_ROM_PROTECTION) != 0;
|
||||
allowed = !write && ((system && !rom && !user) || (!system && rom));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return allowed ? 0 : (page ? FSR_PERMISSION_PAGE : FSR_PERMISSION_SECTION) | (domain << 4);
|
||||
}
|
||||
|
||||
bool ARMCore::IsUserAccess() const
|
||||
{
|
||||
return GetMode() == Mode::User || m_user_access;
|
||||
}
|
||||
|
||||
u32 ARMCore::DataAddress(u32 address, bool write) const
|
||||
{
|
||||
if (!m_mmu_faults)
|
||||
return TranslateVirtualAddress(address);
|
||||
if (m_abort_pending || (m_cp15.control & CP15_CONTROL_MMU) == 0)
|
||||
return address;
|
||||
|
||||
const u32 modified_address = ModifiedVirtualAddress(address);
|
||||
u32 fault_status = 0;
|
||||
const TLBEntry* const entry = LookupTLBChecked(modified_address, &fault_status);
|
||||
if (entry != nullptr)
|
||||
{
|
||||
fault_status =
|
||||
PermissionFault(*entry, write ? AccessKind::Write : AccessKind::Read, IsUserAccess());
|
||||
}
|
||||
if (fault_status != 0)
|
||||
{
|
||||
RaiseDataAbort(fault_status, modified_address);
|
||||
return address;
|
||||
}
|
||||
return entry->physical_page | (modified_address & 0x3ff);
|
||||
}
|
||||
|
||||
void ARMCore::RaiseDataAbort(u32 fault_status, u32 modified_address) const
|
||||
{
|
||||
// Only the first fault of an instruction is reported.
|
||||
if (m_abort_pending)
|
||||
return;
|
||||
m_abort_pending = true;
|
||||
m_abort_status = fault_status;
|
||||
m_abort_address = modified_address;
|
||||
}
|
||||
|
||||
void ARMCore::CheckAlignment(u32 address, u32 mask) const
|
||||
{
|
||||
// ARM926 checks alignment before translation, when control bit A is set.
|
||||
if (!m_mmu_faults || (m_cp15.control & CP15_CONTROL_ALIGNMENT) == 0 || (address & mask) == 0)
|
||||
return;
|
||||
const bool mmu = (m_cp15.control & CP15_CONTROL_MMU) != 0;
|
||||
RaiseDataAbort(FSR_ALIGNMENT, mmu ? ModifiedVirtualAddress(address) : address);
|
||||
}
|
||||
|
||||
bool ARMCore::TryTranslateFetch(u32 address, u32* physical_address, u32* fault_status) const
|
||||
{
|
||||
*fault_status = 0;
|
||||
if ((m_cp15.control & CP15_CONTROL_MMU) == 0)
|
||||
{
|
||||
*physical_address = address;
|
||||
return true;
|
||||
}
|
||||
const u32 modified_address = ModifiedVirtualAddress(address);
|
||||
const TLBEntry* const entry = LookupTLBChecked(modified_address, fault_status);
|
||||
if (entry != nullptr)
|
||||
*fault_status = PermissionFault(*entry, AccessKind::Fetch, GetMode() == Mode::User);
|
||||
if (*fault_status != 0)
|
||||
return false;
|
||||
*physical_address = entry->physical_page | (modified_address & 0x3ff);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ARMCore::EnterPrefetchAbortIfFaulted(u32 address)
|
||||
{
|
||||
u32 physical_address = 0;
|
||||
u32 fault_status = 0;
|
||||
if (TryTranslateFetch(address, &physical_address, &fault_status))
|
||||
return false;
|
||||
// A prefetch abort sets the IFSR only. The FAR keeps the address of the last data abort; the
|
||||
// handler finds the faulting instruction from LR, which is its address + 4 in both states.
|
||||
m_instruction_fault_status = fault_status;
|
||||
EnterException(Mode::Abort, STARLET_EXCEPTION_VECTOR_BASE + 0x0c, address + 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteARMWithAborts(u32 instruction)
|
||||
{
|
||||
const std::array<u32, 16> registers = m_registers;
|
||||
const std::array<u32, 7> user_registers = m_user_r8_r14;
|
||||
const u32 cpsr = m_cpsr;
|
||||
ExecuteARM(instruction);
|
||||
if (!m_abort_pending)
|
||||
return;
|
||||
// Undo the instruction's register results: an LDM^ with PC may also have restored the CPSR
|
||||
// after its loads. Memory it wrote before the fault stays written, as on ARM926.
|
||||
m_abort_pending = false;
|
||||
if (m_cpsr != cpsr)
|
||||
SetCPSR(cpsr);
|
||||
m_registers = registers;
|
||||
m_user_r8_r14 = user_registers;
|
||||
m_cp15.fault_status = m_abort_status;
|
||||
m_cp15.fault_address = m_abort_address;
|
||||
// A data abort returns to the aborted instruction + 8 in both states.
|
||||
EnterException(Mode::Abort, STARLET_EXCEPTION_VECTOR_BASE + 0x10, m_instruction_address + 8);
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteThumbWithAborts(u16 instruction)
|
||||
{
|
||||
const std::array<u32, 16> registers = m_registers;
|
||||
const u32 cpsr = m_cpsr;
|
||||
ExecuteThumb(instruction);
|
||||
if (!m_abort_pending)
|
||||
return;
|
||||
m_abort_pending = false;
|
||||
if (m_cpsr != cpsr)
|
||||
SetCPSR(cpsr);
|
||||
m_registers = registers;
|
||||
m_cp15.fault_status = m_abort_status;
|
||||
m_cp15.fault_address = m_abort_address;
|
||||
EnterException(Mode::Abort, STARLET_EXCEPTION_VECTOR_BASE + 0x10, m_instruction_address + 8);
|
||||
}
|
||||
|
||||
void ARMCore::InvalidateTLBEntry(u32 modified_address)
|
||||
{
|
||||
// ARM926 drops the one entry that maps this MVA, whatever the size of its page. The software TLB
|
||||
// holds that page as 1 KiB granules: drop each of them, and nothing else.
|
||||
for (TLBEntry& entry : m_tlb)
|
||||
{
|
||||
if (entry.generation != m_tlb_generation)
|
||||
continue;
|
||||
const u32 size_log2 = (entry.protection >> 8) & 0xff;
|
||||
const u32 mask = ~((1U << size_log2) - 1);
|
||||
if (((entry.virtual_page << 10) & mask) == (modified_address & mask))
|
||||
entry.generation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ARMCore::FetchARMInstruction(u32 address)
|
||||
{
|
||||
const u32 physical_address = TranslateVirtualAddress(address);
|
||||
@@ -391,35 +693,61 @@ void ARMCore::InvalidateInstructionCache()
|
||||
#endif
|
||||
}
|
||||
|
||||
// The data accessors translate through DataAddress. Without MMU faults that is the plain
|
||||
// translation and m_abort_pending is never set. With them, a fault records the instruction's
|
||||
// data abort and the bus is not touched by this or any later access of that instruction.
|
||||
u8 ARMCore::ReadByte(u32 address) const
|
||||
{
|
||||
return m_bus.Read8(TranslateVirtualAddress(address));
|
||||
const u32 physical_address = DataAddress(address, false);
|
||||
return m_abort_pending ? 0 : m_bus.Read8(physical_address);
|
||||
}
|
||||
|
||||
void ARMCore::WriteByte(u32 address, u8 value)
|
||||
{
|
||||
m_bus.Write8(TranslateVirtualAddress(address), value);
|
||||
const u32 physical_address = DataAddress(address, true);
|
||||
if (!m_abort_pending)
|
||||
m_bus.Write8(physical_address, value);
|
||||
}
|
||||
|
||||
u16 ARMCore::Read16(u32 address) const
|
||||
{
|
||||
const u16 value =
|
||||
(address & 1) == 0 ?
|
||||
m_bus.Read16(TranslateVirtualAddress(address)) :
|
||||
static_cast<u16>((static_cast<u16>(m_bus.Read8(TranslateVirtualAddress(address))) << 8) |
|
||||
m_bus.Read8(TranslateVirtualAddress(address + 1)));
|
||||
const auto read8 = [this](u32 byte_address) -> u16 {
|
||||
const u32 physical_address = DataAddress(byte_address, false);
|
||||
return m_abort_pending ? 0 : m_bus.Read8(physical_address);
|
||||
};
|
||||
u16 value;
|
||||
if ((address & 1) == 0)
|
||||
{
|
||||
const u32 physical_address = DataAddress(address, false);
|
||||
value = m_abort_pending ? 0 : m_bus.Read16(physical_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
const u16 high = read8(address);
|
||||
value = static_cast<u16>((high << 8) | read8(address + 1));
|
||||
}
|
||||
return m_big_endian ? value : static_cast<u16>((value >> 8) | (value << 8));
|
||||
}
|
||||
|
||||
u32 ARMCore::Read32(u32 address) const
|
||||
{
|
||||
const u32 value =
|
||||
(address & 3) == 0 ?
|
||||
m_bus.Read32(TranslateVirtualAddress(address)) :
|
||||
(static_cast<u32>(m_bus.Read8(TranslateVirtualAddress(address))) << 24) |
|
||||
(static_cast<u32>(m_bus.Read8(TranslateVirtualAddress(address + 1))) << 16) |
|
||||
(static_cast<u32>(m_bus.Read8(TranslateVirtualAddress(address + 2))) << 8) |
|
||||
m_bus.Read8(TranslateVirtualAddress(address + 3));
|
||||
const auto read8 = [this](u32 byte_address) -> u32 {
|
||||
const u32 physical_address = DataAddress(byte_address, false);
|
||||
return m_abort_pending ? 0 : m_bus.Read8(physical_address);
|
||||
};
|
||||
u32 value;
|
||||
if ((address & 3) == 0)
|
||||
{
|
||||
const u32 physical_address = DataAddress(address, false);
|
||||
value = m_abort_pending ? 0 : m_bus.Read32(physical_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 byte0 = read8(address);
|
||||
const u32 byte1 = read8(address + 1);
|
||||
const u32 byte2 = read8(address + 2);
|
||||
value = (byte0 << 24) | (byte1 << 16) | (byte2 << 8) | read8(address + 3);
|
||||
}
|
||||
if (m_big_endian)
|
||||
return value;
|
||||
return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24);
|
||||
@@ -431,11 +759,13 @@ void ARMCore::Write16(u32 address, u16 value)
|
||||
value = static_cast<u16>((value >> 8) | (value << 8));
|
||||
if ((address & 1) == 0)
|
||||
{
|
||||
m_bus.Write16(TranslateVirtualAddress(address), value);
|
||||
const u32 physical_address = DataAddress(address, true);
|
||||
if (!m_abort_pending)
|
||||
m_bus.Write16(physical_address, value);
|
||||
return;
|
||||
}
|
||||
m_bus.Write8(TranslateVirtualAddress(address), static_cast<u8>(value >> 8));
|
||||
m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast<u8>(value));
|
||||
WriteByte(address, static_cast<u8>(value >> 8));
|
||||
WriteByte(address + 1, static_cast<u8>(value));
|
||||
}
|
||||
|
||||
void ARMCore::Write32(u32 address, u32 value)
|
||||
@@ -447,13 +777,15 @@ void ARMCore::Write32(u32 address, u32 value)
|
||||
}
|
||||
if ((address & 3) == 0)
|
||||
{
|
||||
m_bus.Write32(TranslateVirtualAddress(address), value);
|
||||
const u32 physical_address = DataAddress(address, true);
|
||||
if (!m_abort_pending)
|
||||
m_bus.Write32(physical_address, value);
|
||||
return;
|
||||
}
|
||||
m_bus.Write8(TranslateVirtualAddress(address), static_cast<u8>(value >> 24));
|
||||
m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast<u8>(value >> 16));
|
||||
m_bus.Write8(TranslateVirtualAddress(address + 2), static_cast<u8>(value >> 8));
|
||||
m_bus.Write8(TranslateVirtualAddress(address + 3), static_cast<u8>(value));
|
||||
WriteByte(address, static_cast<u8>(value >> 24));
|
||||
WriteByte(address + 1, static_cast<u8>(value >> 16));
|
||||
WriteByte(address + 2, static_cast<u8>(value >> 8));
|
||||
WriteByte(address + 3, static_cast<u8>(value));
|
||||
}
|
||||
|
||||
u32 ARMCore::ReadRegisterOperand(u32 index) const
|
||||
@@ -769,11 +1101,23 @@ int ARMCore::StepInternal(bool advance_bus)
|
||||
|
||||
m_instruction_address = m_registers[15];
|
||||
m_pc_written = false;
|
||||
if (m_cpsr & CPSR_T)
|
||||
if (m_mmu_faults && EnterPrefetchAbortIfFaulted(m_instruction_address))
|
||||
{
|
||||
// The instruction is not executed. Idle-loop fast-forwarding is off with MMU faults: its
|
||||
// polls translate without permission checks.
|
||||
}
|
||||
else if (m_cpsr & CPSR_T)
|
||||
{
|
||||
const u16 instruction = FetchThumbInstruction(m_instruction_address);
|
||||
ExecuteThumb(instruction);
|
||||
TryEnterThumbMemoryPoll(instruction);
|
||||
if (m_mmu_faults)
|
||||
{
|
||||
ExecuteThumbWithAborts(instruction);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteThumb(instruction);
|
||||
TryEnterThumbMemoryPoll(instruction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -790,9 +1134,13 @@ int ARMCore::StepInternal(bool advance_bus)
|
||||
}
|
||||
else if (ConditionPassed(instruction >> 28))
|
||||
{
|
||||
ExecuteARM(instruction);
|
||||
if (m_mmu_faults)
|
||||
ExecuteARMWithAborts(instruction);
|
||||
else
|
||||
ExecuteARM(instruction);
|
||||
}
|
||||
TryEnterARMSliceStablePoll(instruction);
|
||||
if (!m_mmu_faults)
|
||||
TryEnterARMSliceStablePoll(instruction);
|
||||
}
|
||||
|
||||
if (!m_pc_written)
|
||||
@@ -1460,6 +1808,7 @@ void ARMCore::ExecuteARM(u32 instruction)
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckAlignment(address, 3);
|
||||
const u32 old = Read32(address & ~3U);
|
||||
Write32(address & ~3U, ReadRegisterOperand(rm));
|
||||
m_registers[rd] = old;
|
||||
@@ -1734,6 +2083,10 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
const u32 rb = (instruction >> 3) & 7;
|
||||
const u32 rd = instruction & 7;
|
||||
const u32 address = m_registers[rb] + m_registers[ro];
|
||||
if (operation == 0 || operation == 4)
|
||||
CheckAlignment(address, 3);
|
||||
else if (operation == 1 || operation == 5 || operation == 7)
|
||||
CheckAlignment(address, 1);
|
||||
switch (operation)
|
||||
{
|
||||
case 0: // STR
|
||||
@@ -1773,6 +2126,8 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
const u32 rb = (instruction >> 3) & 7;
|
||||
const u32 rd = instruction & 7;
|
||||
const u32 address = m_registers[rb] + offset;
|
||||
if (!byte)
|
||||
CheckAlignment(address, 3);
|
||||
if (load)
|
||||
{
|
||||
m_registers[rd] = byte ? ReadByte(address) : Read32(address);
|
||||
@@ -1794,6 +2149,7 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
const bool load = (instruction & 0x0800) != 0;
|
||||
const u32 address = m_registers[(instruction >> 3) & 7] + (((instruction >> 6) & 0x1f) << 1);
|
||||
const u32 rd = instruction & 7;
|
||||
CheckAlignment(address, 1);
|
||||
if (load)
|
||||
m_registers[rd] = Read16(address);
|
||||
else
|
||||
@@ -1807,6 +2163,7 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
const bool load = (instruction & 0x0800) != 0;
|
||||
const u32 rd = (instruction >> 8) & 7;
|
||||
const u32 address = m_registers[13] + ((instruction & 0xff) << 2);
|
||||
CheckAlignment(address, 3);
|
||||
if (load)
|
||||
m_registers[rd] = Read32(address);
|
||||
else
|
||||
@@ -1851,6 +2208,7 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
if (!pop)
|
||||
{
|
||||
u32 address = m_registers[13] - count * 4;
|
||||
CheckAlignment(address, 3);
|
||||
m_registers[13] = address;
|
||||
for (u32 reg = 0; reg < 8; ++reg)
|
||||
{
|
||||
@@ -1866,6 +2224,7 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
else
|
||||
{
|
||||
u32 address = m_registers[13];
|
||||
CheckAlignment(address, 3);
|
||||
for (u32 reg = 0; reg < 8; ++reg)
|
||||
{
|
||||
if (register_list & (1U << reg))
|
||||
@@ -1903,6 +2262,7 @@ void ARMCore::ExecuteThumb(u16 instruction)
|
||||
return;
|
||||
}
|
||||
u32 address = m_registers[rb];
|
||||
CheckAlignment(address, 3);
|
||||
for (u32 reg = 0; reg < 8; ++reg)
|
||||
{
|
||||
if (!(register_list & (1U << reg)))
|
||||
@@ -2121,6 +2481,10 @@ void ARMCore::ExecuteSingleDataTransfer(u32 instruction)
|
||||
const u32 base = ReadRegisterOperand(rn);
|
||||
const u32 indexed = add_offset ? base + offset : base - offset;
|
||||
const u32 address = preindex ? indexed : base;
|
||||
if (!byte)
|
||||
CheckAlignment(address, 3);
|
||||
// LDRT/STRT/LDRBT/STRBT (post-indexed with W set) check User permissions in any mode.
|
||||
m_user_access = !preindex && writeback;
|
||||
if (load)
|
||||
{
|
||||
u32 value;
|
||||
@@ -2147,6 +2511,7 @@ void ARMCore::ExecuteSingleDataTransfer(u32 instruction)
|
||||
else
|
||||
Write32(address & ~3U, value);
|
||||
}
|
||||
m_user_access = false;
|
||||
|
||||
if ((!preindex || writeback) && !(load && rn == rd))
|
||||
m_registers[rn] = indexed;
|
||||
@@ -2203,6 +2568,8 @@ void ARMCore::ExecuteHalfwordDataTransfer(u32 instruction)
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 1 || type == 3)
|
||||
CheckAlignment(address, 1);
|
||||
if (load)
|
||||
{
|
||||
u32 value;
|
||||
@@ -2280,6 +2647,7 @@ void ARMCore::ExecuteBlockDataTransfer(u32 instruction)
|
||||
address = preindex ? base - count * 4 : base - (count - 1) * 4;
|
||||
if (!m_watch_ranges.empty())
|
||||
TraceWatchedBlockTransfer(address, count * 4, load);
|
||||
CheckAlignment(address, 3);
|
||||
|
||||
u32 loaded_pc = 0;
|
||||
bool pc_loaded = false;
|
||||
@@ -2475,6 +2843,12 @@ void ARMCore::ExecuteCoprocessor(u32 instruction)
|
||||
UndefinedInstruction(instruction);
|
||||
return;
|
||||
}
|
||||
// CP15 is privileged: from User mode an MRC or MCR is an undefined instruction.
|
||||
if (m_mmu_faults && GetMode() == Mode::User)
|
||||
{
|
||||
UndefinedInstruction(instruction);
|
||||
return;
|
||||
}
|
||||
|
||||
if (load)
|
||||
{
|
||||
@@ -2699,7 +3073,8 @@ u32 ARMCore::ReadCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2) const
|
||||
case 3:
|
||||
return m_cp15.domain_access_control;
|
||||
case 5:
|
||||
return m_cp15.fault_status;
|
||||
// Opcode_2 1 is the IFSR, modelled with MMU faults only.
|
||||
return m_mmu_faults && opcode2 == 1 ? m_instruction_fault_status : m_cp15.fault_status;
|
||||
case 6:
|
||||
return m_cp15.fault_address;
|
||||
case 13:
|
||||
@@ -2738,7 +3113,10 @@ void ARMCore::WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value)
|
||||
m_cp15.domain_access_control = value;
|
||||
break;
|
||||
case 5:
|
||||
m_cp15.fault_status = value;
|
||||
if (m_mmu_faults && opcode2 == 1)
|
||||
m_instruction_fault_status = value;
|
||||
else
|
||||
m_cp15.fault_status = value;
|
||||
break;
|
||||
case 6:
|
||||
m_cp15.fault_address = value;
|
||||
@@ -2752,7 +3130,12 @@ void ARMCore::WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value)
|
||||
InvalidateInstructionCache();
|
||||
break;
|
||||
case 8: // TLB maintenance.
|
||||
InvalidateTLB();
|
||||
// With MMU faults, c8 c5/c6/c7 opcode2 1 invalidate the single entry that maps the MVA in Rd;
|
||||
// the separate I and D TLBs share one software TLB, so each operation reaches both.
|
||||
if (m_mmu_faults && opcode2 == 1 && (crm == 5 || crm == 6 || crm == 7))
|
||||
InvalidateTLBEntry(value);
|
||||
else
|
||||
InvalidateTLB();
|
||||
break;
|
||||
case 13:
|
||||
// ARM926 FCSE is specifically designed to switch low-address process spaces without flushing
|
||||
|
||||
@@ -172,6 +172,15 @@ public:
|
||||
u64 GetExecutedInstructions() const { return m_executed_instructions; }
|
||||
void SetJitEnabled(bool enabled);
|
||||
bool IsJitEnabled() const { return m_jit_enabled; }
|
||||
// StarOS MMU-001 (F1): the ARM926 MMU's aborts and the CP15 privilege rule. When enabled, a
|
||||
// translation, domain or permission fault (and, with control bit A, an alignment fault) raises
|
||||
// a data abort (FSR c5 opcode2 0, FAR c6) or a prefetch abort (IFSR c5 opcode2 1; FAR left
|
||||
// unchanged), access permissions follow AP, the S and R control bits and the DACR, TLB entries
|
||||
// can be invalidated one MVA at a time, and CP15 from User mode is undefined. Off by default:
|
||||
// the IOS LLE path keeps its identity fallback for fault descriptors and its savestate format.
|
||||
void SetMMUFaultsEnabled(bool enabled);
|
||||
bool AreMMUFaultsEnabled() const { return m_mmu_faults; }
|
||||
u32 GetInstructionFaultStatus() const { return m_instruction_fault_status; }
|
||||
bool IsJitMemoryProfilingEnabled() const { return m_profile_jit_memory; }
|
||||
u64 GetTraceGeneration() const { return m_trace_generation; }
|
||||
u64 GetJitExecutedInstructions() const;
|
||||
@@ -246,6 +255,26 @@ private:
|
||||
u32 virtual_page = 0;
|
||||
u32 physical_page = 0;
|
||||
u32 generation = 0;
|
||||
// Filled only with MMU faults enabled: the descriptor's domain (bits 3:0), the AP of this
|
||||
// 1 KiB granule (5:4), whether it came from a second-level page (bit 6), and log2 of the
|
||||
// size the descriptor maps (15:8), so that one MVA invalidation drops its whole page.
|
||||
u32 protection = 0;
|
||||
};
|
||||
|
||||
enum class AccessKind
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
Fetch,
|
||||
};
|
||||
|
||||
// The result of a page-table walk with MMU faults enabled.
|
||||
struct CheckedWalk
|
||||
{
|
||||
u32 physical_address = 0;
|
||||
// 0, or the FSR value of a translation fault: status in bits 3:0, domain in bits 7:4.
|
||||
u32 fault_status = 0;
|
||||
u32 protection = 0;
|
||||
};
|
||||
|
||||
struct InstructionCacheEntry
|
||||
@@ -291,6 +320,20 @@ private:
|
||||
u32* second_descriptor_value) const;
|
||||
u32 ReadPhysical32(u32 address) const;
|
||||
void InvalidateTLB();
|
||||
// MMU faults enabled only.
|
||||
u32 ModifiedVirtualAddress(u32 address) const;
|
||||
CheckedWalk WalkPageTablesChecked(u32 modified_address) const;
|
||||
const TLBEntry* LookupTLBChecked(u32 modified_address, u32* fault_status) const;
|
||||
u32 PermissionFault(const TLBEntry& entry, AccessKind kind, bool user) const;
|
||||
bool IsUserAccess() const;
|
||||
u32 DataAddress(u32 address, bool write) const;
|
||||
void RaiseDataAbort(u32 fault_status, u32 modified_address) const;
|
||||
void CheckAlignment(u32 address, u32 mask) const;
|
||||
bool TryTranslateFetch(u32 address, u32* physical_address, u32* fault_status) const;
|
||||
bool EnterPrefetchAbortIfFaulted(u32 address);
|
||||
void ExecuteARMWithAborts(u32 instruction);
|
||||
void ExecuteThumbWithAborts(u16 instruction);
|
||||
void InvalidateTLBEntry(u32 modified_address);
|
||||
u16 FetchThumbInstruction(u32 address);
|
||||
u32 FetchARMInstruction(u32 address);
|
||||
void InvalidateInstructionCache();
|
||||
@@ -386,6 +429,18 @@ private:
|
||||
u64 m_next_profile_instruction = PROFILE_SAMPLE_INTERVAL;
|
||||
std::unordered_map<u32, u64> m_pc_samples;
|
||||
bool m_jit_enabled = false;
|
||||
// StarOS MMU-001 (F1). Not serialized: the option is a host choice made before the run, and
|
||||
// the IOS LLE savestate format stays unchanged.
|
||||
bool m_mmu_faults = false;
|
||||
u32 m_instruction_fault_status = 0;
|
||||
// Set by LDRT/STRT/LDRBT/STRBT around their access: User permissions in a privileged mode.
|
||||
bool m_user_access = false;
|
||||
// The first faulting access of an instruction records its abort here. Later accesses of the
|
||||
// same instruction neither read nor write the bus, and the instruction's register results are
|
||||
// undone before the data abort is taken (ARM926 base-restored abort model).
|
||||
mutable bool m_abort_pending = false;
|
||||
mutable u32 m_abort_status = 0;
|
||||
mutable u32 m_abort_address = 0;
|
||||
#if defined(_M_X86_64)
|
||||
std::unique_ptr<ARMJitX64> m_jit;
|
||||
#endif
|
||||
|
||||
@@ -194,6 +194,11 @@ void ARMJitX64::GenerateDispatcher()
|
||||
MOV(64, R(RAX), ImmPtr(&m_clear_pending));
|
||||
CMP(8, MatR(RAX), Imm8(0));
|
||||
FixupBranch invalidated = J_CC(CC_NE, Jump::Near);
|
||||
// With MMU faults enabled, every block entry goes through C++, which checks the fetch under the
|
||||
// current mode and DACR: the native caches below validate descriptors, not permissions.
|
||||
MOV(64, R(RAX), ImmPtr(&m_core.m_mmu_faults));
|
||||
CMP(8, MatR(RAX), Imm8(0));
|
||||
FixupBranch checked_dispatch = J_CC(CC_NE, Jump::Near);
|
||||
|
||||
// Four-way native block cache. The key includes CPSR.T in bit zero and uses the ARM926 modified
|
||||
// virtual address (MVA) for low FCSE addresses. Different IOS process identifiers can therefore
|
||||
@@ -379,6 +384,11 @@ void ARMJitX64::GenerateDispatcher()
|
||||
MOV(32, R(ABI_PARAM3), R(EAX));
|
||||
MOV(32, R(ABI_PARAM2), Imm32(static_cast<u32>(DispatchReason::PageMiss)));
|
||||
FixupBranch dispatch_reason_page = J(Jump::Near);
|
||||
SetJumpTarget(checked_dispatch);
|
||||
XOR(32, R(ABI_PARAM4), R(ABI_PARAM4));
|
||||
XOR(32, R(ABI_PARAM3), R(ABI_PARAM3));
|
||||
MOV(32, R(ABI_PARAM2), Imm32(static_cast<u32>(DispatchReason::Checked)));
|
||||
FixupBranch dispatch_reason_checked = J(Jump::Near);
|
||||
SetJumpTarget(empty_entry);
|
||||
SetJumpTarget(empty_entry_no_mmu);
|
||||
MOV(32, R(ABI_PARAM4), R(EDX));
|
||||
@@ -387,6 +397,7 @@ void ARMJitX64::GenerateDispatcher()
|
||||
SetJumpTarget(dispatch_reason_translation);
|
||||
SetJumpTarget(dispatch_reason_key);
|
||||
SetJumpTarget(dispatch_reason_page);
|
||||
SetJumpTarget(dispatch_reason_checked);
|
||||
MOV(64, R(ABI_PARAM1), ImmPtr(this));
|
||||
ABI_CallFunction(Dispatch);
|
||||
TEST(64, R(RAX), R(RAX));
|
||||
@@ -427,9 +438,62 @@ size_t ARMJitX64::GetFastEntrySetIndex(u32 key)
|
||||
return static_cast<u32>(key * 0x9e3779b1U) >> 16;
|
||||
}
|
||||
|
||||
const u8* ARMJitX64::DispatchChecked(ARMJitX64* jit)
|
||||
{
|
||||
ARMCore& core = jit->m_core;
|
||||
++jit->m_dispatch_slow_count;
|
||||
u32 physical_address = 0;
|
||||
u32 fault_status = 0;
|
||||
// A fetch that faults is left to the interpreter, whose next step takes the prefetch abort.
|
||||
if (!core.TryTranslateFetch(core.m_registers[15], &physical_address, &fault_status))
|
||||
return nullptr;
|
||||
const bool thumb = (core.m_cpsr & ARMCore::CPSR_T) != 0;
|
||||
const u8* first_descriptor = nullptr;
|
||||
const u8* second_descriptor = nullptr;
|
||||
u32 first_descriptor_value = 0;
|
||||
u32 second_descriptor_value = 0;
|
||||
// No fast entry is recorded: the generated dispatcher sends the next entry here again.
|
||||
Block* const block = jit->GetOrCompileBlock(
|
||||
core.m_registers[15] | (thumb ? 1U : 0U), &physical_address, &first_descriptor,
|
||||
&first_descriptor_value, &second_descriptor, &second_descriptor_value);
|
||||
return block && block->runnable ? block->entry : nullptr;
|
||||
}
|
||||
|
||||
bool ARMJitX64::IsCheckedNativeARM(u32 instruction)
|
||||
{
|
||||
if ((instruction >> 28) == 0xf)
|
||||
return false;
|
||||
// B and BL.
|
||||
if ((instruction & 0x0e000000) == 0x0a000000)
|
||||
return true;
|
||||
// Data processing only: not the multiply and extra load/store space, not the miscellaneous
|
||||
// space (MRS, MSR, BX, BLX, CLZ, DSP: opcodes 8 to 11 with S clear), and not a write of PC,
|
||||
// which may be an exception return.
|
||||
if ((instruction & 0x0c000000) != 0 || (instruction & 0x02000090) == 0x00000090 ||
|
||||
(instruction & 0x01900000) == 0x01000000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ((instruction >> 12) & 0xf) != 15;
|
||||
}
|
||||
|
||||
bool ARMJitX64::IsCheckedNativeThumb(u16 instruction)
|
||||
{
|
||||
// Shifts, add/subtract, move/compare/add/subtract immediate, ALU operations, conditional
|
||||
// branches (not SWI or the undefined condition) and unconditional branches.
|
||||
if ((instruction & 0xc000) == 0x0000 || (instruction & 0xfc00) == 0x4000 ||
|
||||
(instruction & 0xf800) == 0xe000)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (instruction & 0xf000) == 0xd000 && ((instruction >> 8) & 0xf) < 0xe;
|
||||
}
|
||||
|
||||
const u8* ARMJitX64::Dispatch(ARMJitX64* jit, DispatchReason reason, u32 generated_key,
|
||||
u32 generated_set_offset)
|
||||
{
|
||||
if (reason == DispatchReason::Checked)
|
||||
return DispatchChecked(jit);
|
||||
const bool thumb = (jit->m_core.m_cpsr & ARMCore::CPSR_T) != 0;
|
||||
const u32 key = jit->m_core.m_registers[15] | (thumb ? 1U : 0U);
|
||||
const u32 fast_key =
|
||||
@@ -767,6 +831,10 @@ ARMJitX64::Block ARMJitX64::CompileBlock(u32 address, bool thumb)
|
||||
|
||||
bool ARMJitX64::EmitDirectARM(u32 instruction, u32 address, bool* terminal, bool* dispatcher_exit)
|
||||
{
|
||||
// With MMU faults, the interpreter (through FallbackARM, which ends the block) runs every
|
||||
// instruction that could fault or change the next fetch's permission.
|
||||
if (m_core.m_mmu_faults && !IsCheckedNativeARM(instruction))
|
||||
return false;
|
||||
// The IOS scheduler executes a small set of ARM926 system-control writes in virtually every
|
||||
// syscall and interrupt path. Decode those architectural operations once while compiling the
|
||||
// block instead of entering the complete interpreter decoder every time the block runs.
|
||||
@@ -1862,6 +1930,8 @@ void ARMJitX64::EmitExchangeBranch(OpArg target, bool link, u32 return_address)
|
||||
|
||||
bool ARMJitX64::EmitDirectThumb(u16 instruction, u32 address, bool* terminal)
|
||||
{
|
||||
if (m_core.m_mmu_faults && !IsCheckedNativeThumb(instruction))
|
||||
return false;
|
||||
if ((instruction & 0xff00) == 0xdf00)
|
||||
{
|
||||
FlushRegisterCache();
|
||||
@@ -2911,8 +2981,15 @@ void ARMJitX64::FallbackThumb(ARMJitX64* jit, u16 instruction, u32 address)
|
||||
core->RecordJitFallback(address, true);
|
||||
core->m_instruction_address = address;
|
||||
core->m_pc_written = false;
|
||||
core->ExecuteThumb(instruction);
|
||||
core->TryEnterThumbMemoryPoll(instruction);
|
||||
if (core->m_mmu_faults)
|
||||
{
|
||||
core->ExecuteThumbWithAborts(instruction);
|
||||
}
|
||||
else
|
||||
{
|
||||
core->ExecuteThumb(instruction);
|
||||
core->TryEnterThumbMemoryPoll(instruction);
|
||||
}
|
||||
if (!core->m_pc_written)
|
||||
core->m_registers[15] = address + ((core->m_cpsr & ARMCore::CPSR_T) ? 2 : 4);
|
||||
}
|
||||
@@ -2934,9 +3011,13 @@ void ARMJitX64::FallbackARM(ARMJitX64* jit, u32 instruction, u32 address)
|
||||
}
|
||||
else if (core->ConditionPassed(instruction >> 28))
|
||||
{
|
||||
core->ExecuteARM(instruction);
|
||||
if (core->m_mmu_faults)
|
||||
core->ExecuteARMWithAborts(instruction);
|
||||
else
|
||||
core->ExecuteARM(instruction);
|
||||
}
|
||||
core->TryEnterARMSliceStablePoll(instruction);
|
||||
if (!core->m_mmu_faults)
|
||||
core->TryEnterARMSliceStablePoll(instruction);
|
||||
if (!core->m_pc_written)
|
||||
core->m_registers[15] = address + ((core->m_cpsr & ARMCore::CPSR_T) ? 2 : 4);
|
||||
}
|
||||
|
||||
@@ -157,6 +157,8 @@ private:
|
||||
KeyMiss,
|
||||
PageMiss,
|
||||
EmptyEntry,
|
||||
// MMU faults enabled: every block entry checks its fetch (ARMCore::SetMMUFaultsEnabled).
|
||||
Checked,
|
||||
};
|
||||
|
||||
void PoisonMemory() override;
|
||||
@@ -172,6 +174,11 @@ private:
|
||||
u32* first_descriptor_value_out, const u8** second_descriptor_out,
|
||||
u32* second_descriptor_value_out);
|
||||
Block CompileBlock(u32 address, bool thumb);
|
||||
static const u8* DispatchChecked(ARMJitX64* jit);
|
||||
// With MMU faults enabled, the instructions that run natively: they touch neither memory, CP15,
|
||||
// the PSRs nor the exception model, so they can neither fault nor change a fetch's permission.
|
||||
static bool IsCheckedNativeARM(u32 instruction);
|
||||
static bool IsCheckedNativeThumb(u16 instruction);
|
||||
bool EmitDirectARM(u32 instruction, u32 address, bool* terminal, bool* dispatcher_exit);
|
||||
bool EmitARMMultiplyLong(u32 instruction);
|
||||
bool CanEmitARMDataProcessing(u32 instruction) const;
|
||||
|
||||
Reference in new issue
Block a user