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
@@ -6,7 +6,9 @@
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <chrono>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -3536,5 +3538,495 @@ TEST(StarletARMCore, JitCachesProvisionalIdentityTranslationForFaultPages)
|
||||
EXPECT_GT(bus.GetDirectPointerCount(), 0u); // Native descriptor validation is still present.
|
||||
#endif
|
||||
}
|
||||
|
||||
// StarOS MMU-001 (F1): ARM926 MMU aborts with ARMCore::SetMMUFaultsEnabled. Every case runs in the
|
||||
// interpreter and, on x86-64, in the JIT; both must reach the same state.
|
||||
class MMUFaultMachine
|
||||
{
|
||||
public:
|
||||
static constexpr u32 TABLE = 0x4000; // first-level table, 16 KiB aligned
|
||||
static constexpr u32 COARSE = 0x8000; // one coarse second-level table
|
||||
// Section 0x800 maps physical 0 privileged-only (AP 01), section 0x880 the same bytes with full
|
||||
// access (AP 11), both in domain 0. Code is written once and run from either view.
|
||||
static constexpr u32 KERNEL = 0x80000000;
|
||||
static constexpr u32 CODE = KERNEL + 0x1000;
|
||||
static constexpr u32 USER_CODE = 0x88001000;
|
||||
static constexpr u32 FAR_SENTINEL = 0xdeadbeef;
|
||||
static constexpr u32 SVC_CPSR = 0xd3;
|
||||
static constexpr u32 USER_CPSR = 0xd0;
|
||||
static constexpr u32 ABORT_MODE = 0x17;
|
||||
static constexpr u32 UNDEFINED_MODE = 0x1b;
|
||||
|
||||
explicit MMUFaultMachine(bool jit) : m_bus(0x100000), m_core(m_bus)
|
||||
{
|
||||
// High vectors: undefined and SWI stop on themselves; both aborts branch to a handler that
|
||||
// records SPSR (r8), DFSR (r9), FAR (r10) and IFSR (r11) through MRS/MRC, then marks its kind
|
||||
// in r12 (0xd data, 0xc prefetch) and stops.
|
||||
for (u32 vector = 0; vector < 0x20; vector += 4)
|
||||
m_bus.WriteARM(0xffff0000 + vector, 0xeafffffe); // b .
|
||||
m_bus.WriteARM(0xffff000c, 0xea00004b); // b 0xffff0140
|
||||
m_bus.WriteARM(0xffff0010, 0xea00003a); // b 0xffff0100
|
||||
for (const auto [handler, kind] : {std::pair{0xffff0100u, 0xe3a0c00du},
|
||||
std::pair{0xffff0140u, 0xe3a0c00cu}})
|
||||
{
|
||||
m_bus.WriteARM(handler, 0xe14f8000); // mrs r8, spsr
|
||||
m_bus.WriteARM(handler + 4, 0xee159f10); // mrc p15, 0, r9, c5, c0, 0 (DFSR)
|
||||
m_bus.WriteARM(handler + 8, 0xee16af10); // mrc p15, 0, r10, c6, c0, 0 (FAR)
|
||||
m_bus.WriteARM(handler + 12, 0xee15bf30); // mrc p15, 0, r11, c5, c0, 1 (IFSR)
|
||||
m_bus.WriteARM(handler + 16, kind); // mov r12, #kind
|
||||
m_bus.WriteARM(handler + 20, 0xeafffffe); // b .
|
||||
}
|
||||
MapSection(0x800, 0, 1, 0);
|
||||
MapSection(0x880, 0, 3, 0);
|
||||
MapSection(0xfff, 0xfff00000, 1, 0);
|
||||
auto& cp15 = m_core.GetCP15State();
|
||||
cp15.translation_table_base = TABLE;
|
||||
cp15.domain_access_control = 1; // domain 0 client, every other domain no access
|
||||
cp15.control |= 1;
|
||||
cp15.fault_address = FAR_SENTINEL;
|
||||
m_core.SetMMUFaultsEnabled(true);
|
||||
m_core.SetJitEnabled(jit);
|
||||
m_core.SetRegister(15, CODE);
|
||||
}
|
||||
|
||||
void MapSection(u32 index, u32 physical, u32 ap, u32 domain)
|
||||
{
|
||||
m_bus.WriteARM(TABLE + index * 4,
|
||||
(physical & 0xfff00000) | (ap << 10) | (domain << 5) | 0x12);
|
||||
}
|
||||
|
||||
// The section of `virtual_address` points at the coarse table, whose entry for it maps a small
|
||||
// page of `physical` with the same AP in its four subpages.
|
||||
void MapSmallPage(u32 virtual_address, u32 physical, u32 ap, u32 domain)
|
||||
{
|
||||
m_bus.WriteARM(TABLE + (virtual_address >> 20) * 4, COARSE | (domain << 5) | 0x11);
|
||||
m_bus.WriteARM(COARSE + ((virtual_address >> 12) & 0xff) * 4,
|
||||
(physical & 0xfffff000) | ((ap * 0x55) << 4) | 2);
|
||||
}
|
||||
|
||||
// Code at a physical address (its view in section 0x800 or 0x880 is `physical` + base).
|
||||
void Code(u32 physical, std::initializer_list<u32> instructions)
|
||||
{
|
||||
for (const u32 instruction : instructions)
|
||||
{
|
||||
m_bus.WriteARM(physical, instruction);
|
||||
physical += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void Start(u32 cpsr, u32 pc)
|
||||
{
|
||||
m_core.SetCPSR(cpsr);
|
||||
m_core.SetRegister(15, pc);
|
||||
}
|
||||
|
||||
void Run(u64 cycles = 48) { m_core.RunCycles(cycles); }
|
||||
|
||||
void ExpectDataAbort(u32 instruction_address, u32 dfsr, u32 far, u32 spsr)
|
||||
{
|
||||
EXPECT_EQ(m_core.GetCPSR() & 0x1f, ABORT_MODE);
|
||||
EXPECT_EQ(m_core.GetRegister(12), 0xdu) << "not the data abort vector";
|
||||
EXPECT_EQ(m_core.GetRegister(14), instruction_address + 8);
|
||||
EXPECT_EQ(m_core.GetRegister(8), spsr);
|
||||
EXPECT_EQ(m_core.GetRegister(9), dfsr);
|
||||
EXPECT_EQ(m_core.GetRegister(10), far);
|
||||
}
|
||||
|
||||
void ExpectPrefetchAbort(u32 instruction_address, u32 ifsr, u32 spsr)
|
||||
{
|
||||
EXPECT_EQ(m_core.GetCPSR() & 0x1f, ABORT_MODE);
|
||||
EXPECT_EQ(m_core.GetRegister(12), 0xcu) << "not the prefetch abort vector";
|
||||
EXPECT_EQ(m_core.GetRegister(14), instruction_address + 4);
|
||||
EXPECT_EQ(m_core.GetRegister(8), spsr);
|
||||
EXPECT_EQ(m_core.GetRegister(11), ifsr);
|
||||
// A prefetch abort leaves the FAR as it was.
|
||||
EXPECT_EQ(m_core.GetRegister(10), FAR_SENTINEL);
|
||||
}
|
||||
|
||||
TestBus& Bus() { return m_bus; }
|
||||
ARMCore& Core() { return m_core; }
|
||||
|
||||
private:
|
||||
TestBus m_bus;
|
||||
ARMCore m_core;
|
||||
};
|
||||
|
||||
template <typename Case>
|
||||
void ForEachEngine(Case run_case)
|
||||
{
|
||||
for (const bool jit : {false, true})
|
||||
{
|
||||
#if !defined(_M_X86_64)
|
||||
if (jit)
|
||||
continue;
|
||||
#endif
|
||||
SCOPED_TRACE(jit ? "JIT" : "interpreter");
|
||||
MMUFaultMachine machine(jit);
|
||||
run_case(machine);
|
||||
}
|
||||
}
|
||||
|
||||
using M = MMUFaultMachine;
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, SectionTranslationFaultIsADataAbort)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(1, 0x11111111);
|
||||
m.Core().SetRegister(2, 0x90000004); // section 0x900 has no descriptor
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x05, 0x90000004, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x11111111u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, PageTranslationFaultReportsItsDomain)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSmallPage(0x90000000, 0x2000, 3, 2);
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, 0x90001000); // the next small page has no descriptor
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x27, 0x90001000, M::SVC_CPSR);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, DomainWithoutAccessFaultsForSectionsAndPages)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSection(0x900, 0, 3, 2);
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, 0x90002000);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x29, 0x90002000, M::SVC_CPSR);
|
||||
});
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSmallPage(0x91000000, 0x2000, 3, 4);
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, 0x91000010);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x4b, 0x91000010, M::SVC_CPSR);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, UserAccessFollowsAP)
|
||||
{
|
||||
// AP 01: privileged only. AP 10: User read-only. AP 11: full access.
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, M::KERNEL + 0x2000);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::USER_CODE, 0x0d, M::KERNEL + 0x2000, M::USER_CPSR);
|
||||
});
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSection(0x920, 0, 2, 0);
|
||||
m.Bus().WriteARM(0x2000, 0x12345678);
|
||||
m.Code(0x1000, {0xe5921000, 0xe5823000}); // ldr r1, [r2]; str r3, [r2]
|
||||
m.Core().SetRegister(2, 0x92002000);
|
||||
m.Core().SetRegister(3, 0xabcdef01);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x12345678u);
|
||||
m.ExpectDataAbort(M::USER_CODE + 4, 0x0d, 0x92002000, M::USER_CPSR);
|
||||
EXPECT_EQ(m.Bus()[0x2000], 0x12u) << "the faulting store wrote memory";
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, APZeroDependsOnTheSAndRBits)
|
||||
{
|
||||
struct Case
|
||||
{
|
||||
u32 control_bits;
|
||||
bool user;
|
||||
bool write;
|
||||
bool allowed;
|
||||
};
|
||||
constexpr u32 S = 1U << 8;
|
||||
constexpr u32 R = 1U << 9;
|
||||
for (const Case c : {Case{0, false, false, false}, Case{S, false, false, true},
|
||||
Case{S, false, true, false}, Case{S, true, false, false},
|
||||
Case{R, true, false, true}, Case{R, false, true, false},
|
||||
Case{S | R, false, false, false}})
|
||||
{
|
||||
SCOPED_TRACE(testing::Message() << "control " << std::hex << c.control_bits << " user "
|
||||
<< c.user << " write " << c.write);
|
||||
ForEachEngine([&](M& m) {
|
||||
m.MapSection(0x900, 0, 0, 0);
|
||||
m.Bus().WriteARM(0x2000, 0x0badf00d);
|
||||
m.Core().GetCP15State().control |= c.control_bits;
|
||||
m.Code(0x1000, {c.write ? 0xe5821000u : 0xe5921000u}); // str/ldr r1, [r2]
|
||||
m.Core().SetRegister(1, 0x11111111);
|
||||
m.Core().SetRegister(2, 0x90002000);
|
||||
const u32 code = c.user ? M::USER_CODE : M::CODE;
|
||||
m.Start(c.user ? M::USER_CPSR : M::SVC_CPSR, code);
|
||||
m.Run();
|
||||
if (c.allowed)
|
||||
{
|
||||
EXPECT_NE(m.Core().GetCPSR() & 0x1f, M::ABORT_MODE);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), c.write ? 0x11111111u : 0x0badf00du);
|
||||
}
|
||||
else
|
||||
{
|
||||
m.ExpectDataAbort(code, 0x0d, 0x90002000, c.user ? M::USER_CPSR : M::SVC_CPSR);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, ManagerDomainSkipsAP)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSection(0x900, 0, 0, 5);
|
||||
m.Core().GetCP15State().domain_access_control = 1 | (3 << 10);
|
||||
m.Bus().WriteARM(0x2000, 0x600dcafe);
|
||||
m.Code(0x1000, {0xe5921000, 0xe5823004}); // ldr r1, [r2]; str r3, [r2, #4]
|
||||
m.Core().SetRegister(2, 0x90002000);
|
||||
m.Core().SetRegister(3, 0x01020304);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run(8);
|
||||
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, 0x10u);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x600dcafeu);
|
||||
EXPECT_EQ(m.Bus()[0x2004], 0x01u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, FetchFromAnUnmappedSectionIsAPrefetchAbort)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe12fff13}); // bx r3
|
||||
m.Core().SetRegister(3, 0x90000000);
|
||||
m.Run();
|
||||
m.ExpectPrefetchAbort(0x90000000, 0x05, M::SVC_CPSR);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, UserFetchFromPrivilegedCodeIsAPrefetchAbort)
|
||||
{
|
||||
// ARMv5 has no execute permission: a fetch needs read permission, so AP 01 stops User code.
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe12fff13}); // bx r3
|
||||
m.Core().SetRegister(3, M::CODE + 0x40);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run();
|
||||
m.ExpectPrefetchAbort(M::CODE + 0x40, 0x0d, M::USER_CPSR);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, CachedCodeIsCheckedAgainAtEveryEntry)
|
||||
{
|
||||
// The JIT compiles the block in Supervisor mode; entering it again in User mode must fault.
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1040, {0xe3a07001, 0xeafffffe}); // mov r7, #1; b .
|
||||
m.Start(M::SVC_CPSR, M::CODE + 0x40);
|
||||
m.Run(8);
|
||||
EXPECT_EQ(m.Core().GetRegister(7), 1u);
|
||||
m.Core().SetRegister(7, 0);
|
||||
m.Start(M::USER_CPSR, M::CODE + 0x40);
|
||||
m.Run();
|
||||
m.ExpectPrefetchAbort(M::CODE + 0x40, 0x0d, M::USER_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(7), 0u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, RevokingTheCodeDomainStopsTheNextFetch)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSection(0x930, 0, 1, 3);
|
||||
m.Core().GetCP15State().domain_access_control = 1 | (1 << 6);
|
||||
m.Code(0x1000, {0xee034f10, 0xe3a07001}); // mcr p15, 0, r4, c3, c0, 0; mov r7, #1
|
||||
m.Core().SetRegister(4, 1); // domain 3: no access
|
||||
m.Start(M::SVC_CPSR, 0x93001000);
|
||||
m.Run();
|
||||
m.ExpectPrefetchAbort(0x93001004, 0x39, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(7), 0u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, BlockTransferAbortRestoresTheBase)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe8b0000e}); // ldmia r0!, {r1, r2, r3}
|
||||
for (u32 reg = 1; reg <= 3; ++reg)
|
||||
m.Core().SetRegister(reg, 0x11111111 * reg);
|
||||
m.Core().SetRegister(0, 0x800ffffc); // the second word is in unmapped section 0x801
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x05, 0x80100000, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(0), 0x800ffffcu);
|
||||
for (u32 reg = 1; reg <= 3; ++reg)
|
||||
EXPECT_EQ(m.Core().GetRegister(reg), 0x11111111 * reg);
|
||||
});
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xe8a00006}); // stmia r0!, {r1, r2}
|
||||
m.Core().SetRegister(0, 0x800ffffc);
|
||||
m.Core().SetRegister(1, 0xa1a2a3a4);
|
||||
m.Core().SetRegister(2, 0xb1b2b3b4);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x05, 0x80100000, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(0), 0x800ffffcu);
|
||||
// The word stored before the fault stays written, as on ARM926.
|
||||
EXPECT_EQ(m.Bus()[0xffffc], 0xa1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, LDRTChecksUserPermissionsInAPrivilegedMode)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Bus().WriteARM(0x2000, 0x13572468);
|
||||
m.Code(0x1000, {0xe5921000, 0xe4b23000}); // ldr r1, [r2]; ldrt r3, [r2], #0
|
||||
m.Core().SetRegister(2, M::KERNEL + 0x2000);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x13572468u);
|
||||
m.ExpectDataAbort(M::CODE + 4, 0x0d, M::KERNEL + 0x2000, M::SVC_CPSR);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, AlignmentFaultsNeedTheABit)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Core().GetCP15State().control |= 2;
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, M::KERNEL + 0x2002);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x01, M::KERNEL + 0x2002, M::SVC_CPSR);
|
||||
});
|
||||
ForEachEngine([](M& m) {
|
||||
m.Bus().WriteARM(0x2000, 0x11223344);
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]: rotated, no fault
|
||||
m.Core().SetRegister(2, M::KERNEL + 0x2002);
|
||||
m.Run(4);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x33441122u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, CP15FromUserModeIsUndefined)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Code(0x1000, {0xee034f10}); // mcr p15, 0, r4, c3, c0, 0
|
||||
m.Core().SetRegister(4, 0xffffffff);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run(8);
|
||||
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, M::UNDEFINED_MODE);
|
||||
EXPECT_EQ(m.Core().GetRegister(15), 0xffff0004u);
|
||||
EXPECT_EQ(m.Core().GetCP15State().domain_access_control, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, TLBEntriesStaleUntilTheirOwnInvalidation)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSmallPage(0x94000000, 0x3000, 3, 0);
|
||||
m.Bus().WriteARM(0x3000, 0xcafef00d);
|
||||
m.Code(0x1000, {
|
||||
0xe5921000, // ldr r1, [r2]: fills the TLB
|
||||
0xe5854000, // str r4, [r5]: clears the page's descriptor
|
||||
0xe5923000, // ldr r3, [r2]: the stale entry still translates
|
||||
0xee086f37, // mcr p15, 0, r6, c8, c7, 1: another page's MVA
|
||||
0xe5927000, // ldr r7, [r2]: still stale
|
||||
0xee082f37, // mcr p15, 0, r2, c8, c7, 1: this page's MVA
|
||||
0xe5920000, // ldr r0, [r2]: translation fault
|
||||
});
|
||||
m.Core().SetRegister(0, 0x5a5a5a5a);
|
||||
m.Core().SetRegister(2, 0x94000000);
|
||||
m.Core().SetRegister(4, 0);
|
||||
m.Core().SetRegister(5, M::KERNEL + M::COARSE);
|
||||
m.Core().SetRegister(6, 0x95000000);
|
||||
m.Run(64);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0xcafef00du);
|
||||
EXPECT_EQ(m.Core().GetRegister(3), 0xcafef00du);
|
||||
EXPECT_EQ(m.Core().GetRegister(7), 0xcafef00du);
|
||||
m.ExpectDataAbort(M::CODE + 24, 0x07, 0x94000000, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(0), 0x5a5a5a5au);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, DACRChangesNeedNoTLBMaintenance)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.MapSection(0x960, 0, 3, 6);
|
||||
m.Core().GetCP15State().domain_access_control = 1 | (1 << 12);
|
||||
m.Bus().WriteARM(0x2000, 0x76543210);
|
||||
m.Code(0x1000, {0xe5921000, 0xee034f10, 0xe5923000}); // ldr; mcr c3 (r4); ldr r3
|
||||
m.Core().SetRegister(2, 0x96002000);
|
||||
m.Core().SetRegister(3, 0x33333333);
|
||||
m.Core().SetRegister(4, 1);
|
||||
m.Run();
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x76543210u);
|
||||
m.ExpectDataAbort(M::CODE + 8, 0x69, 0x96002000, M::SVC_CPSR);
|
||||
EXPECT_EQ(m.Core().GetRegister(3), 0x33333333u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, FCSEFaultsReportTheModifiedAddress)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Core().GetCP15State().process_id = 0x02000000;
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]
|
||||
m.Core().SetRegister(2, 0x00001000);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE, 0x05, 0x02001000, M::SVC_CPSR);
|
||||
});
|
||||
ForEachEngine([](M& m) {
|
||||
m.Core().GetCP15State().process_id = 0x02000000;
|
||||
m.MapSection(0x020, 0, 3, 0);
|
||||
m.Code(0x1000, {0xe5921000}); // ldr r1, [r2]: MVA 0x02001000, this instruction itself
|
||||
m.Core().SetRegister(2, 0x00001000);
|
||||
m.Run(4);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0xe5921000u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, ThumbDataAbortReturnsToTheInstructionPlusEight)
|
||||
{
|
||||
ForEachEngine([](M& m) {
|
||||
m.Bus().WriteThumb(0x1080, 0x6811); // ldr r1, [r2, #0]
|
||||
m.Core().SetRegister(2, 0x90000000);
|
||||
m.Start(M::SVC_CPSR | 0x20, M::CODE + 0x80);
|
||||
m.Run();
|
||||
m.ExpectDataAbort(M::CODE + 0x80, 0x05, 0x90000000, M::SVC_CPSR | 0x20);
|
||||
EXPECT_EQ(m.Core().GetCPSR() & 0x20, 0u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, TheJITStillRunsNativeCode)
|
||||
{
|
||||
#if defined(_M_X86_64)
|
||||
MMUFaultMachine m(true);
|
||||
m.Code(0x1000, {
|
||||
0xe3a00000, // mov r0, #0
|
||||
0xe2800001, // add r0, r0, #1
|
||||
0xe3500010, // cmp r0, #16
|
||||
0x1afffffc, // bne 0x1004
|
||||
0xeafffffe, // b .
|
||||
});
|
||||
m.Run(96);
|
||||
EXPECT_EQ(m.Core().GetRegister(0), 16u);
|
||||
EXPECT_GT(m.Core().GetJitNativeExecutedInstructions(), 0u);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCoreMMUFaults, DisabledFaultsKeepTheIdentityFallbackAndUserCP15)
|
||||
{
|
||||
// The IOS LLE model is unchanged unless SetMMUFaultsEnabled(true) is called.
|
||||
for (const bool jit : {false, true})
|
||||
{
|
||||
#if !defined(_M_X86_64)
|
||||
if (jit)
|
||||
continue;
|
||||
#endif
|
||||
SCOPED_TRACE(jit ? "JIT" : "interpreter");
|
||||
MMUFaultMachine m(jit);
|
||||
m.Core().SetMMUFaultsEnabled(false);
|
||||
m.Bus().WriteARM(0x2000, 0x44556677);
|
||||
m.Code(0x1000, {0xe5921000, 0xee034f10, 0xeafffffe}); // ldr; mcr c3 (r4); b .
|
||||
m.Core().SetRegister(2, 0x00002000); // section 0x000: no descriptor
|
||||
m.Core().SetRegister(4, 0x55555555);
|
||||
m.Start(M::USER_CPSR, M::USER_CODE);
|
||||
m.Run(8);
|
||||
EXPECT_EQ(m.Core().GetRegister(1), 0x44556677u);
|
||||
EXPECT_EQ(m.Core().GetCP15State().domain_access_control, 0x55555555u);
|
||||
EXPECT_EQ(m.Core().GetCPSR() & 0x1f, 0x10u);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // namespace IOS::LLE
|
||||
Reference in new issue
Block a user