mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-05-21 15:25:43 -04:00

Fixes Rock-Climbing - Mitouhou e no Chousen - Alps Hen (Japan) crashing on boot. Silly game does a 512 word DMA from CDROM to 0x001FFF04, with the mapped memory size set to 4MB, expecting the DMA to trigger a bus error, because if it doesn't, then your kernel memory goes bye bye. A bunch of other games do set the upper 4MB to be unmapped, but this change appears to have no effect on them. As expected, they're probably doing the correct thing.
1857 lines
60 KiB
C++
1857 lines
60 KiB
C++
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
#include "bus.h"
|
|
#include "cdrom.h"
|
|
#include "cpu_code_cache.h"
|
|
#include "cpu_core.h"
|
|
#include "cpu_core_private.h"
|
|
#include "cpu_disasm.h"
|
|
#include "dma.h"
|
|
#include "gpu.h"
|
|
#include "host.h"
|
|
#include "interrupt_controller.h"
|
|
#include "mdec.h"
|
|
#include "pad.h"
|
|
#include "settings.h"
|
|
#include "sio.h"
|
|
#include "spu.h"
|
|
#include "system.h"
|
|
#include "timers.h"
|
|
#include "timing_event.h"
|
|
|
|
#include "util/state_wrapper.h"
|
|
|
|
#include "common/align.h"
|
|
#include "common/assert.h"
|
|
#include "common/error.h"
|
|
#include "common/intrin.h"
|
|
#include "common/log.h"
|
|
#include "common/memmap.h"
|
|
|
|
#include <cstdio>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
Log_SetChannel(Bus);
|
|
|
|
// TODO: Get rid of page code bits, instead use page faults to track SMC.
|
|
|
|
// Exports for external debugger access
|
|
#ifndef __ANDROID__
|
|
namespace Exports {
|
|
|
|
extern "C" {
|
|
#ifdef _WIN32
|
|
_declspec(dllexport) uintptr_t RAM;
|
|
_declspec(dllexport) u32 RAM_SIZE, RAM_MASK;
|
|
#else
|
|
__attribute__((visibility("default"), used)) uintptr_t RAM;
|
|
__attribute__((visibility("default"), used)) u32 RAM_SIZE, RAM_MASK;
|
|
#endif
|
|
}
|
|
|
|
} // namespace Exports
|
|
#endif
|
|
|
|
namespace Bus {
|
|
|
|
namespace {
|
|
union MEMDELAY
|
|
{
|
|
u32 bits;
|
|
|
|
BitField<u32, u8, 4, 4> access_time; // cycles
|
|
BitField<u32, bool, 8, 1> use_com0_time;
|
|
BitField<u32, bool, 9, 1> use_com1_time;
|
|
BitField<u32, bool, 10, 1> use_com2_time;
|
|
BitField<u32, bool, 11, 1> use_com3_time;
|
|
BitField<u32, bool, 12, 1> data_bus_16bit;
|
|
BitField<u32, u8, 16, 5> memory_window_size;
|
|
|
|
static constexpr u32 WRITE_MASK = 0b10101111'00011111'11111111'11111111;
|
|
};
|
|
|
|
union COMDELAY
|
|
{
|
|
u32 bits;
|
|
|
|
BitField<u32, u8, 0, 4> com0;
|
|
BitField<u32, u8, 4, 4> com1;
|
|
BitField<u32, u8, 8, 4> com2;
|
|
BitField<u32, u8, 12, 4> com3;
|
|
BitField<u32, u8, 16, 2> comunk;
|
|
|
|
static constexpr u32 WRITE_MASK = 0b00000000'00000011'11111111'11111111;
|
|
};
|
|
|
|
union MEMCTRL
|
|
{
|
|
u32 regs[MEMCTRL_REG_COUNT];
|
|
|
|
struct
|
|
{
|
|
u32 exp1_base;
|
|
u32 exp2_base;
|
|
MEMDELAY exp1_delay_size;
|
|
MEMDELAY exp3_delay_size;
|
|
MEMDELAY bios_delay_size;
|
|
MEMDELAY spu_delay_size;
|
|
MEMDELAY cdrom_delay_size;
|
|
MEMDELAY exp2_delay_size;
|
|
COMDELAY common_delay;
|
|
};
|
|
};
|
|
|
|
union RAM_SIZE_REG
|
|
{
|
|
u32 bits;
|
|
|
|
// All other bits unknown/unhandled.
|
|
BitField<u32, u8, 9, 3> memory_window;
|
|
};
|
|
} // namespace
|
|
|
|
static void* s_shmem_handle = nullptr;
|
|
|
|
std::bitset<RAM_8MB_CODE_PAGE_COUNT> g_ram_code_bits{};
|
|
u8* g_ram = nullptr;
|
|
u8* g_unprotected_ram = nullptr;
|
|
u32 g_ram_size = 0;
|
|
u32 g_ram_mapped_size = 0;
|
|
u32 g_ram_mask = 0;
|
|
u8* g_bios = nullptr;
|
|
void** g_memory_handlers = nullptr;
|
|
void** g_memory_handlers_isc = nullptr;
|
|
|
|
std::array<TickCount, 3> g_exp1_access_time = {};
|
|
std::array<TickCount, 3> g_exp2_access_time = {};
|
|
std::array<TickCount, 3> g_bios_access_time = {};
|
|
std::array<TickCount, 3> g_cdrom_access_time = {};
|
|
std::array<TickCount, 3> g_spu_access_time = {};
|
|
|
|
static std::vector<u8> s_exp1_rom;
|
|
|
|
static MEMCTRL s_MEMCTRL = {};
|
|
static RAM_SIZE_REG s_RAM_SIZE = {};
|
|
|
|
static std::string s_tty_line_buffer;
|
|
|
|
static CPUFastmemMode s_fastmem_mode = CPUFastmemMode::Disabled;
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
static SharedMemoryMappingArea s_fastmem_arena;
|
|
static std::vector<std::pair<u8*, size_t>> s_fastmem_ram_views;
|
|
#endif
|
|
|
|
static u8** s_fastmem_lut = nullptr;
|
|
|
|
static void SetRAMSize(bool enable_8mb_ram);
|
|
|
|
static std::tuple<TickCount, TickCount, TickCount> CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay);
|
|
static void RecalculateMemoryTimings();
|
|
|
|
static u8* GetLUTFastmemPointer(u32 address, u8* ram_ptr);
|
|
|
|
static void SetRAMPageWritable(u32 page_index, bool writable);
|
|
|
|
static void SetHandlers();
|
|
static void UpdateMappedRAMSize();
|
|
|
|
template<typename FP>
|
|
static FP* OffsetHandlerArray(void** handlers, MemoryAccessSize size, MemoryAccessType type);
|
|
} // namespace Bus
|
|
|
|
namespace MemoryMap {
|
|
static constexpr size_t RAM_OFFSET = 0;
|
|
static constexpr size_t RAM_SIZE = Bus::RAM_8MB_SIZE;
|
|
static constexpr size_t BIOS_OFFSET = RAM_OFFSET + RAM_SIZE;
|
|
static constexpr size_t BIOS_SIZE = Bus::BIOS_SIZE;
|
|
static constexpr size_t LUT_OFFSET = BIOS_OFFSET + BIOS_SIZE;
|
|
static constexpr size_t LUT_SIZE = (sizeof(void*) * Bus::MEMORY_LUT_SLOTS) * 2; // normal and isolated
|
|
static constexpr size_t TOTAL_SIZE = LUT_OFFSET + LUT_SIZE;
|
|
} // namespace MemoryMap
|
|
|
|
#define FIXUP_HALFWORD_OFFSET(size, offset) ((size >= MemoryAccessSize::HalfWord) ? (offset) : ((offset) & ~1u))
|
|
#define FIXUP_HALFWORD_READ_VALUE(size, offset, value) \
|
|
((size >= MemoryAccessSize::HalfWord) ? (value) : ((value) >> (((offset) & u32(1)) * 8u)))
|
|
#define FIXUP_HALFWORD_WRITE_VALUE(size, offset, value) \
|
|
((size >= MemoryAccessSize::HalfWord) ? (value) : ((value) << (((offset) & u32(1)) * 8u)))
|
|
|
|
#define FIXUP_WORD_OFFSET(size, offset) ((size == MemoryAccessSize::Word) ? (offset) : ((offset) & ~3u))
|
|
#define FIXUP_WORD_READ_VALUE(size, offset, value) \
|
|
((size == MemoryAccessSize::Word) ? (value) : ((value) >> (((offset) & 3u) * 8)))
|
|
#define FIXUP_WORD_WRITE_VALUE(size, offset, value) \
|
|
((size == MemoryAccessSize::Word) ? (value) : ((value) << (((offset) & 3u) * 8)))
|
|
|
|
bool Bus::AllocateMemory(Error* error)
|
|
{
|
|
s_shmem_handle =
|
|
MemMap::CreateSharedMemory(MemMap::GetFileMappingName("duckstation").c_str(), MemoryMap::TOTAL_SIZE, error);
|
|
if (!s_shmem_handle)
|
|
{
|
|
#ifndef __linux__
|
|
Error::AddSuffix(error, "\nYou may need to close some programs to free up additional memory.");
|
|
#else
|
|
Error::AddSuffix(
|
|
error, "\nYou may need to close some programs to free up additional memory, or increase the size of /dev/shm.");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
g_ram = static_cast<u8*>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::RAM_OFFSET, nullptr, MemoryMap::RAM_SIZE,
|
|
PageProtect::ReadWrite));
|
|
g_unprotected_ram = static_cast<u8*>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::RAM_OFFSET, nullptr,
|
|
MemoryMap::RAM_SIZE, PageProtect::ReadWrite));
|
|
if (!g_ram || !g_unprotected_ram)
|
|
{
|
|
Error::SetStringView(error, "Failed to map memory for RAM");
|
|
ReleaseMemory();
|
|
return false;
|
|
}
|
|
|
|
VERBOSE_LOG("RAM is mapped at {}.", static_cast<void*>(g_ram));
|
|
|
|
g_bios = static_cast<u8*>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::BIOS_OFFSET, nullptr,
|
|
MemoryMap::BIOS_SIZE, PageProtect::ReadWrite));
|
|
if (!g_bios)
|
|
{
|
|
Error::SetStringView(error, "Failed to map memory for BIOS");
|
|
ReleaseMemory();
|
|
return false;
|
|
}
|
|
|
|
VERBOSE_LOG("BIOS is mapped at {}.", static_cast<void*>(g_bios));
|
|
|
|
g_memory_handlers = static_cast<void**>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::LUT_OFFSET, nullptr,
|
|
MemoryMap::LUT_SIZE, PageProtect::ReadWrite));
|
|
if (!g_memory_handlers)
|
|
{
|
|
Error::SetStringView(error, "Failed to map memory for LUTs");
|
|
ReleaseMemory();
|
|
return false;
|
|
}
|
|
|
|
VERBOSE_LOG("LUTs are mapped at {}.", static_cast<void*>(g_memory_handlers));
|
|
g_memory_handlers_isc = g_memory_handlers + MEMORY_LUT_SLOTS;
|
|
SetHandlers();
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
if (!s_fastmem_arena.Create(FASTMEM_ARENA_SIZE))
|
|
{
|
|
Error::SetStringView(error, "Failed to create fastmem arena");
|
|
ReleaseMemory();
|
|
return false;
|
|
}
|
|
|
|
INFO_LOG("Fastmem base: {}", static_cast<void*>(s_fastmem_arena.BasePointer()));
|
|
#endif
|
|
|
|
#ifndef __ANDROID__
|
|
Exports::RAM = reinterpret_cast<uintptr_t>(g_unprotected_ram);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void Bus::ReleaseMemory()
|
|
{
|
|
#ifndef __ANDROID__
|
|
Exports::RAM = 0;
|
|
Exports::RAM_SIZE = 0;
|
|
Exports::RAM_MASK = 0;
|
|
#endif
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
DebugAssert(s_fastmem_ram_views.empty());
|
|
s_fastmem_arena.Destroy();
|
|
#endif
|
|
|
|
std::free(s_fastmem_lut);
|
|
s_fastmem_lut = nullptr;
|
|
|
|
g_memory_handlers_isc = nullptr;
|
|
if (g_memory_handlers)
|
|
{
|
|
MemMap::UnmapSharedMemory(g_memory_handlers, MemoryMap::LUT_SIZE);
|
|
g_memory_handlers = nullptr;
|
|
}
|
|
|
|
if (g_bios)
|
|
{
|
|
MemMap::UnmapSharedMemory(g_bios, MemoryMap::BIOS_SIZE);
|
|
g_bios = nullptr;
|
|
}
|
|
|
|
if (g_unprotected_ram)
|
|
{
|
|
MemMap::UnmapSharedMemory(g_unprotected_ram, MemoryMap::RAM_SIZE);
|
|
g_unprotected_ram = nullptr;
|
|
}
|
|
|
|
if (g_ram)
|
|
{
|
|
MemMap::UnmapSharedMemory(g_ram, MemoryMap::RAM_SIZE);
|
|
g_ram = nullptr;
|
|
}
|
|
|
|
if (s_shmem_handle)
|
|
{
|
|
MemMap::DestroySharedMemory(s_shmem_handle);
|
|
s_shmem_handle = nullptr;
|
|
}
|
|
}
|
|
|
|
bool Bus::Initialize()
|
|
{
|
|
SetRAMSize(g_settings.enable_8mb_ram);
|
|
Reset();
|
|
return true;
|
|
}
|
|
|
|
void Bus::SetRAMSize(bool enable_8mb_ram)
|
|
{
|
|
g_ram_size = enable_8mb_ram ? RAM_8MB_SIZE : RAM_2MB_SIZE;
|
|
g_ram_mask = enable_8mb_ram ? RAM_8MB_MASK : RAM_2MB_MASK;
|
|
|
|
#ifndef __ANDROID__
|
|
Exports::RAM_SIZE = g_ram_size;
|
|
Exports::RAM_MASK = g_ram_mask;
|
|
#endif
|
|
}
|
|
|
|
void Bus::Shutdown()
|
|
{
|
|
UpdateFastmemViews(CPUFastmemMode::Disabled);
|
|
CPU::g_state.fastmem_base = nullptr;
|
|
|
|
g_ram_mask = 0;
|
|
g_ram_size = 0;
|
|
|
|
#ifndef __ANDROID__
|
|
Exports::RAM = 0;
|
|
Exports::RAM_SIZE = 0;
|
|
Exports::RAM_MASK = 0;
|
|
#endif
|
|
}
|
|
|
|
void Bus::Reset()
|
|
{
|
|
std::memset(g_ram, 0, g_ram_size);
|
|
s_MEMCTRL.exp1_base = 0x1F000000;
|
|
s_MEMCTRL.exp2_base = 0x1F802000;
|
|
s_MEMCTRL.exp1_delay_size.bits = 0x0013243F;
|
|
s_MEMCTRL.exp3_delay_size.bits = 0x00003022;
|
|
s_MEMCTRL.bios_delay_size.bits = 0x0013243F;
|
|
s_MEMCTRL.spu_delay_size.bits = 0x200931E1;
|
|
s_MEMCTRL.cdrom_delay_size.bits = 0x00020843;
|
|
s_MEMCTRL.exp2_delay_size.bits = 0x00070777;
|
|
s_MEMCTRL.common_delay.bits = 0x00031125;
|
|
g_ram_code_bits = {};
|
|
RecalculateMemoryTimings();
|
|
|
|
// Avoid remapping if unchanged.
|
|
if (s_RAM_SIZE.bits != 0x00000B88)
|
|
{
|
|
s_RAM_SIZE.bits = 0x00000B88;
|
|
UpdateMappedRAMSize();
|
|
}
|
|
}
|
|
|
|
void Bus::AddTTYCharacter(char ch)
|
|
{
|
|
if (ch == '\r')
|
|
{
|
|
}
|
|
else if (ch == '\n')
|
|
{
|
|
if (!s_tty_line_buffer.empty())
|
|
{
|
|
Log::FastWrite("TTY", "", LOGLEVEL_INFO, "\033[1;34m{}\033[0m", s_tty_line_buffer);
|
|
#ifdef _DEBUG
|
|
if (CPU::IsTraceEnabled())
|
|
CPU::WriteToExecutionLog("TTY: %s\n", s_tty_line_buffer.c_str());
|
|
#endif
|
|
}
|
|
s_tty_line_buffer.clear();
|
|
}
|
|
else
|
|
{
|
|
s_tty_line_buffer += ch;
|
|
}
|
|
}
|
|
|
|
void Bus::AddTTYString(std::string_view str)
|
|
{
|
|
for (char ch : str)
|
|
AddTTYCharacter(ch);
|
|
}
|
|
|
|
bool Bus::DoState(StateWrapper& sw)
|
|
{
|
|
u32 ram_size = g_ram_size;
|
|
sw.DoEx(&ram_size, 52, static_cast<u32>(RAM_2MB_SIZE));
|
|
if (ram_size != g_ram_size)
|
|
{
|
|
const bool using_8mb_ram = (ram_size == RAM_8MB_SIZE);
|
|
SetRAMSize(using_8mb_ram);
|
|
UpdateFastmemViews(s_fastmem_mode);
|
|
CPU::UpdateMemoryPointers();
|
|
}
|
|
|
|
sw.Do(&g_exp1_access_time);
|
|
sw.Do(&g_exp2_access_time);
|
|
sw.Do(&g_bios_access_time);
|
|
sw.Do(&g_cdrom_access_time);
|
|
sw.Do(&g_spu_access_time);
|
|
sw.DoBytes(g_ram, g_ram_size);
|
|
|
|
if (sw.GetVersion() < 58) [[unlikely]]
|
|
{
|
|
WARNING_LOG("Overwriting loaded BIOS with old save state.");
|
|
sw.DoBytes(g_bios, BIOS_SIZE);
|
|
}
|
|
|
|
sw.DoArray(s_MEMCTRL.regs, countof(s_MEMCTRL.regs));
|
|
|
|
const RAM_SIZE_REG old_ram_size_reg = s_RAM_SIZE;
|
|
sw.Do(&s_RAM_SIZE.bits);
|
|
if (s_RAM_SIZE.memory_window != old_ram_size_reg.memory_window)
|
|
UpdateMappedRAMSize();
|
|
|
|
sw.Do(&s_tty_line_buffer);
|
|
return !sw.HasError();
|
|
}
|
|
|
|
void Bus::SetExpansionROM(std::vector<u8> data)
|
|
{
|
|
s_exp1_rom = std::move(data);
|
|
}
|
|
|
|
std::tuple<TickCount, TickCount, TickCount> Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay)
|
|
{
|
|
// from nocash spec
|
|
s32 first = 0, seq = 0, min = 0;
|
|
if (mem_delay.use_com0_time)
|
|
{
|
|
first += s32(common_delay.com0) - 1;
|
|
seq += s32(common_delay.com0) - 1;
|
|
}
|
|
if (mem_delay.use_com2_time)
|
|
{
|
|
first += s32(common_delay.com2);
|
|
seq += s32(common_delay.com2);
|
|
}
|
|
if (mem_delay.use_com3_time)
|
|
{
|
|
min = s32(common_delay.com3);
|
|
}
|
|
if (first < 6)
|
|
first++;
|
|
|
|
first = first + s32(mem_delay.access_time) + 2;
|
|
seq = seq + s32(mem_delay.access_time) + 2;
|
|
|
|
if (first < (min + 6))
|
|
first = min + 6;
|
|
if (seq < (min + 2))
|
|
seq = min + 2;
|
|
|
|
const TickCount byte_access_time = first;
|
|
const TickCount halfword_access_time = mem_delay.data_bus_16bit ? first : (first + seq);
|
|
const TickCount word_access_time = mem_delay.data_bus_16bit ? (first + seq) : (first + seq + seq + seq);
|
|
return std::tie(std::max(byte_access_time - 1, 0), std::max(halfword_access_time - 1, 0),
|
|
std::max(word_access_time - 1, 0));
|
|
}
|
|
|
|
void Bus::RecalculateMemoryTimings()
|
|
{
|
|
std::tie(g_bios_access_time[0], g_bios_access_time[1], g_bios_access_time[2]) =
|
|
CalculateMemoryTiming(s_MEMCTRL.bios_delay_size, s_MEMCTRL.common_delay);
|
|
std::tie(g_cdrom_access_time[0], g_cdrom_access_time[1], g_cdrom_access_time[2]) =
|
|
CalculateMemoryTiming(s_MEMCTRL.cdrom_delay_size, s_MEMCTRL.common_delay);
|
|
std::tie(g_spu_access_time[0], g_spu_access_time[1], g_spu_access_time[2]) =
|
|
CalculateMemoryTiming(s_MEMCTRL.spu_delay_size, s_MEMCTRL.common_delay);
|
|
|
|
TRACE_LOG("BIOS Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
|
s_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, g_bios_access_time[0] + 1, g_bios_access_time[1] + 1,
|
|
g_bios_access_time[2] + 1);
|
|
TRACE_LOG("CDROM Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
|
s_MEMCTRL.cdrom_delay_size.data_bus_16bit ? 16 : 8, g_cdrom_access_time[0] + 1, g_cdrom_access_time[1] + 1,
|
|
g_cdrom_access_time[2] + 1);
|
|
TRACE_LOG("SPU Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
|
|
s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
|
|
g_spu_access_time[2] + 1);
|
|
}
|
|
|
|
CPUFastmemMode Bus::GetFastmemMode()
|
|
{
|
|
return s_fastmem_mode;
|
|
}
|
|
|
|
void* Bus::GetFastmemBase(bool isc)
|
|
{
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
if (s_fastmem_mode == CPUFastmemMode::MMap)
|
|
return isc ? nullptr : s_fastmem_arena.BasePointer();
|
|
#endif
|
|
if (s_fastmem_mode == CPUFastmemMode::LUT)
|
|
return reinterpret_cast<u8*>(s_fastmem_lut + (isc ? (FASTMEM_LUT_SIZE * sizeof(void*)) : 0));
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
u8* Bus::GetLUTFastmemPointer(u32 address, u8* ram_ptr)
|
|
{
|
|
return ram_ptr - address;
|
|
}
|
|
|
|
void Bus::UpdateFastmemViews(CPUFastmemMode mode)
|
|
{
|
|
#ifndef ENABLE_MMAP_FASTMEM
|
|
Assert(mode != CPUFastmemMode::MMap);
|
|
#else
|
|
for (const auto& it : s_fastmem_ram_views)
|
|
s_fastmem_arena.Unmap(it.first, it.second);
|
|
s_fastmem_ram_views.clear();
|
|
#endif
|
|
|
|
s_fastmem_mode = mode;
|
|
if (mode == CPUFastmemMode::Disabled)
|
|
return;
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
if (mode == CPUFastmemMode::MMap)
|
|
{
|
|
auto MapRAM = [](u32 base_address) {
|
|
u8* map_address = s_fastmem_arena.BasePointer() + base_address;
|
|
if (!s_fastmem_arena.Map(s_shmem_handle, 0, map_address, g_ram_size, PageProtect::ReadWrite)) [[unlikely]]
|
|
{
|
|
ERROR_LOG("Failed to map RAM at fastmem area {} (offset 0x{:08X})", static_cast<void*>(map_address),
|
|
g_ram_size);
|
|
return;
|
|
}
|
|
|
|
// mark all pages with code as non-writable
|
|
for (u32 i = 0; i < static_cast<u32>(g_ram_code_bits.size()); i++)
|
|
{
|
|
if (g_ram_code_bits[i])
|
|
{
|
|
u8* page_address = map_address + (i * HOST_PAGE_SIZE);
|
|
if (!MemMap::MemProtect(page_address, HOST_PAGE_SIZE, PageProtect::ReadOnly)) [[unlikely]]
|
|
{
|
|
ERROR_LOG("Failed to write-protect code page at {}", static_cast<void*>(page_address));
|
|
s_fastmem_arena.Unmap(map_address, g_ram_size);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
s_fastmem_ram_views.emplace_back(map_address, g_ram_size);
|
|
};
|
|
|
|
// KUSEG - cached
|
|
MapRAM(0x00000000);
|
|
|
|
// KSEG0 - cached
|
|
MapRAM(0x80000000);
|
|
|
|
// KSEG1 - uncached
|
|
MapRAM(0xA0000000);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!s_fastmem_lut)
|
|
{
|
|
s_fastmem_lut = static_cast<u8**>(std::malloc(sizeof(u8*) * FASTMEM_LUT_SLOTS));
|
|
Assert(s_fastmem_lut);
|
|
|
|
INFO_LOG("Fastmem base (software): {}", static_cast<void*>(s_fastmem_lut));
|
|
}
|
|
|
|
// This assumes the top 4KB of address space is not mapped. It shouldn't be on any sane OSes.
|
|
for (u32 i = 0; i < FASTMEM_LUT_SLOTS; i++)
|
|
s_fastmem_lut[i] = GetLUTFastmemPointer(i << FASTMEM_LUT_PAGE_SHIFT, nullptr);
|
|
|
|
auto MapRAM = [](u32 base_address) {
|
|
u8* ram_ptr = g_ram + (base_address & g_ram_mask);
|
|
for (u32 address = 0; address < g_ram_size; address += FASTMEM_LUT_PAGE_SIZE)
|
|
{
|
|
const u32 lut_index = (base_address + address) >> FASTMEM_LUT_PAGE_SHIFT;
|
|
s_fastmem_lut[lut_index] = GetLUTFastmemPointer(base_address + address, ram_ptr);
|
|
ram_ptr += FASTMEM_LUT_PAGE_SIZE;
|
|
}
|
|
};
|
|
|
|
// KUSEG - cached
|
|
MapRAM(0x00000000);
|
|
MapRAM(0x00200000);
|
|
MapRAM(0x00400000);
|
|
MapRAM(0x00600000);
|
|
|
|
// KSEG0 - cached
|
|
MapRAM(0x80000000);
|
|
MapRAM(0x80200000);
|
|
MapRAM(0x80400000);
|
|
MapRAM(0x80600000);
|
|
|
|
// KSEG1 - uncached
|
|
MapRAM(0xA0000000);
|
|
MapRAM(0xA0200000);
|
|
MapRAM(0xA0400000);
|
|
MapRAM(0xA0600000);
|
|
}
|
|
|
|
bool Bus::CanUseFastmemForAddress(VirtualMemoryAddress address)
|
|
{
|
|
const PhysicalMemoryAddress paddr = address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
|
|
|
switch (s_fastmem_mode)
|
|
{
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
case CPUFastmemMode::MMap:
|
|
{
|
|
// Currently since we don't map the mirrors, don't use fastmem for them.
|
|
// This is because the swapping of page code bits for SMC is too expensive.
|
|
return (paddr < g_ram_size);
|
|
}
|
|
#endif
|
|
|
|
case CPUFastmemMode::LUT:
|
|
return (paddr < RAM_MIRROR_END);
|
|
|
|
case CPUFastmemMode::Disabled:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Bus::IsRAMCodePage(u32 index)
|
|
{
|
|
return g_ram_code_bits[index];
|
|
}
|
|
|
|
void Bus::SetRAMCodePage(u32 index)
|
|
{
|
|
if (g_ram_code_bits[index])
|
|
return;
|
|
|
|
// protect fastmem pages
|
|
g_ram_code_bits[index] = true;
|
|
SetRAMPageWritable(index, false);
|
|
}
|
|
|
|
void Bus::ClearRAMCodePage(u32 index)
|
|
{
|
|
if (!g_ram_code_bits[index])
|
|
return;
|
|
|
|
// unprotect fastmem pages
|
|
g_ram_code_bits[index] = false;
|
|
SetRAMPageWritable(index, true);
|
|
}
|
|
|
|
void Bus::SetRAMPageWritable(u32 page_index, bool writable)
|
|
{
|
|
if (!MemMap::MemProtect(&g_ram[page_index * HOST_PAGE_SIZE], HOST_PAGE_SIZE,
|
|
writable ? PageProtect::ReadWrite : PageProtect::ReadOnly)) [[unlikely]]
|
|
{
|
|
ERROR_LOG("Failed to set RAM host page {} ({}) to {}", page_index,
|
|
reinterpret_cast<const void*>(&g_ram[page_index * HOST_PAGE_SIZE]),
|
|
writable ? "read-write" : "read-only");
|
|
}
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
if (s_fastmem_mode == CPUFastmemMode::MMap)
|
|
{
|
|
const PageProtect protect = writable ? PageProtect::ReadWrite : PageProtect::ReadOnly;
|
|
|
|
// unprotect fastmem pages
|
|
for (const auto& it : s_fastmem_ram_views)
|
|
{
|
|
u8* page_address = it.first + (page_index * HOST_PAGE_SIZE);
|
|
if (!MemMap::MemProtect(page_address, HOST_PAGE_SIZE, protect)) [[unlikely]]
|
|
{
|
|
ERROR_LOG("Failed to {} code page {} (0x{:08X}) @ {}", writable ? "unprotect" : "protect", page_index,
|
|
page_index * static_cast<u32>(HOST_PAGE_SIZE), static_cast<void*>(page_address));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Bus::ClearRAMCodePageFlags()
|
|
{
|
|
g_ram_code_bits.reset();
|
|
|
|
if (!MemMap::MemProtect(g_ram, RAM_8MB_SIZE, PageProtect::ReadWrite))
|
|
ERROR_LOG("Failed to restore RAM protection to read-write.");
|
|
|
|
#ifdef ENABLE_MMAP_FASTMEM
|
|
if (s_fastmem_mode == CPUFastmemMode::MMap)
|
|
{
|
|
// unprotect fastmem pages
|
|
for (const auto& it : s_fastmem_ram_views)
|
|
{
|
|
if (!MemMap::MemProtect(it.first, it.second, PageProtect::ReadWrite))
|
|
ERROR_LOG("Failed to unprotect code pages for fastmem view @ {}", static_cast<void*>(it.first));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool Bus::IsCodePageAddress(PhysicalMemoryAddress address)
|
|
{
|
|
return IsRAMAddress(address) ? g_ram_code_bits[(address & g_ram_mask) / HOST_PAGE_SIZE] : false;
|
|
}
|
|
|
|
bool Bus::HasCodePagesInRange(PhysicalMemoryAddress start_address, u32 size)
|
|
{
|
|
if (!IsRAMAddress(start_address))
|
|
return false;
|
|
|
|
start_address = (start_address & g_ram_mask);
|
|
|
|
const u32 end_address = start_address + size;
|
|
while (start_address < end_address)
|
|
{
|
|
const u32 code_page_index = start_address / HOST_PAGE_SIZE;
|
|
if (g_ram_code_bits[code_page_index])
|
|
return true;
|
|
|
|
start_address += HOST_PAGE_SIZE;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const TickCount* Bus::GetMemoryAccessTimePtr(PhysicalMemoryAddress address, MemoryAccessSize size)
|
|
{
|
|
// Currently only BIOS, but could be EXP1 as well.
|
|
if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_MIRROR_SIZE))
|
|
return &g_bios_access_time[static_cast<size_t>(size)];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<Bus::MemoryRegion> Bus::GetMemoryRegionForAddress(PhysicalMemoryAddress address)
|
|
{
|
|
if (address < RAM_2MB_SIZE)
|
|
return MemoryRegion::RAM;
|
|
else if (address < RAM_MIRROR_END)
|
|
return static_cast<MemoryRegion>(static_cast<u32>(MemoryRegion::RAM) + (address / RAM_2MB_SIZE));
|
|
else if (address >= EXP1_BASE && address < (EXP1_BASE + EXP1_SIZE))
|
|
return MemoryRegion::EXP1;
|
|
else if (address >= CPU::SCRATCHPAD_ADDR && address < (CPU::SCRATCHPAD_ADDR + CPU::SCRATCHPAD_SIZE))
|
|
return MemoryRegion::Scratchpad;
|
|
else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
|
|
return MemoryRegion::BIOS;
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static constexpr std::array<std::pair<PhysicalMemoryAddress, PhysicalMemoryAddress>,
|
|
static_cast<u32>(Bus::MemoryRegion::Count)>
|
|
s_code_region_ranges = {{
|
|
{0, Bus::RAM_2MB_SIZE},
|
|
{Bus::RAM_2MB_SIZE, Bus::RAM_2MB_SIZE * 2},
|
|
{Bus::RAM_2MB_SIZE * 2, Bus::RAM_2MB_SIZE * 3},
|
|
{Bus::RAM_2MB_SIZE * 3, Bus::RAM_MIRROR_END},
|
|
{Bus::EXP1_BASE, Bus::EXP1_BASE + Bus::EXP1_SIZE},
|
|
{CPU::SCRATCHPAD_ADDR, CPU::SCRATCHPAD_ADDR + CPU::SCRATCHPAD_SIZE},
|
|
{Bus::BIOS_BASE, Bus::BIOS_BASE + Bus::BIOS_SIZE},
|
|
}};
|
|
|
|
PhysicalMemoryAddress Bus::GetMemoryRegionStart(MemoryRegion region)
|
|
{
|
|
return s_code_region_ranges[static_cast<u32>(region)].first;
|
|
}
|
|
|
|
PhysicalMemoryAddress Bus::GetMemoryRegionEnd(MemoryRegion region)
|
|
{
|
|
return s_code_region_ranges[static_cast<u32>(region)].second;
|
|
}
|
|
|
|
u8* Bus::GetMemoryRegionPointer(MemoryRegion region)
|
|
{
|
|
switch (region)
|
|
{
|
|
case MemoryRegion::RAM:
|
|
return g_unprotected_ram;
|
|
|
|
case MemoryRegion::RAMMirror1:
|
|
return (g_unprotected_ram + (RAM_2MB_SIZE & g_ram_mask));
|
|
|
|
case MemoryRegion::RAMMirror2:
|
|
return (g_unprotected_ram + ((RAM_2MB_SIZE * 2) & g_ram_mask));
|
|
|
|
case MemoryRegion::RAMMirror3:
|
|
return (g_unprotected_ram + ((RAM_8MB_SIZE * 3) & g_ram_mask));
|
|
|
|
case MemoryRegion::EXP1:
|
|
return nullptr;
|
|
|
|
case MemoryRegion::Scratchpad:
|
|
return CPU::g_state.scratchpad.data();
|
|
|
|
case MemoryRegion::BIOS:
|
|
return g_bios;
|
|
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static ALWAYS_INLINE_RELEASE bool MaskedMemoryCompare(const u8* pattern, const u8* mask, u32 pattern_length,
|
|
const u8* mem)
|
|
{
|
|
if (!mask)
|
|
return std::memcmp(mem, pattern, pattern_length) == 0;
|
|
|
|
for (u32 i = 0; i < pattern_length; i++)
|
|
{
|
|
if ((mem[i] & mask[i]) != (pattern[i] & mask[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::optional<PhysicalMemoryAddress> Bus::SearchMemory(PhysicalMemoryAddress start_address, const u8* pattern,
|
|
const u8* mask, u32 pattern_length)
|
|
{
|
|
std::optional<MemoryRegion> region = GetMemoryRegionForAddress(start_address);
|
|
if (!region.has_value())
|
|
return std::nullopt;
|
|
|
|
PhysicalMemoryAddress current_address = start_address;
|
|
MemoryRegion current_region = region.value();
|
|
while (current_region != MemoryRegion::Count)
|
|
{
|
|
const u8* mem = GetMemoryRegionPointer(current_region);
|
|
const PhysicalMemoryAddress region_start = GetMemoryRegionStart(current_region);
|
|
const PhysicalMemoryAddress region_end = GetMemoryRegionEnd(current_region);
|
|
|
|
if (mem)
|
|
{
|
|
PhysicalMemoryAddress region_offset = current_address - region_start;
|
|
PhysicalMemoryAddress bytes_remaining = region_end - current_address;
|
|
while (bytes_remaining >= pattern_length)
|
|
{
|
|
if (MaskedMemoryCompare(pattern, mask, pattern_length, mem + region_offset))
|
|
return region_start + region_offset;
|
|
|
|
region_offset++;
|
|
bytes_remaining--;
|
|
}
|
|
}
|
|
|
|
// skip RAM mirrors
|
|
if (current_region == MemoryRegion::RAM)
|
|
current_region = MemoryRegion::EXP1;
|
|
else
|
|
current_region = static_cast<MemoryRegion>(static_cast<int>(current_region) + 1);
|
|
|
|
if (current_region != MemoryRegion::Count)
|
|
current_address = GetMemoryRegionStart(current_region);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
#define BUS_CYCLES(n) CPU::g_state.pending_ticks += n
|
|
|
|
// TODO: Move handlers to own files for better inlining.
|
|
namespace Bus {
|
|
static void ClearHandlers(void** handlers);
|
|
static void SetHandlerForRegion(void** handlers, VirtualMemoryAddress address, u32 size,
|
|
MemoryReadHandler read_byte_handler, MemoryReadHandler read_halfword_handler,
|
|
MemoryReadHandler read_word_handler, MemoryWriteHandler write_byte_handler,
|
|
MemoryWriteHandler write_halfword_handler, MemoryWriteHandler write_word_handler);
|
|
|
|
// clang-format off
|
|
template<MemoryAccessSize size> static u32 UnknownReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void UnknownWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static void IgnoreWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 UnmappedReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void UnmappedWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 RAMReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void RAMWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 BIOSReadHandler(VirtualMemoryAddress address);
|
|
|
|
template<MemoryAccessSize size> static u32 ScratchpadReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void ScratchpadWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 CacheControlReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void CacheControlWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 ICacheReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void ICacheWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 EXP1ReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void EXP1WriteHandler(VirtualMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 EXP2ReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void EXP2WriteHandler(VirtualMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 EXP3ReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void EXP3WriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
template<MemoryAccessSize size> static u32 HardwareReadHandler(VirtualMemoryAddress address);
|
|
template<MemoryAccessSize size> static void HardwareWriteHandler(VirtualMemoryAddress address, u32 value);
|
|
|
|
// clang-format on
|
|
} // namespace Bus
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::UnknownReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
static constexpr const char* sizes[3] = {"byte", "halfword", "word"};
|
|
ERROR_LOG("Invalid {} read at address 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)], address,
|
|
CPU::g_state.pc);
|
|
return 0xFFFFFFFFu;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::UnknownWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
static constexpr const char* sizes[3] = {"byte", "halfword", "word"};
|
|
ERROR_LOG("Invalid {} write at address 0x{:08X}, value 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)], address,
|
|
value, CPU::g_state.pc);
|
|
CPU::g_state.bus_error = true;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::IgnoreWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
// noop
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::UnmappedReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
CPU::g_state.bus_error = true;
|
|
return UnknownReadHandler<size>(address);
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::UnmappedWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
CPU::g_state.bus_error = true;
|
|
UnknownWriteHandler<size>(address, value);
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::RAMReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
BUS_CYCLES(RAM_READ_TICKS);
|
|
|
|
const u32 offset = address & g_ram_mask;
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
{
|
|
return ZeroExtend32(g_ram[offset]);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
u16 temp;
|
|
std::memcpy(&temp, &g_ram[offset], sizeof(u16));
|
|
return ZeroExtend32(temp);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::Word)
|
|
{
|
|
u32 value;
|
|
std::memcpy(&value, &g_ram[offset], sizeof(u32));
|
|
return value;
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::RAMWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & g_ram_mask;
|
|
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
{
|
|
g_ram[offset] = Truncate8(value);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
const u16 temp = Truncate16(value);
|
|
std::memcpy(&g_ram[offset], &temp, sizeof(u16));
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::Word)
|
|
{
|
|
std::memcpy(&g_ram[offset], &value, sizeof(u32));
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::BIOSReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
BUS_CYCLES(g_bios_access_time[static_cast<u32>(size)]);
|
|
|
|
// TODO: Configurable mirroring.
|
|
const u32 offset = address & UINT32_C(0x7FFFF);
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
{
|
|
return ZeroExtend32(g_bios[offset]);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
u16 temp;
|
|
std::memcpy(&temp, &g_bios[offset], sizeof(u16));
|
|
return ZeroExtend32(temp);
|
|
}
|
|
else
|
|
{
|
|
u32 value;
|
|
std::memcpy(&value, &g_bios[offset], sizeof(u32));
|
|
return value;
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::ScratchpadReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
const PhysicalMemoryAddress cache_offset = address & MEMORY_LUT_PAGE_MASK;
|
|
if (cache_offset >= CPU::SCRATCHPAD_SIZE) [[unlikely]]
|
|
return UnknownReadHandler<size>(address);
|
|
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
{
|
|
return ZeroExtend32(CPU::g_state.scratchpad[cache_offset]);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
u16 temp;
|
|
std::memcpy(&temp, &CPU::g_state.scratchpad[cache_offset], sizeof(temp));
|
|
return ZeroExtend32(temp);
|
|
}
|
|
else
|
|
{
|
|
u32 value;
|
|
std::memcpy(&value, &CPU::g_state.scratchpad[cache_offset], sizeof(value));
|
|
return value;
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::ScratchpadWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
const PhysicalMemoryAddress cache_offset = address & MEMORY_LUT_PAGE_MASK;
|
|
if (cache_offset >= CPU::SCRATCHPAD_SIZE) [[unlikely]]
|
|
{
|
|
UnknownWriteHandler<size>(address, value);
|
|
return;
|
|
}
|
|
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
CPU::g_state.scratchpad[cache_offset] = Truncate8(value);
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
std::memcpy(&CPU::g_state.scratchpad[cache_offset], &value, sizeof(u16));
|
|
else if constexpr (size == MemoryAccessSize::Word)
|
|
std::memcpy(&CPU::g_state.scratchpad[cache_offset], &value, sizeof(u32));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::CacheControlReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
if (address != 0xFFFE0130)
|
|
return UnknownReadHandler<size>(address);
|
|
|
|
return CPU::g_state.cache_control.bits;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::CacheControlWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
if (address != 0xFFFE0130)
|
|
return UnknownWriteHandler<size>(address, value);
|
|
|
|
DEV_LOG("Cache control <- 0x{:08X}", value);
|
|
CPU::g_state.cache_control.bits = value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::ICacheReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
const u32 line = CPU::GetICacheLine(address);
|
|
const u8* line_data = &CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE];
|
|
const u32 offset = CPU::GetICacheLineOffset(address);
|
|
u32 result;
|
|
std::memcpy(&result, &line_data[offset], sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::ICacheWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
const u32 line = CPU::GetICacheLine(address);
|
|
const u32 offset = CPU::GetICacheLineOffset(address);
|
|
CPU::g_state.icache_tags[line] = CPU::GetICacheTagForAddress(address) | CPU::ICACHE_INVALID_BITS;
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u8));
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u16));
|
|
else
|
|
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u32));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::EXP1ReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
BUS_CYCLES(g_exp1_access_time[static_cast<u32>(size)]);
|
|
|
|
const u32 offset = address & EXP1_MASK;
|
|
u32 value;
|
|
if (s_exp1_rom.empty())
|
|
{
|
|
// EXP1 not present.
|
|
value = UINT32_C(0xFFFFFFFF);
|
|
}
|
|
else if (offset == 0x20018)
|
|
{
|
|
// Bit 0 - Action Replay On/Off
|
|
value = UINT32_C(1);
|
|
}
|
|
else
|
|
{
|
|
const u32 transfer_size = u32(1) << static_cast<u32>(size);
|
|
if ((offset + transfer_size) > s_exp1_rom.size())
|
|
{
|
|
value = UINT32_C(0);
|
|
}
|
|
else
|
|
{
|
|
if constexpr (size == MemoryAccessSize::Byte)
|
|
{
|
|
value = ZeroExtend32(s_exp1_rom[offset]);
|
|
}
|
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
u16 halfword;
|
|
std::memcpy(&halfword, &s_exp1_rom[offset], sizeof(halfword));
|
|
value = ZeroExtend32(halfword);
|
|
}
|
|
else
|
|
{
|
|
std::memcpy(&value, &s_exp1_rom[offset], sizeof(value));
|
|
}
|
|
|
|
// Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", address, value);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::EXP1WriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
WARNING_LOG("EXP1 write: 0x{:08X} <- 0x{:08X}", address, value);
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::EXP2ReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
BUS_CYCLES(g_exp2_access_time[static_cast<u32>(size)]);
|
|
|
|
const u32 offset = address & EXP2_MASK;
|
|
u32 value;
|
|
|
|
// rx/tx buffer empty
|
|
if (offset == 0x21)
|
|
{
|
|
value = 0x04 | 0x08;
|
|
}
|
|
else if (offset >= 0x60 && offset <= 0x67)
|
|
{
|
|
// nocash expansion area
|
|
value = UINT32_C(0xFFFFFFFF);
|
|
}
|
|
else
|
|
{
|
|
WARNING_LOG("EXP2 read: 0x{:08X}", address);
|
|
value = UINT32_C(0xFFFFFFFF);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::EXP2WriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & EXP2_MASK;
|
|
if (offset == 0x23 || offset == 0x80)
|
|
{
|
|
AddTTYCharacter(static_cast<char>(value));
|
|
}
|
|
else if (offset == 0x41 || offset == 0x42)
|
|
{
|
|
DEV_LOG("BIOS POST status: {:02X}", value & UINT32_C(0x0F));
|
|
}
|
|
else if (offset == 0x70)
|
|
{
|
|
DEV_LOG("BIOS POST2 status: {:02X}", value & UINT32_C(0x0F));
|
|
}
|
|
#if 0
|
|
// TODO: Put behind configuration variable
|
|
else if (offset == 0x81)
|
|
{
|
|
Log_WarningPrint("pcsx_debugbreak()");
|
|
Host::ReportErrorAsync("Error", "pcsx_debugbreak()");
|
|
System::PauseSystem(true);
|
|
CPU::ExitExecution();
|
|
}
|
|
else if (offset == 0x82)
|
|
{
|
|
Log_WarningFmt("pcsx_exit() with status 0x{:02X}", value & UINT32_C(0xFF));
|
|
Host::ReportErrorAsync("Error", fmt::format("pcsx_exit() with status 0x{:02X}", value & UINT32_C(0xFF)));
|
|
System::ShutdownSystem(false);
|
|
CPU::ExitExecution();
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
WARNING_LOG("EXP2 write: 0x{:08X} <- 0x{:08X}", address, value);
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::EXP3ReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
WARNING_LOG("EXP3 read: 0x{:08X}", address);
|
|
return UINT32_C(0xFFFFFFFF);
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::EXP3WriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & EXP3_MASK;
|
|
if (offset == 0)
|
|
WARNING_LOG("BIOS POST3 status: {:02X}", value & UINT32_C(0x0F));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// HARDWARE HANDLERS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace Bus::HWHandlers {
|
|
// clang-format off
|
|
template<MemoryAccessSize size> static u32 MemCtrlRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void MemCtrlWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 PADRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void PADWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 SIORead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void SIOWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 MemCtrl2Read(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void MemCtrl2Write(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 INTCRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void INTCWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 DMARead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void DMAWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 TimersRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void TimersWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 CDROMRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void CDROMWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 GPURead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void GPUWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 MDECRead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void MDECWrite(PhysicalMemoryAddress address, u32 value);
|
|
template<MemoryAccessSize size> static u32 SPURead(PhysicalMemoryAddress address);
|
|
template<MemoryAccessSize size> static void SPUWrite(PhysicalMemoryAddress address, u32 value);
|
|
// clang-format on
|
|
} // namespace Bus::HWHandlers
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::MemCtrlRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & MEMCTRL_MASK;
|
|
|
|
u32 value = s_MEMCTRL.regs[FIXUP_WORD_OFFSET(size, offset) / 4];
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::MemCtrlWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & MEMCTRL_MASK;
|
|
const u32 index = FIXUP_WORD_OFFSET(size, offset) / 4;
|
|
value = FIXUP_WORD_WRITE_VALUE(size, offset, value);
|
|
|
|
const u32 write_mask = (index == 8) ? COMDELAY::WRITE_MASK : MEMDELAY::WRITE_MASK;
|
|
const u32 new_value = (s_MEMCTRL.regs[index] & ~write_mask) | (value & write_mask);
|
|
if (s_MEMCTRL.regs[index] != new_value)
|
|
{
|
|
s_MEMCTRL.regs[index] = new_value;
|
|
RecalculateMemoryTimings();
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::MemCtrl2Read(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & MEMCTRL2_MASK;
|
|
|
|
u32 value;
|
|
if (offset == 0x00)
|
|
{
|
|
value = s_RAM_SIZE.bits;
|
|
}
|
|
else
|
|
{
|
|
return UnknownReadHandler<size>(address);
|
|
}
|
|
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::MemCtrl2Write(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & MEMCTRL2_MASK;
|
|
|
|
if (offset == 0x00)
|
|
{
|
|
if (s_RAM_SIZE.bits != value)
|
|
{
|
|
DEV_LOG("RAM size register set to 0x{:08X}", value);
|
|
|
|
const RAM_SIZE_REG old_ram_size_reg = s_RAM_SIZE;
|
|
s_RAM_SIZE.bits = value;
|
|
|
|
if (s_RAM_SIZE.memory_window != old_ram_size_reg.memory_window)
|
|
UpdateMappedRAMSize();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return UnknownWriteHandler<size>(address, value);
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::PADRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & PAD_MASK;
|
|
|
|
u32 value = Pad::ReadRegister(FIXUP_HALFWORD_OFFSET(size, offset));
|
|
value = FIXUP_HALFWORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::PADWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & PAD_MASK;
|
|
Pad::WriteRegister(FIXUP_HALFWORD_OFFSET(size, offset), FIXUP_HALFWORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::SIORead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & SIO_MASK;
|
|
u32 value = SIO::ReadRegister(FIXUP_HALFWORD_OFFSET(size, offset));
|
|
value = FIXUP_HALFWORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::SIOWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & SIO_MASK;
|
|
SIO::WriteRegister(FIXUP_HALFWORD_OFFSET(size, offset), FIXUP_HALFWORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::CDROMRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & CDROM_MASK;
|
|
|
|
u32 value;
|
|
switch (size)
|
|
{
|
|
case MemoryAccessSize::Word:
|
|
{
|
|
const u32 b0 = ZeroExtend32(CDROM::ReadRegister(offset));
|
|
const u32 b1 = ZeroExtend32(CDROM::ReadRegister(offset + 1u));
|
|
const u32 b2 = ZeroExtend32(CDROM::ReadRegister(offset + 2u));
|
|
const u32 b3 = ZeroExtend32(CDROM::ReadRegister(offset + 3u));
|
|
value = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
|
}
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
{
|
|
const u32 lsb = ZeroExtend32(CDROM::ReadRegister(offset));
|
|
const u32 msb = ZeroExtend32(CDROM::ReadRegister(offset + 1u));
|
|
value = lsb | (msb << 8);
|
|
}
|
|
|
|
case MemoryAccessSize::Byte:
|
|
default:
|
|
value = ZeroExtend32(CDROM::ReadRegister(offset));
|
|
}
|
|
|
|
BUS_CYCLES(Bus::g_cdrom_access_time[static_cast<u32>(size)]);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::CDROMWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & CDROM_MASK;
|
|
switch (size)
|
|
{
|
|
case MemoryAccessSize::Word:
|
|
{
|
|
CDROM::WriteRegister(offset, Truncate8(value & 0xFFu));
|
|
CDROM::WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu));
|
|
CDROM::WriteRegister(offset + 2u, Truncate8((value >> 16) & 0xFFu));
|
|
CDROM::WriteRegister(offset + 3u, Truncate8((value >> 24) & 0xFFu));
|
|
}
|
|
break;
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
{
|
|
CDROM::WriteRegister(offset, Truncate8(value & 0xFFu));
|
|
CDROM::WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu));
|
|
}
|
|
break;
|
|
|
|
case MemoryAccessSize::Byte:
|
|
default:
|
|
CDROM::WriteRegister(offset, Truncate8(value));
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::GPURead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & GPU_MASK;
|
|
u32 value = g_gpu->ReadRegister(FIXUP_WORD_OFFSET(size, offset));
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::GPUWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & GPU_MASK;
|
|
g_gpu->WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::MDECRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & MDEC_MASK;
|
|
u32 value = MDEC::ReadRegister(FIXUP_WORD_OFFSET(size, offset));
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::MDECWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & MDEC_MASK;
|
|
MDEC::WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::INTCRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & INTERRUPT_CONTROLLER_MASK;
|
|
u32 value = InterruptController::ReadRegister(FIXUP_WORD_OFFSET(size, offset));
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::INTCWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & INTERRUPT_CONTROLLER_MASK;
|
|
InterruptController::WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::TimersRead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & TIMERS_MASK;
|
|
u32 value = Timers::ReadRegister(FIXUP_WORD_OFFSET(size, offset));
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::TimersWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & TIMERS_MASK;
|
|
Timers::WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::SPURead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & SPU_MASK;
|
|
u32 value;
|
|
|
|
switch (size)
|
|
{
|
|
case MemoryAccessSize::Word:
|
|
{
|
|
// 32-bit reads are read as two 16-bit accesses.
|
|
const u16 lsb = SPU::ReadRegister(offset);
|
|
const u16 msb = SPU::ReadRegister(offset + 2);
|
|
value = ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16);
|
|
}
|
|
break;
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
{
|
|
value = ZeroExtend32(SPU::ReadRegister(offset));
|
|
}
|
|
break;
|
|
|
|
case MemoryAccessSize::Byte:
|
|
default:
|
|
{
|
|
const u16 value16 = SPU::ReadRegister(FIXUP_HALFWORD_OFFSET(size, offset));
|
|
value = FIXUP_HALFWORD_READ_VALUE(size, offset, value16);
|
|
}
|
|
break;
|
|
}
|
|
|
|
BUS_CYCLES(Bus::g_spu_access_time[static_cast<u32>(size)]);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::SPUWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & SPU_MASK;
|
|
|
|
// 32-bit writes are written as two 16-bit writes.
|
|
switch (size)
|
|
{
|
|
case MemoryAccessSize::Word:
|
|
{
|
|
DebugAssert(Common::IsAlignedPow2(offset, 2));
|
|
SPU::WriteRegister(offset, Truncate16(value));
|
|
SPU::WriteRegister(offset + 2, Truncate16(value >> 16));
|
|
break;
|
|
}
|
|
|
|
case MemoryAccessSize::HalfWord:
|
|
{
|
|
DebugAssert(Common::IsAlignedPow2(offset, 2));
|
|
SPU::WriteRegister(offset, Truncate16(value));
|
|
break;
|
|
}
|
|
|
|
case MemoryAccessSize::Byte:
|
|
{
|
|
// Byte writes to unaligned addresses are apparently ignored.
|
|
if (address & 1)
|
|
return;
|
|
|
|
SPU::WriteRegister(offset, Truncate16(FIXUP_HALFWORD_READ_VALUE(size, offset, value)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HWHandlers::DMARead(PhysicalMemoryAddress address)
|
|
{
|
|
const u32 offset = address & DMA_MASK;
|
|
u32 value = DMA::ReadRegister(FIXUP_WORD_OFFSET(size, offset));
|
|
value = FIXUP_WORD_READ_VALUE(size, offset, value);
|
|
BUS_CYCLES(2);
|
|
return value;
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HWHandlers::DMAWrite(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
const u32 offset = address & DMA_MASK;
|
|
DMA::WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
|
|
}
|
|
|
|
#undef BUS_CYCLES
|
|
|
|
namespace Bus::HWHandlers {
|
|
// We index hardware registers by bits 15..8.
|
|
template<MemoryAccessType type, MemoryAccessSize size,
|
|
typename RT = std::conditional_t<type == MemoryAccessType::Read, MemoryReadHandler, MemoryWriteHandler>>
|
|
static constexpr std::array<RT, 256> GetHardwareRegisterHandlerTable()
|
|
{
|
|
std::array<RT, 256> ret = {};
|
|
for (size_t i = 0; i < ret.size(); i++)
|
|
{
|
|
if constexpr (type == MemoryAccessType::Read)
|
|
ret[i] = UnmappedReadHandler<size>;
|
|
else
|
|
ret[i] = UnmappedWriteHandler<size>;
|
|
}
|
|
|
|
#if 0
|
|
// Verifies no region has >1 handler, but doesn't compile on older GCC.
|
|
#define SET(raddr, rsize, read_handler, write_handler) \
|
|
static_assert(raddr >= 0x1F801000 && (raddr + rsize) <= 0x1F802000); \
|
|
for (u32 taddr = raddr; taddr < (raddr + rsize); taddr += 16) \
|
|
{ \
|
|
const u32 i = (taddr >> 4) & 0xFFu; \
|
|
if constexpr (type == MemoryAccessType::Read) \
|
|
ret[i] = (ret[i] == UnmappedReadHandler<size>) ? read_handler<size> : (abort(), read_handler<size>); \
|
|
else \
|
|
ret[i] = (ret[i] == UnmappedWriteHandler<size>) ? write_handler<size> : (abort(), write_handler<size>); \
|
|
}
|
|
#else
|
|
#define SET(raddr, rsize, read_handler, write_handler) \
|
|
static_assert(raddr >= 0x1F801000 && (raddr + rsize) <= 0x1F802000); \
|
|
for (u32 taddr = raddr; taddr < (raddr + rsize); taddr += 16) \
|
|
{ \
|
|
const u32 i = (taddr >> 4) & 0xFFu; \
|
|
if constexpr (type == MemoryAccessType::Read) \
|
|
ret[i] = read_handler<size>; \
|
|
else \
|
|
ret[i] = write_handler<size>; \
|
|
}
|
|
#endif
|
|
|
|
SET(MEMCTRL_BASE, MEMCTRL_SIZE, MemCtrlRead, MemCtrlWrite);
|
|
SET(PAD_BASE, PAD_SIZE, PADRead, PADWrite);
|
|
SET(SIO_BASE, SIO_SIZE, SIORead, SIOWrite);
|
|
SET(MEMCTRL2_BASE, MEMCTRL2_SIZE, MemCtrl2Read, MemCtrl2Write);
|
|
SET(INTC_BASE, INTC_SIZE, INTCRead, INTCWrite);
|
|
SET(DMA_BASE, DMA_SIZE, DMARead, DMAWrite);
|
|
SET(TIMERS_BASE, TIMERS_SIZE, TimersRead, TimersWrite);
|
|
SET(CDROM_BASE, CDROM_SIZE, CDROMRead, CDROMWrite);
|
|
SET(GPU_BASE, GPU_SIZE, GPURead, GPUWrite);
|
|
SET(MDEC_BASE, MDEC_SIZE, MDECRead, MDECWrite);
|
|
SET(SPU_BASE, SPU_SIZE, SPURead, SPUWrite);
|
|
|
|
#undef SET
|
|
|
|
return ret;
|
|
}
|
|
} // namespace Bus::HWHandlers
|
|
|
|
template<MemoryAccessSize size>
|
|
u32 Bus::HardwareReadHandler(VirtualMemoryAddress address)
|
|
{
|
|
static constexpr const auto table = HWHandlers::GetHardwareRegisterHandlerTable<MemoryAccessType::Read, size>();
|
|
const u32 table_index = (address >> 4) & 0xFFu;
|
|
return table[table_index](address);
|
|
}
|
|
|
|
template<MemoryAccessSize size>
|
|
void Bus::HardwareWriteHandler(VirtualMemoryAddress address, u32 value)
|
|
{
|
|
static constexpr const auto table = HWHandlers::GetHardwareRegisterHandlerTable<MemoryAccessType::Write, size>();
|
|
const u32 table_index = (address >> 4) & 0xFFu;
|
|
return table[table_index](address, value);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
static constexpr u32 KUSEG = 0;
|
|
static constexpr u32 KSEG0 = 0x80000000U;
|
|
static constexpr u32 KSEG1 = 0xA0000000U;
|
|
static constexpr u32 KSEG2 = 0xC0000000U;
|
|
|
|
void Bus::SetHandlers()
|
|
{
|
|
ClearHandlers(g_memory_handlers);
|
|
ClearHandlers(g_memory_handlers_isc);
|
|
|
|
#define SET(table, start, size, read_handler, write_handler) \
|
|
SetHandlerForRegion(table, start, size, read_handler<MemoryAccessSize::Byte>, \
|
|
read_handler<MemoryAccessSize::HalfWord>, read_handler<MemoryAccessSize::Word>, \
|
|
write_handler<MemoryAccessSize::Byte>, write_handler<MemoryAccessSize::HalfWord>, \
|
|
write_handler<MemoryAccessSize::Word>)
|
|
#define SETUC(start, size, read_handler, write_handler) \
|
|
SET(g_memory_handlers, start, size, read_handler, write_handler); \
|
|
SET(g_memory_handlers_isc, start, size, read_handler, write_handler)
|
|
|
|
// KUSEG - Cached
|
|
// Cache isolated appears to affect KUSEG+KSEG0.
|
|
SET(g_memory_handlers, KUSEG | RAM_BASE, RAM_MIRROR_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
SET(g_memory_handlers, KUSEG | CPU::SCRATCHPAD_ADDR, 0x1000, ScratchpadReadHandler, ScratchpadWriteHandler);
|
|
SET(g_memory_handlers, KUSEG | BIOS_BASE, BIOS_MIRROR_SIZE, BIOSReadHandler, IgnoreWriteHandler);
|
|
SET(g_memory_handlers, KUSEG | EXP1_BASE, EXP1_SIZE, EXP1ReadHandler, EXP1WriteHandler);
|
|
SET(g_memory_handlers, KUSEG | HW_BASE, HW_SIZE, HardwareReadHandler, HardwareWriteHandler);
|
|
SET(g_memory_handlers, KUSEG | EXP2_BASE, EXP2_SIZE, EXP2ReadHandler, EXP2WriteHandler);
|
|
SET(g_memory_handlers, KUSEG | EXP3_BASE, EXP3_SIZE, EXP3ReadHandler, EXP3WriteHandler);
|
|
SET(g_memory_handlers_isc, KUSEG, 0x80000000, ICacheReadHandler, ICacheWriteHandler);
|
|
|
|
// KSEG0 - Cached
|
|
SET(g_memory_handlers, KSEG0 | RAM_BASE, RAM_MIRROR_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | CPU::SCRATCHPAD_ADDR, 0x1000, ScratchpadReadHandler, ScratchpadWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | BIOS_BASE, BIOS_MIRROR_SIZE, BIOSReadHandler, IgnoreWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | EXP1_BASE, EXP1_SIZE, EXP1ReadHandler, EXP1WriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | HW_BASE, HW_SIZE, HardwareReadHandler, HardwareWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | EXP2_BASE, EXP2_SIZE, EXP2ReadHandler, EXP2WriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | EXP3_BASE, EXP3_SIZE, EXP3ReadHandler, EXP3WriteHandler);
|
|
SET(g_memory_handlers_isc, KSEG0, 0x20000000, ICacheReadHandler, ICacheWriteHandler);
|
|
|
|
// KSEG1 - Uncached
|
|
SETUC(KSEG1 | RAM_BASE, RAM_MIRROR_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
SETUC(KSEG1 | BIOS_BASE, BIOS_MIRROR_SIZE, BIOSReadHandler, IgnoreWriteHandler);
|
|
SETUC(KSEG1 | EXP1_BASE, EXP1_SIZE, EXP1ReadHandler, EXP1WriteHandler);
|
|
SETUC(KSEG1 | HW_BASE, HW_SIZE, HardwareReadHandler, HardwareWriteHandler);
|
|
SETUC(KSEG1 | EXP2_BASE, EXP2_SIZE, EXP2ReadHandler, EXP2WriteHandler);
|
|
SETUC(KSEG1 | EXP3_BASE, EXP3_SIZE, EXP3ReadHandler, EXP3WriteHandler);
|
|
|
|
// KSEG2 - Uncached - 0xFFFE0130
|
|
SETUC(KSEG2 | 0xFFFE0000, 0x1000, CacheControlReadHandler, CacheControlWriteHandler);
|
|
}
|
|
|
|
void Bus::UpdateMappedRAMSize()
|
|
{
|
|
switch (s_RAM_SIZE.memory_window)
|
|
{
|
|
case 4: // 2MB memory + 6MB unmapped
|
|
{
|
|
// Used by Rock-Climbing - Mitouhou e no Chousen - Alps Hen (Japan).
|
|
// By default, all 8MB is mapped, so we only need to remap the high 6MB.
|
|
constexpr u32 MAPPED_SIZE = RAM_2MB_SIZE;
|
|
constexpr u32 UNMAPPED_START = RAM_BASE + MAPPED_SIZE;
|
|
constexpr u32 UNMAPPED_SIZE = RAM_MIRROR_SIZE - MAPPED_SIZE;
|
|
SET(g_memory_handlers, KUSEG | UNMAPPED_START, UNMAPPED_SIZE, UnmappedReadHandler, UnmappedWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | UNMAPPED_START, UNMAPPED_SIZE, UnmappedReadHandler, UnmappedWriteHandler);
|
|
SET(g_memory_handlers, KSEG1 | UNMAPPED_START, UNMAPPED_SIZE, UnmappedReadHandler, UnmappedWriteHandler);
|
|
g_ram_mapped_size = MAPPED_SIZE;
|
|
}
|
|
break;
|
|
|
|
case 0: // 1MB memory + 7MB unmapped
|
|
case 1: // 4MB memory + 4MB unmapped
|
|
case 2: // 1MB memory + 1MB HighZ + 6MB unmapped
|
|
case 3: // 4MB memory + 4MB HighZ
|
|
case 6: // 2MB memory + 2MB HighZ + 4MB unmapped
|
|
case 7: // 8MB memory
|
|
{
|
|
// These aren't implemented because nothing is known to use them, so it can't be tested.
|
|
// If you find something that does, please let us know.
|
|
WARNING_LOG("Unhandled memory window 0x{} (register 0x{:08X}). Please report this game to developers.",
|
|
s_RAM_SIZE.memory_window.GetValue(), s_RAM_SIZE.bits);
|
|
}
|
|
[[fallthrough]];
|
|
|
|
case 5: // 8MB memory
|
|
{
|
|
// We only unmap the upper 6MB above, so we only need to remap this as well.
|
|
constexpr u32 REMAP_START = RAM_BASE + RAM_2MB_SIZE;
|
|
constexpr u32 REMAP_SIZE = RAM_MIRROR_SIZE - RAM_2MB_SIZE;
|
|
SET(g_memory_handlers, KUSEG | REMAP_START, REMAP_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
SET(g_memory_handlers, KSEG0 | REMAP_START, REMAP_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
SET(g_memory_handlers, KSEG1 | REMAP_START, REMAP_SIZE, RAMReadHandler, RAMWriteHandler);
|
|
g_ram_mapped_size = RAM_8MB_SIZE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#undef SET
|
|
#undef SETUC
|
|
|
|
void Bus::ClearHandlers(void** handlers)
|
|
{
|
|
for (u32 size = 0; size < 3; size++)
|
|
{
|
|
MemoryReadHandler* read_handlers =
|
|
OffsetHandlerArray<MemoryReadHandler>(handlers, static_cast<MemoryAccessSize>(size), MemoryAccessType::Read);
|
|
const MemoryReadHandler read_handler =
|
|
(size == 0) ?
|
|
UnmappedReadHandler<MemoryAccessSize::Byte> :
|
|
((size == 1) ? UnmappedReadHandler<MemoryAccessSize::HalfWord> : UnmappedReadHandler<MemoryAccessSize::Word>);
|
|
MemsetPtrs(read_handlers, read_handler, MEMORY_LUT_SIZE);
|
|
|
|
MemoryWriteHandler* write_handlers =
|
|
OffsetHandlerArray<MemoryWriteHandler>(handlers, static_cast<MemoryAccessSize>(size), MemoryAccessType::Write);
|
|
const MemoryWriteHandler write_handler =
|
|
(size == 0) ?
|
|
UnmappedWriteHandler<MemoryAccessSize::Byte> :
|
|
((size == 1) ? UnmappedWriteHandler<MemoryAccessSize::HalfWord> : UnmappedWriteHandler<MemoryAccessSize::Word>);
|
|
MemsetPtrs(write_handlers, write_handler, MEMORY_LUT_SIZE);
|
|
}
|
|
}
|
|
|
|
void Bus::SetHandlerForRegion(void** handlers, VirtualMemoryAddress address, u32 size,
|
|
MemoryReadHandler read_byte_handler, MemoryReadHandler read_halfword_handler,
|
|
MemoryReadHandler read_word_handler, MemoryWriteHandler write_byte_handler,
|
|
MemoryWriteHandler write_halfword_handler, MemoryWriteHandler write_word_handler)
|
|
{
|
|
// Should be 4K aligned.
|
|
DebugAssert(Common::IsAlignedPow2(size, MEMORY_LUT_PAGE_SIZE));
|
|
|
|
const u32 start_page = (address / MEMORY_LUT_PAGE_SIZE);
|
|
const u32 num_pages = ((size + (MEMORY_LUT_PAGE_SIZE - 1)) / MEMORY_LUT_PAGE_SIZE);
|
|
|
|
for (u32 acc_size = 0; acc_size < 3; acc_size++)
|
|
{
|
|
MemoryReadHandler* read_handlers =
|
|
OffsetHandlerArray<MemoryReadHandler>(handlers, static_cast<MemoryAccessSize>(acc_size), MemoryAccessType::Read) +
|
|
start_page;
|
|
const MemoryReadHandler read_handler =
|
|
(acc_size == 0) ? read_byte_handler : ((acc_size == 1) ? read_halfword_handler : read_word_handler);
|
|
#if 0
|
|
for (u32 i = 0; i < num_pages; i++)
|
|
{
|
|
DebugAssert((acc_size == 0 && read_handlers[i] == UnmappedReadHandler<MemoryAccessSize::Byte>) ||
|
|
(acc_size == 1 && read_handlers[i] == UnmappedReadHandler<MemoryAccessSize::HalfWord>) ||
|
|
(acc_size == 2 && read_handlers[i] == UnmappedReadHandler<MemoryAccessSize::Word>));
|
|
|
|
read_handlers[i] = read_handler;
|
|
}
|
|
#else
|
|
MemsetPtrs(read_handlers, read_handler, num_pages);
|
|
#endif
|
|
|
|
MemoryWriteHandler* write_handlers = OffsetHandlerArray<MemoryWriteHandler>(
|
|
handlers, static_cast<MemoryAccessSize>(acc_size), MemoryAccessType::Write) +
|
|
start_page;
|
|
const MemoryWriteHandler write_handler =
|
|
(acc_size == 0) ? write_byte_handler : ((acc_size == 1) ? write_halfword_handler : write_word_handler);
|
|
#if 0
|
|
for (u32 i = 0; i < num_pages; i++)
|
|
{
|
|
DebugAssert((acc_size == 0 && write_handlers[i] == UnmappedWriteHandler<MemoryAccessSize::Byte>) ||
|
|
(acc_size == 1 && write_handlers[i] == UnmappedWriteHandler<MemoryAccessSize::HalfWord>) ||
|
|
(acc_size == 2 && write_handlers[i] == UnmappedWriteHandler<MemoryAccessSize::Word>));
|
|
|
|
write_handlers[i] = write_handler;
|
|
}
|
|
#else
|
|
MemsetPtrs(write_handlers, write_handler, num_pages);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void** Bus::GetMemoryHandlers(bool isolate_cache, bool swap_caches)
|
|
{
|
|
if (!isolate_cache)
|
|
return g_memory_handlers;
|
|
|
|
#ifdef _DEBUG
|
|
if (swap_caches)
|
|
WARNING_LOG("Cache isolated and swapped, icache will be written instead of scratchpad?");
|
|
#endif
|
|
|
|
return g_memory_handlers_isc;
|
|
}
|