Fix the addresses of MMIO registers.

MMIO registers are located at 0x0C000000 and 0x0D000000, not 0xCC000000.
The 0xCC000000 addresses are just an artifact of address translation.
This commit is contained in:
magumagu 2015-02-11 18:01:47 -08:00
parent 5b6a947e8f
commit f316265973
8 changed files with 202 additions and 163 deletions

View file

@ -16,9 +16,9 @@ namespace MMIO
{
// There are three main MMIO blocks on the Wii (only one on the GameCube):
// - 0xCC00xxxx: GameCube MMIOs (CP, PE, VI, PI, MI, DSP, DVD, SI, EI, AI, GP)
// - 0xCD00xxxx: Wii MMIOs and GC mirrors (IPC, DVD, SI, EI, AI)
// - 0xCD80xxxx: Mirror of 0xCD00xxxx.
// - 0x0C00xxxx: GameCube MMIOs (CP, PE, VI, PI, MI, DSP, DVD, SI, EI, AI, GP)
// - 0x0D00xxxx: Wii MMIOs and GC mirrors (IPC, DVD, SI, EI, AI)
// - 0x0D80xxxx: Mirror of 0x0D00xxxx.
//
// In practice, since the third block is a mirror of the second one, we can
// assume internally that there are only two blocks: one for GC, one for Wii.
@ -41,15 +41,15 @@ const u32 NUM_MMIOS = NUM_BLOCKS * BLOCK_SIZE;
// interface.
inline bool IsMMIOAddress(u32 address)
{
if (address == 0xCC008000)
if (address == 0x0C008000)
return false; // WG Pipe
if ((address & 0xFFFF0000) == 0xCC000000)
if ((address & 0xFFFF0000) == 0x0C000000)
return true; // GameCube MMIOs
if(SConfig::GetInstance().m_LocalCoreStartupParameter.bWii)
{
return ((address & 0xFFFF0000) == 0xCD000000) || // Wii MMIOs
((address & 0xFFFF0000) == 0xCD800000); // Mirror of Wii MMIOs
return ((address & 0xFFFF0000) == 0x0D000000) || // Wii MMIOs
((address & 0xFFFF0000) == 0x0D800000); // Mirror of Wii MMIOs
}
return false;
@ -61,9 +61,9 @@ inline bool IsMMIOAddress(u32 address)
// The block ID can easily be computed by simply checking bit 24 (CC vs. CD).
inline u32 UniqueID(u32 address)
{
_dbg_assert_msg_(MEMMAP, ((address & 0xFFFF0000) == 0xCC000000) ||
((address & 0xFFFF0000) == 0xCD000000) ||
((address & 0xFFFF0000) == 0xCD800000),
_dbg_assert_msg_(MEMMAP, ((address & 0xFFFF0000) == 0x0C000000) ||
((address & 0xFFFF0000) == 0x0D000000) ||
((address & 0xFFFF0000) == 0x0D800000),
"Trying to get the ID of a non-existing MMIO address.");
return (((address >> 24) & 1) << 16) | (address & 0xFFFF);

View file

@ -74,27 +74,27 @@ MMIO::Mapping* mmio_mapping;
static void InitMMIO(MMIO::Mapping* mmio)
{
g_video_backend->RegisterCPMMIO(mmio, 0xCC000000);
PixelEngine::RegisterMMIO(mmio, 0xCC001000);
VideoInterface::RegisterMMIO(mmio, 0xCC002000);
ProcessorInterface::RegisterMMIO(mmio, 0xCC003000);
MemoryInterface::RegisterMMIO(mmio, 0xCC004000);
DSP::RegisterMMIO(mmio, 0xCC005000);
DVDInterface::RegisterMMIO(mmio, 0xCC006000);
SerialInterface::RegisterMMIO(mmio, 0xCC006400);
ExpansionInterface::RegisterMMIO(mmio, 0xCC006800);
AudioInterface::RegisterMMIO(mmio, 0xCC006C00);
g_video_backend->RegisterCPMMIO(mmio, 0x0C000000);
PixelEngine::RegisterMMIO(mmio, 0x0C001000);
VideoInterface::RegisterMMIO(mmio, 0x0C002000);
ProcessorInterface::RegisterMMIO(mmio, 0x0C003000);
MemoryInterface::RegisterMMIO(mmio, 0x0C004000);
DSP::RegisterMMIO(mmio, 0x0C005000);
DVDInterface::RegisterMMIO(mmio, 0x0C006000);
SerialInterface::RegisterMMIO(mmio, 0x0C006400);
ExpansionInterface::RegisterMMIO(mmio, 0x0C006800);
AudioInterface::RegisterMMIO(mmio, 0x0C006C00);
}
static void InitMMIOWii(MMIO::Mapping* mmio)
{
InitMMIO(mmio);
WII_IPCInterface::RegisterMMIO(mmio, 0xCD000000);
DVDInterface::RegisterMMIO(mmio, 0xCD006000);
SerialInterface::RegisterMMIO(mmio, 0xCD006400);
ExpansionInterface::RegisterMMIO(mmio, 0xCD006800);
AudioInterface::RegisterMMIO(mmio, 0xCD006C00);
WII_IPCInterface::RegisterMMIO(mmio, 0x0D000000);
DVDInterface::RegisterMMIO(mmio, 0x0D006000);
SerialInterface::RegisterMMIO(mmio, 0x0D006400);
ExpansionInterface::RegisterMMIO(mmio, 0x0D006800);
AudioInterface::RegisterMMIO(mmio, 0x0D006C00);
}
bool IsInitialized()

View file

@ -147,15 +147,20 @@ void JitArm64::SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 o
if (update)
MOV(gpr.R(addr), addr_reg);
u32 access_size = BackPatchInfo::GetFlagSize(flags);
u32 mmio_address = 0;
if (is_immediate)
mmio_address = PowerPC::IsOptimizableMMIOAccess(imm_addr, access_size);
if (is_immediate && PowerPC::IsOptimizableRAMAddress(imm_addr))
{
EmitBackpatchRoutine(this, flags, true, false, dest_reg, XA);
}
else if (is_immediate && MMIO::IsMMIOAddress(imm_addr))
else if (mmio_address)
{
MMIOLoadToReg(Memory::mmio_mapping, this,
regs_in_use, fprs_in_use, dest_reg,
imm_addr, flags);
mmio_address, flags);
}
else
{
@ -288,18 +293,22 @@ void JitArm64::SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s
ARM64Reg XA = EncodeRegTo64(addr_reg);
u32 access_size = BackPatchInfo::GetFlagSize(flags);
u32 mmio_address = 0;
if (is_immediate)
mmio_address = PowerPC::IsOptimizableMMIOAccess(imm_addr, access_size);
if (is_immediate && PowerPC::IsOptimizableRAMAddress(imm_addr))
{
MOVI2R(XA, imm_addr);
EmitBackpatchRoutine(this, flags, true, false, RS, XA);
}
else if (is_immediate && MMIO::IsMMIOAddress(imm_addr) &&
!(flags & BackPatchInfo::FLAG_REVERSE))
else if (mmio_address && !(flags & BackPatchInfo::FLAG_REVERSE))
{
MMIOWriteRegToAddr(Memory::mmio_mapping, this,
regs_in_use, fprs_in_use, RS,
imm_addr, flags);
mmio_address, flags);
}
else
{

View file

@ -20,6 +20,21 @@ struct BackPatchInfo
FLAG_EXTEND = (1 << 8),
};
static u32 GetFlagSize(u32 flags)
{
if (flags & FLAG_SIZE_8)
return 8;
if (flags & FLAG_SIZE_16)
return 16;
if (flags & FLAG_SIZE_32)
return 32;
if (flags & FLAG_SIZE_F32)
return 32;
if (flags & FLAG_SIZE_F64)
return 64;
return 0;
}
u32 m_fastmem_size;
u32 m_fastmem_trouble_inst_offset;
u32 m_slowmem_size;

View file

@ -289,116 +289,111 @@ void EmuCodeBlock::SafeLoadToReg(X64Reg reg_value, const Gen::OpArg & opAddress,
registersInUseAtLoc[mov] = registersInUse;
jit->js.fastmemLoadStore = mov;
return;
}
else
u32 mem_mask = Memory::ADDR_MASK_HW_ACCESS;
// The following masks the region used by the GC/Wii virtual memory lib
mem_mask |= Memory::ADDR_MASK_MEM1;
if (opAddress.IsImm())
{
u32 mem_mask = Memory::ADDR_MASK_HW_ACCESS;
u32 address = (u32)opAddress.offset + offset;
// The following masks the region used by the GC/Wii virtual memory lib
mem_mask |= Memory::ADDR_MASK_MEM1;
if (opAddress.IsImm())
// If the address is known to be RAM, just load it directly.
if (PowerPC::IsOptimizableRAMAddress(address))
{
u32 address = (u32)opAddress.offset + offset;
// If we know the address, try the following loading methods in
// order:
//
// 1. If the address is in RAM, generate an unsafe load (directly
// access the RAM buffer and load from there).
// 2. If the address is in the MMIO range, find the appropriate
// MMIO handler and generate the code to load using the handler.
// 3. Otherwise, just generate a call to PowerPC::Read_* with the
// address hardcoded.
if (PowerPC::IsOptimizableRAMAddress(address))
{
UnsafeLoadToReg(reg_value, opAddress, accessSize, offset, signExtend);
}
else if (MMIO::IsMMIOAddress(address) && accessSize != 64)
{
MMIOLoadToReg(Memory::mmio_mapping, reg_value, registersInUse,
address, accessSize, signExtend);
}
else
{
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
switch (accessSize)
{
case 64: ABI_CallFunctionC((void *)&PowerPC::Read_U64, address); break;
case 32: ABI_CallFunctionC((void *)&PowerPC::Read_U32, address); break;
case 16: ABI_CallFunctionC((void *)&PowerPC::Read_U16_ZX, address); break;
case 8: ABI_CallFunctionC((void *)&PowerPC::Read_U8_ZX, address); break;
}
ABI_PopRegistersAndAdjustStack(registersInUse, 0);
MemoryExceptionCheck();
if (signExtend && accessSize < 32)
{
// Need to sign extend values coming from the Read_U* functions.
MOVSX(32, accessSize, reg_value, R(ABI_RETURN));
}
else if (reg_value != ABI_RETURN)
{
MOVZX(64, accessSize, reg_value, R(ABI_RETURN));
}
}
UnsafeLoadToReg(reg_value, opAddress, accessSize, offset, signExtend);
return;
}
else
// If the address maps to an MMIO register, inline MMIO read code.
u32 mmioAddress = PowerPC::IsOptimizableMMIOAccess(address, accessSize);
if (accessSize != 64 && mmioAddress)
{
_assert_msg_(DYNA_REC, opAddress.IsSimpleReg(), "Incorrect use of SafeLoadToReg (address isn't register or immediate)");
X64Reg reg_addr = opAddress.GetSimpleReg();
if (offset)
{
reg_addr = RSCRATCH;
LEA(32, RSCRATCH, MDisp(opAddress.GetSimpleReg(), offset));
}
FixupBranch slow, exit;
slow = CheckIfSafeAddress(R(reg_value), reg_addr, registersInUse, mem_mask);
UnsafeLoadToReg(reg_value, R(reg_addr), accessSize, 0, signExtend);
if (farcode.Enabled())
SwitchToFarCode();
else
exit = J(true);
SetJumpTarget(slow);
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment);
switch (accessSize)
{
case 64:
ABI_CallFunctionR((void *)&PowerPC::Read_U64, reg_addr);
break;
case 32:
ABI_CallFunctionR((void *)&PowerPC::Read_U32, reg_addr);
break;
case 16:
ABI_CallFunctionR((void *)&PowerPC::Read_U16_ZX, reg_addr);
break;
case 8:
ABI_CallFunctionR((void *)&PowerPC::Read_U8_ZX, reg_addr);
break;
}
ABI_PopRegistersAndAdjustStack(registersInUse, rsp_alignment);
MemoryExceptionCheck();
if (signExtend && accessSize < 32)
{
// Need to sign extend values coming from the Read_U* functions.
MOVSX(32, accessSize, reg_value, R(ABI_RETURN));
}
else if (reg_value != ABI_RETURN)
{
MOVZX(64, accessSize, reg_value, R(ABI_RETURN));
}
if (farcode.Enabled())
{
exit = J(true);
SwitchToNearCode();
}
SetJumpTarget(exit);
MMIOLoadToReg(Memory::mmio_mapping, reg_value, registersInUse,
address, accessSize, signExtend);
return;
}
// Fall back to general-case code.
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
switch (accessSize)
{
case 64: ABI_CallFunctionC((void *)&PowerPC::Read_U64, address); break;
case 32: ABI_CallFunctionC((void *)&PowerPC::Read_U32, address); break;
case 16: ABI_CallFunctionC((void *)&PowerPC::Read_U16_ZX, address); break;
case 8: ABI_CallFunctionC((void *)&PowerPC::Read_U8_ZX, address); break;
}
ABI_PopRegistersAndAdjustStack(registersInUse, 0);
MemoryExceptionCheck();
if (signExtend && accessSize < 32)
{
// Need to sign extend values coming from the Read_U* functions.
MOVSX(32, accessSize, reg_value, R(ABI_RETURN));
}
else if (reg_value != ABI_RETURN)
{
MOVZX(64, accessSize, reg_value, R(ABI_RETURN));
}
return;
}
_assert_msg_(DYNA_REC, opAddress.IsSimpleReg(), "Incorrect use of SafeLoadToReg (address isn't register or immediate)");
X64Reg reg_addr = opAddress.GetSimpleReg();
if (offset)
{
reg_addr = RSCRATCH;
LEA(32, RSCRATCH, MDisp(opAddress.GetSimpleReg(), offset));
}
FixupBranch slow, exit;
slow = CheckIfSafeAddress(R(reg_value), reg_addr, registersInUse, mem_mask);
UnsafeLoadToReg(reg_value, R(reg_addr), accessSize, 0, signExtend);
if (farcode.Enabled())
SwitchToFarCode();
else
exit = J(true);
SetJumpTarget(slow);
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
ABI_PushRegistersAndAdjustStack(registersInUse, rsp_alignment);
switch (accessSize)
{
case 64:
ABI_CallFunctionR((void *)&PowerPC::Read_U64, reg_addr);
break;
case 32:
ABI_CallFunctionR((void *)&PowerPC::Read_U32, reg_addr);
break;
case 16:
ABI_CallFunctionR((void *)&PowerPC::Read_U16_ZX, reg_addr);
break;
case 8:
ABI_CallFunctionR((void *)&PowerPC::Read_U8_ZX, reg_addr);
break;
}
ABI_PopRegistersAndAdjustStack(registersInUse, rsp_alignment);
MemoryExceptionCheck();
if (signExtend && accessSize < 32)
{
// Need to sign extend values coming from the Read_U* functions.
MOVSX(32, accessSize, reg_value, R(ABI_RETURN));
}
else if (reg_value != ABI_RETURN)
{
MOVZX(64, accessSize, reg_value, R(ABI_RETURN));
}
if (farcode.Enabled())
{
exit = J(true);
SwitchToNearCode();
}
SetJumpTarget(exit);
}
static OpArg SwapImmediate(int accessSize, OpArg reg_value)

View file

@ -134,7 +134,7 @@ __forceinline static T ReadFromHardware(const u32 em_address)
if (em_address < 0xcc000000)
return EFB_Read(em_address);
else
return (T)Memory::mmio_mapping->Read<typename std::make_unsigned<T>::type>(em_address);
return (T)Memory::mmio_mapping->Read<typename std::make_unsigned<T>::type>(em_address & 0x0FFFFFFF);
}
if ((segment == 0x0 || segment == 0x8 || segment == 0xC) && (em_address & 0x0FFFFFFF) < Memory::REALRAM_SIZE)
{
@ -163,7 +163,7 @@ __forceinline static T ReadFromHardware(const u32 em_address)
if (em_address < 0x0c000000)
return EFB_Read(em_address);
else
return (T)Memory::mmio_mapping->Read<typename std::make_unsigned<T>::type>(em_address | 0xC0000000);
return (T)Memory::mmio_mapping->Read<typename std::make_unsigned<T>::type>(em_address);
}
if (em_address < Memory::REALRAM_SIZE)
{
@ -250,7 +250,7 @@ __forceinline static void WriteToHardware(u32 em_address, const T data)
}
else
{
Memory::mmio_mapping->Write(em_address, data);
Memory::mmio_mapping->Write(em_address & 0x0FFFFFFF, data);
return;
}
}
@ -300,7 +300,7 @@ __forceinline static void WriteToHardware(u32 em_address, const T data)
}
else
{
Memory::mmio_mapping->Write(em_address | 0xC0000000, data);
Memory::mmio_mapping->Write(em_address, data);
return;
}
}
@ -721,6 +721,21 @@ void ClearCacheLine(const u32 address)
Write_U64(0, address + i);
}
u32 IsOptimizableMMIOAccess(u32 address, u32 accessSize)
{
if (!UReg_MSR(MSR).DR)
return 0;
if ((address & 0xF0000000) != 0xC0000000)
return 0;
unsigned translated = address & 0x0FFFFFFF;
bool aligned = (translated & ((accessSize >> 3) - 1)) == 0;
if (!aligned || !MMIO::IsMMIOAddress(translated))
return 0;
return translated;
}
// *********************************************************************************
// Warning: Test Area
//

View file

@ -260,6 +260,7 @@ void InvalidateTLBEntry(u32 address);
// it's safe to optimize a read or write to this address to an unguarded
// memory access. Does not consider page tables.
bool IsOptimizableRAMAddress(const u32 address);
u32 IsOptimizableMMIOAccess(u32 address, u32 accessSize);
} // namespace

View file

@ -9,13 +9,13 @@
TEST(UniqueID, UniqueEnough)
{
std::unordered_set<u32> ids;
for (u32 i = 0xCC000000; i < 0xCC010000; ++i)
for (u32 i = 0x0C000000; i < 0x0C010000; ++i)
{
u32 unique_id = MMIO::UniqueID(i);
EXPECT_EQ(ids.end(), ids.find(unique_id));
ids.insert(unique_id);
}
for (u32 i = 0xCD000000; i < 0xCD010000; ++i)
for (u32 i = 0x0D000000; i < 0x0D010000; ++i)
{
u32 unique_id = MMIO::UniqueID(i);
EXPECT_EQ(ids.end(), ids.find(unique_id));
@ -29,18 +29,22 @@ TEST(IsMMIOAddress, SpecialAddresses)
SConfig::GetInstance().m_LocalCoreStartupParameter.bWii = true;
// WG Pipe address, should not be handled by MMIO.
EXPECT_FALSE(MMIO::IsMMIOAddress(0xCC008000));
EXPECT_FALSE(MMIO::IsMMIOAddress(0x0C008000));
// Memory zone used by games using the "MMU Speedhack".
// Locked L1 cache allocation.
EXPECT_FALSE(MMIO::IsMMIOAddress(0xE0000000));
// Uncached mirror of MEM1, shouldn't be handled by MMIO
EXPECT_FALSE(MMIO::IsMMIOAddress(0xC0000000));
// Effective address of an MMIO register; MMIO only deals with physical
// addresses.
EXPECT_FALSE(MMIO::IsMMIOAddress(0xCC0000E0));
// And lets check some valid addresses too
EXPECT_TRUE(MMIO::IsMMIOAddress(0xCC0000E0)); // Gamecube MMIOs
EXPECT_TRUE(MMIO::IsMMIOAddress(0xCD00008C)); // Wii MMIOs
EXPECT_TRUE(MMIO::IsMMIOAddress(0xCD800F10)); // Mirror of Wii MMIOs
EXPECT_TRUE(MMIO::IsMMIOAddress(0x0C0000E0)); // Gamecube MMIOs
EXPECT_TRUE(MMIO::IsMMIOAddress(0x0D00008C)); // Wii MMIOs
EXPECT_TRUE(MMIO::IsMMIOAddress(0x0D800F10)); // Mirror of Wii MMIOs
SConfig::Shutdown();
}
@ -63,13 +67,13 @@ protected:
TEST_F(MappingTest, ReadConstant)
{
m_mapping->Register(0xCC001234, MMIO::Constant<u8>(0x42), MMIO::Nop<u8>());
m_mapping->Register(0xCC001234, MMIO::Constant<u16>(0x1234), MMIO::Nop<u16>());
m_mapping->Register(0xCC001234, MMIO::Constant<u32>(0xdeadbeef), MMIO::Nop<u32>());
m_mapping->Register(0x0C001234, MMIO::Constant<u8>(0x42), MMIO::Nop<u8>());
m_mapping->Register(0x0C001234, MMIO::Constant<u16>(0x1234), MMIO::Nop<u16>());
m_mapping->Register(0x0C001234, MMIO::Constant<u32>(0xdeadbeef), MMIO::Nop<u32>());
u8 val8 = m_mapping->Read<u8>(0xCC001234);
u16 val16 = m_mapping->Read<u16>(0xCC001234);
u32 val32 = m_mapping->Read<u32>(0xCC001234);
u8 val8 = m_mapping->Read<u8>(0x0C001234);
u16 val16 = m_mapping->Read<u16>(0x0C001234);
u32 val32 = m_mapping->Read<u32>(0x0C001234);
EXPECT_EQ(0x42, val8);
EXPECT_EQ(0x1234, val16);
@ -82,19 +86,19 @@ TEST_F(MappingTest, ReadWriteDirect)
u16 target_16 = 0;
u32 target_32 = 0;
m_mapping->Register(0xCC001234, MMIO::DirectRead<u8>(&target_8), MMIO::DirectWrite<u8>(&target_8));
m_mapping->Register(0xCC001234, MMIO::DirectRead<u16>(&target_16), MMIO::DirectWrite<u16>(&target_16));
m_mapping->Register(0xCC001234, MMIO::DirectRead<u32>(&target_32), MMIO::DirectWrite<u32>(&target_32));
m_mapping->Register(0x0C001234, MMIO::DirectRead<u8>(&target_8), MMIO::DirectWrite<u8>(&target_8));
m_mapping->Register(0x0C001234, MMIO::DirectRead<u16>(&target_16), MMIO::DirectWrite<u16>(&target_16));
m_mapping->Register(0x0C001234, MMIO::DirectRead<u32>(&target_32), MMIO::DirectWrite<u32>(&target_32));
for (u32 i = 0; i < 100; ++i)
{
u8 val8 = m_mapping->Read<u8>(0xCC001234); EXPECT_EQ(i, val8);
u16 val16 = m_mapping->Read<u16>(0xCC001234); EXPECT_EQ(i, val16);
u32 val32 = m_mapping->Read<u32>(0xCC001234); EXPECT_EQ(i, val32);
u8 val8 = m_mapping->Read<u8>(0x0C001234); EXPECT_EQ(i, val8);
u16 val16 = m_mapping->Read<u16>(0x0C001234); EXPECT_EQ(i, val16);
u32 val32 = m_mapping->Read<u32>(0x0C001234); EXPECT_EQ(i, val32);
val8 += 1; m_mapping->Write(0xCC001234, val8);
val16 += 1; m_mapping->Write(0xCC001234, val16);
val32 += 1; m_mapping->Write(0xCC001234, val32);
val8 += 1; m_mapping->Write(0x0C001234, val8);
val16 += 1; m_mapping->Write(0x0C001234, val16);
val32 += 1; m_mapping->Write(0x0C001234, val32);
}
}
@ -102,21 +106,21 @@ TEST_F(MappingTest, ReadWriteComplex)
{
bool read_called = false, write_called = false;
m_mapping->Register(0xCC001234,
m_mapping->Register(0x0C001234,
MMIO::ComplexRead<u8>([&read_called](u32 addr) {
EXPECT_EQ(0xCC001234, addr);
EXPECT_EQ(0x0C001234, addr);
read_called = true;
return 0x12;
}),
MMIO::ComplexWrite<u8>([&write_called](u32 addr, u8 val) {
EXPECT_EQ(0xCC001234, addr);
EXPECT_EQ(0x0C001234, addr);
EXPECT_EQ(0x34, val);
write_called = true;
})
);
u8 val = m_mapping->Read<u8>(0xCC001234); EXPECT_EQ(0x12, val);
m_mapping->Write(0xCC001234, (u8)0x34);
u8 val = m_mapping->Read<u8>(0x0C001234); EXPECT_EQ(0x12, val);
m_mapping->Write(0x0C001234, (u8)0x34);
EXPECT_TRUE(read_called);
EXPECT_TRUE(write_called);