316 lines
7.6 KiB
C++
316 lines
7.6 KiB
C++
/*
|
|
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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#pragma once
|
|
#include "types.h"
|
|
#include <cstring>
|
|
|
|
class HwRegister
|
|
{
|
|
public:
|
|
HwRegister()
|
|
{
|
|
read8 = invalidRead<u8>;
|
|
read16 = invalidRead<u16>;
|
|
read32 = invalidRead<u32>;
|
|
write8 = invalidWrite<u8>;
|
|
write16 = invalidWrite<u16>;
|
|
write32 = invalidWrite<u32>;
|
|
}
|
|
|
|
template<typename T>
|
|
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<typename T>
|
|
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<typename T>
|
|
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<typename T>
|
|
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<typename T>
|
|
static T invalidRead(u32 addr) {
|
|
INFO_LOG(MEMORY, "Invalid register read<%d> %x", (int)sizeof(T), addr);
|
|
return 0;
|
|
}
|
|
template<typename T>
|
|
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<u32 *Module, u32 AddressMask = 0xff, u32 BaseAddress = 0>
|
|
class MMRegister : public HwRegister
|
|
{
|
|
public:
|
|
template<u32 Addr, typename T = u32, u32 Mask = 0xffffffff, u32 OrMask = 0>
|
|
void setReadWrite()
|
|
{
|
|
setReadHandler(readModule<Addr, T>);
|
|
setWriteHandler(writeModule<Addr, T, Mask, OrMask>);
|
|
}
|
|
|
|
template<u32 Addr, typename T = u32>
|
|
void setReadOnly()
|
|
{
|
|
setReadHandler(readModule<Addr, T>);
|
|
}
|
|
|
|
template<u32 Addr, typename T = u32, u32 Mask = 0xffffffff, u32 OrMask = 0>
|
|
void setWriteOnly()
|
|
{
|
|
setWriteHandler(writeModule<Addr, T, Mask, OrMask>);
|
|
}
|
|
|
|
private:
|
|
template<u32 Addr, typename T = u32>
|
|
static T readModule(u32 addr)
|
|
{
|
|
return (T)Module[((Addr - BaseAddress) & AddressMask) / 4];
|
|
}
|
|
|
|
template<u32 Addr, typename T = u32, u32 Mask = 0xffffffff, u32 OrMask = 0>
|
|
static void writeModule(u32 addr, T data)
|
|
{
|
|
Module[((Addr - BaseAddress) & AddressMask) / 4] = (u32)((data & Mask) | OrMask);
|
|
}
|
|
};
|
|
|
|
template<u32 *Module, size_t Size, u32 AddressMask = 0xff, u32 BaseAddress = 0>
|
|
class RegisterBank
|
|
{
|
|
using RegisterType = MMRegister<Module, AddressMask, BaseAddress>;
|
|
|
|
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<u32 Addr>
|
|
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<u32 Addr, typename T = u32, u32 Mask = 0xffffffff, u32 OrMask = 0>
|
|
void setRW()
|
|
{
|
|
getRegister<Addr>().template setReadWrite<Addr, 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<u32 Addr, typename T>
|
|
void setHandlers(T (*readHandler)(u32), void (*writeHandler)(u32, T))
|
|
{
|
|
RegisterType& reg = getRegister<Addr>();
|
|
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<u32 Addr, typename T>
|
|
void setWriteHandler(void (*writeHandler)(u32, T))
|
|
{
|
|
RegisterType& reg = getRegister<Addr>();
|
|
reg.template setReadOnly<Addr, T>();
|
|
reg.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<u32 Addr, typename T = u32, u32 Mask = 0xffffffff, u32 OrMask = 0>
|
|
void setWriteOnly(void (*writeHandler)(u32, T) = nullptr)
|
|
{
|
|
RegisterType& reg = getRegister<Addr>();
|
|
if (writeHandler != nullptr)
|
|
reg.setWriteHandler(writeHandler);
|
|
else
|
|
reg.template setWriteOnly<Addr, T, Mask, OrMask>();
|
|
}
|
|
|
|
// Configure the register at the given address to be read only, with optional read handler
|
|
template<u32 Addr, typename T = u32>
|
|
void setReadOnly(T (*readHandler)(u32) = nullptr)
|
|
{
|
|
RegisterType& reg = getRegister<Addr>();
|
|
if (readHandler != nullptr)
|
|
reg.setReadHandler(readHandler);
|
|
else
|
|
reg.template setReadOnly<Addr, T>();
|
|
}
|
|
|
|
size_t getRegIndex(u32 addr)
|
|
{
|
|
return ((addr - BaseAddress) & AddressMask) / sizeof(u32);
|
|
}
|
|
|
|
// Read handler for the bank
|
|
template<typename T>
|
|
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 registers[index].template read<T>(addr);
|
|
}
|
|
|
|
// Write handler for the bank
|
|
template<typename T>
|
|
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].write(addr, data);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
T ReadMemArr(const u8 *array, u32 addr)
|
|
{
|
|
return *(const T *)&array[addr];
|
|
}
|
|
|
|
template<typename T>
|
|
void WriteMemArr(u8 *array, u32 addr, T data)
|
|
{
|
|
*(T *)&array[addr] = data;
|
|
}
|
|
|
|
class SerialPort
|
|
{
|
|
public:
|
|
class Pipe
|
|
{
|
|
public:
|
|
// Serial TX
|
|
virtual void write(u8 data) { }
|
|
// RX buffer Size
|
|
virtual int available() { return 0; }
|
|
// Serial RX
|
|
virtual u8 read() { return 0; }
|
|
|
|
virtual ~Pipe() = default;
|
|
};
|
|
|
|
virtual void setPipe(Pipe *pipe) = 0;
|
|
virtual void updateStatus() = 0;
|
|
|
|
virtual ~SerialPort() = default;
|
|
};
|