Skip to content

Commit

Permalink
PowerPC: Implement broken masking for uncached unaligned writes
Browse files Browse the repository at this point in the history
This implements the behavior described in
https://bugs.dolphin-emu.org/issues/12565.

Thank you to eigenform, delroth, phire, marcan, segher, and Extrems
for all helping in one way or another with the efforts to reverse
engineer this behavior, and to Rylie for reporting the issue.
  • Loading branch information
JosJuice committed Aug 4, 2021
1 parent 12629be commit 543ed8a
Showing 1 changed file with 57 additions and 35 deletions.
92 changes: 57 additions & 35 deletions Source/Core/Core/PowerPC/MMU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "Common/Assert.h"
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"

#include "Core/ConfigManager.h"
#include "Core/HW/CPU.h"
Expand Down Expand Up @@ -277,6 +278,8 @@ static void WriteToHardware(u32 em_address, const u32 data, const u32 size)
return;
}

bool wi = false;

if (!never_translate && MSR.DR)
{
auto translated_addr = TranslateAddress<flag>(em_address);
Expand All @@ -287,41 +290,7 @@ static void WriteToHardware(u32 em_address, const u32 data, const u32 size)
return;
}
em_address = translated_addr.address;
}

const u32 swapped_data = Common::swap32(Common::RotateRight(data, size * 8));

if (Memory::m_pRAM && (em_address & 0xF8000000) == 0x00000000)
{
// Handle RAM; the masking intentionally discards bits (essentially creating
// mirrors of memory).
// TODO: Only the first GetRamSizeReal() is supposed to be backed by actual memory.
std::memcpy(&Memory::m_pRAM[em_address & Memory::GetRamMask()], &swapped_data, size);
return;
}

if (Memory::m_pEXRAM && (em_address >> 28) == 0x1 &&
(em_address & 0x0FFFFFFF) < Memory::GetExRamSizeReal())
{
std::memcpy(&Memory::m_pEXRAM[em_address & 0x0FFFFFFF], &swapped_data, size);
return;
}

// Locked L1 technically doesn't have a fixed address, but games all use 0xE0000000.
if (Memory::m_pL1Cache && (em_address >> 28 == 0xE) &&
(em_address < (0xE0000000 + Memory::GetL1CacheSize())))
{
std::memcpy(&Memory::m_pL1Cache[em_address & 0x0FFFFFFF], &swapped_data, size);
return;
}

// In Fake-VMEM mode, we need to map the memory somewhere into
// physical memory for BAT translation to work; we currently use
// [0x7E000000, 0x80000000).
if (Memory::m_pFakeVMEM && ((em_address & 0xFE000000) == 0x7E000000))
{
std::memcpy(&Memory::m_pFakeVMEM[em_address & Memory::GetFakeVMemMask()], &swapped_data, size);
return;
wi = translated_addr.wi;
}

// Check for a gather pipe write.
Expand Down Expand Up @@ -381,6 +350,59 @@ static void WriteToHardware(u32 em_address, const u32 data, const u32 size)
}
}

const u32 swapped_data = Common::swap32(Common::RotateRight(data, size * 8));

// Locked L1 technically doesn't have a fixed address, but games all use 0xE0000000.
if (Memory::m_pL1Cache && (em_address >> 28 == 0xE) &&
(em_address < (0xE0000000 + Memory::GetL1CacheSize())))
{
std::memcpy(&Memory::m_pL1Cache[em_address & 0x0FFFFFFF], &swapped_data, size);
return;
}

if (wi && (size < 4 || (em_address & 0x3)))
{
// When a write to memory is performed in hardware, 64 bits of data are sent to the memory
// controller along with a mask. This mask is encoded using just two bits of data - one for
// the upper 32 bits and one for the lower 32 bits - which leads to some odd data duplication
// behavior for write-through/cache-inhibited writes with a start address or end address that
// isn't 32-bit aligned. See https://bugs.dolphin-emu.org/issues/12565 for details.

const u32 rotated_data = Common::RotateRight(data, ((em_address & 0x3) + size) * 8);

for (u32 addr = em_address & ~0x7; addr < em_address + size; addr += 8)
{
WriteToHardware<flag, true>(addr, rotated_data, 4);
WriteToHardware<flag, true>(addr + 4, rotated_data, 4);
}

return;
}

if (Memory::m_pRAM && (em_address & 0xF8000000) == 0x00000000)
{
// Handle RAM; the masking intentionally discards bits (essentially creating
// mirrors of memory).
std::memcpy(&Memory::m_pRAM[em_address & Memory::GetRamMask()], &swapped_data, size);
return;
}

if (Memory::m_pEXRAM && (em_address >> 28) == 0x1 &&
(em_address & 0x0FFFFFFF) < Memory::GetExRamSizeReal())
{
std::memcpy(&Memory::m_pEXRAM[em_address & 0x0FFFFFFF], &swapped_data, size);
return;
}

// In Fake-VMEM mode, we need to map the memory somewhere into
// physical memory for BAT translation to work; we currently use
// [0x7E000000, 0x80000000).
if (Memory::m_pFakeVMEM && ((em_address & 0xFE000000) == 0x7E000000))
{
std::memcpy(&Memory::m_pFakeVMEM[em_address & Memory::GetFakeVMemMask()], &swapped_data, size);
return;
}

PanicAlertFmt("Unable to resolve write address {:x} PC {:x}", em_address, PC);
}
// =====================
Expand Down

0 comments on commit 543ed8a

Please sign in to comment.