/* Copyright 2023 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ #pragma once #include "types.h" class HwRegister { public: HwRegister() { read8 = invalidRead; read16 = invalidRead; read32 = invalidRead; write8 = invalidWrite; write16 = invalidWrite; write32 = invalidWrite; } template T read(u32 addr) { static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "invalid type size"); switch (sizeof(T)) { case 1: return (T)read8(addr); case 2: return (T)read16(addr); case 4: return (T)read32(addr); } } template void write(u32 addr, T data) { static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "invalid type size"); switch (sizeof(T)) { case 1: write8(addr, data); break; case 2: write16(addr, data); break; case 4: write32(addr, data); break; } } template void setReadHandler(T (*readHandler)(u32)) { static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "invalid type size"); switch (sizeof(T)) { case 1: read8 = (u8 (*)(u32))readHandler; break; case 2: read16 = (u16 (*)(u32))readHandler; break; case 4: read32 = (u32 (*)(u32))readHandler; break; } } template void setWriteHandler(void (*writeHandler)(u32, T)) { static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "invalid type size"); switch (sizeof(T)) { case 1: write8 = (void (*)(u32, u8))writeHandler; break; case 2: write16 = (void (*)(u32, u16))writeHandler; break; case 4: write32 = (void (*)(u32, u32))writeHandler; break; } } template static T invalidRead(u32 addr) { INFO_LOG(MEMORY, "Invalid register read<%d> %x", (int)sizeof(T), addr); return 0; } template static void invalidWrite(u32 addr, T value) { INFO_LOG(MEMORY, "Invalid register write<%d> %x = %x", (int)sizeof(T), addr, (int)value); } private: u8 (*read8)(u32 addr); void (*write8)(u32 addr, u8 value); u16 (*read16)(u32 addr); void (*write16)(u32 addr, u16 value); u32 (*read32)(u32 addr); void (*write32)(u32 addr, u32 value); }; template class MMRegister : public HwRegister { public: template void setReadWrite() { setReadHandler(readModule); setWriteHandler(writeModule); } template void setReadOnly() { setReadHandler(readModule); } template void setWriteOnly() { setWriteHandler(writeModule); } private: template static T readModule(u32 addr) { return (T)Module[((Addr - BaseAddress) & AddressMask) / 4]; } template static void writeModule(u32 addr, T data) { Module[((Addr - BaseAddress) & AddressMask) / 4] = (u32)((data & Mask) | OrMask); } }; template class RegisterBank { using RegisterType = MMRegister; static_assert((((Size - 1) * sizeof(u32)) & AddressMask) == ((Size - 1) * sizeof(u32)), "Size too big for address mask"); RegisterType registers[Size]; public: void init() { for (RegisterType& reg : registers) reg = {}; } void reset() { memset(Module, 0, Size * sizeof(u32)); } void term() { } template RegisterType& getRegister() { constexpr size_t index = ((Addr - BaseAddress) & AddressMask) / sizeof(u32); static_assert(index < Size, "Out of bound register"); return registers[index]; } // Configure the register at the given address to be readable and writable, with optional write masks. // Only accesses of the specified type size (defaults to u32) are allowed. template void setRW() { getRegister().template setReadWrite<(Addr & AddressMask), T, Mask, OrMask>(); } // Configure the register at the given address to use the given read and write handlers // Only accesses of the handlers type size are allowed. template void setHandlers(T (*readHandler)(u32), void (*writeHandler)(u32, T)) { RegisterType& reg = getRegister(); reg.setReadHandler(readHandler); reg.setWriteHandler(writeHandler); } // Configure the register at the given address to use the given write handler and be readable // Only accesses of the specified or inferred type size (defaults to u32) are allowed. template void setWriteHandler(void (*writeHandler)(u32, T)) { RegisterType& reg = getRegister(); reg.template setReadOnly(); reg.template setWriteHandler(writeHandler); } // Configure the register at the given address to be write only, with optional write handler or write masks. // Write masks are ignored if a write handler is specified. template void setWriteOnly(void (*writeHandler)(u32, T) = nullptr) { RegisterType& reg = getRegister(); if (writeHandler != nullptr) reg.template setWriteHandler(writeHandler); else reg.template setWriteOnly(); } // Configure the register at the given address to be read only, with optional read handler template void setReadOnly(T (*readHandler)(u32) = nullptr) { RegisterType& reg = getRegister(); if (readHandler != nullptr) reg.template setReadHandler(readHandler); else reg.template setReadOnly(); } size_t getRegIndex(u32 addr) { return ((addr - BaseAddress) & AddressMask) / sizeof(u32); } // Read handler for the bank template T read(u32 addr) { size_t index = getRegIndex(addr); if (index >= Size) { INFO_LOG(MEMORY, "Out of bound read @ %x", addr); return 0; } if (addr & 3) { INFO_LOG(MEMORY, "Unaligned register read @ %x", addr); return 0; } return (T)registers[index].template read(addr); } // Write handler for the bank template void write(u32 addr, T data) { size_t index = getRegIndex(addr); if (index >= Size) INFO_LOG(MEMORY, "Out of bound write @ %x = %x", addr, (int)data); else if (addr & 3) INFO_LOG(MEMORY, "Unaligned register write @ %x = %x", addr, (int)data); else registers[index].template write(addr, data); } }; template T ReadMemArr(const u8 *array, u32 addr) { return *(const T *)&array[addr]; } template void WriteMemArr(u8 *array, u32 addr, T data) { *(T *)&array[addr] = data; }