mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 10:19:04 +02:00
The interpreter's MMIO dispatch was a hardcoded chain for GPIO and the syscon serial port. Devices now register an address range in Core/HW/MMIO and get each access with its width and the MIPSState making it, for registers that answer each CPU differently. An access no device handles is logged and reads as poison, as before. GPIO and syscon serial become the first two devices. They're reset at boot, which they never were, and saved in savestates (SaveStart section version 4). ll/sc, lwl/lwr and lwc1/swc1 reach MMIO in the interpreter too, and kernel blocks leave lwc1/swc1 to it like the other loads and stores. swl/swr and lv/sv still treat it as a bad address. TestMMIO runs a kernel-mode access on each core and the registers through a savestate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
261 lines
6.5 KiB
C++
261 lines
6.5 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program 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, version 2.0 or later versions.
|
|
|
|
// This program 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <cstring>
|
|
|
|
#include "Common/Common.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Core/HW/GpioMMIO.h"
|
|
#include "Core/HW/MMIO.h"
|
|
|
|
namespace GpioMMIO {
|
|
|
|
namespace {
|
|
|
|
constexpr u32 REG_INTR_ACK = 0x024;
|
|
|
|
class GpioDevice : public MMIODevice {
|
|
public:
|
|
GpioDevice() : MMIODevice("GPIO", BASE_ADDRESS, SIZE) {}
|
|
|
|
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
|
|
if (size != 4)
|
|
return false;
|
|
*value = regs_[offset / 4];
|
|
return true;
|
|
}
|
|
|
|
bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
|
|
if (size != 4)
|
|
return false;
|
|
if (offset == REG_INTR_ACK) {
|
|
// Acknowledging an interrupt clears the corresponding bit(s) in the triggered-status
|
|
// register, rather than storing the acknowledge value itself - matches uofw's usage
|
|
// (GpioAcquireIntr writes the same bit it's acknowledging) and JPCSP's
|
|
// acknowledgeInterrupt().
|
|
regs_[REG_INTR_STATUS / 4] &= ~value;
|
|
return true;
|
|
}
|
|
regs_[offset / 4] = value;
|
|
return true;
|
|
}
|
|
|
|
void Reset() override {
|
|
memset(regs_, 0, sizeof(regs_));
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("GpioMMIO", 1, 1);
|
|
if (!s)
|
|
return;
|
|
DoArray(p, regs_, ARRAY_SIZE(regs_));
|
|
}
|
|
|
|
void SetInterruptStatusBits(u32 bits) {
|
|
regs_[REG_INTR_STATUS / 4] |= bits;
|
|
}
|
|
|
|
private:
|
|
u32 regs_[SIZE / 4]{};
|
|
};
|
|
|
|
GpioDevice g_device;
|
|
|
|
} // namespace
|
|
|
|
MMIODevice *Device() {
|
|
return &g_device;
|
|
}
|
|
|
|
void SetInterruptStatusBits(u32 bits) {
|
|
g_device.SetInterruptStatusBits(bits);
|
|
}
|
|
|
|
} // namespace GpioMMIO
|
|
|
|
namespace SysconSerialMMIO {
|
|
|
|
namespace {
|
|
|
|
// Real PSP Syscon command bytes (from JPCSP's sceSyscon.java, which lists the full real set -
|
|
// only the ones vsh_module's boot path is known to need are actually handled specially here).
|
|
enum SysconCmd : u8 {
|
|
CMD_NOP = 0x00,
|
|
CMD_READ_CLOCK = 0x09,
|
|
CMD_READ_ALARM = 0x0A,
|
|
CMD_WRITE_CLOCK = 0x20,
|
|
CMD_WRITE_ALARM = 0x22,
|
|
};
|
|
|
|
// Packet layout (shared between TX and RX, matching uofw's Syscon_cmd() and JPCSP's
|
|
// MMIOHandlerSyscon): byte 0 = command (TX) / status (RX), byte 1 = length in bytes
|
|
// (including these first two), bytes 2.. = payload, byte [length] = checksum
|
|
// (~sum(bytes[0..length-1]) & 0xFF).
|
|
constexpr int DATA_SIZE = 16;
|
|
|
|
// AC_SUPPLY | WLAN_POWER | POWER_SWITCH - matches JPCSP's MMIOHandlerSyscon.getBaryonStatus(),
|
|
// used as the RX status byte for every response (real hardware would report genuine baryon
|
|
// status bits here; this is just a plausible constant since PPSSPP doesn't model those).
|
|
constexpr u8 kBaryonStatus = 0x13;
|
|
|
|
class SysconSerialDevice : public MMIODevice {
|
|
public:
|
|
SysconSerialDevice() : MMIODevice("syscon serial", BASE_ADDRESS, SIZE) {}
|
|
|
|
bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
|
|
if (size != 4)
|
|
return false;
|
|
switch (offset) {
|
|
case 0x008:
|
|
{
|
|
*value = (data_[dataIndex_] << 8) | data_[dataIndex_ + 1];
|
|
dataIndex_ += 2;
|
|
if (dataIndex_ > responseLen_) {
|
|
dataIndex_ = 0;
|
|
dataExhausted_ = true;
|
|
}
|
|
return true;
|
|
}
|
|
case 0x00C:
|
|
{
|
|
u32 flags = 1; // bit 0: no error.
|
|
if (!dataExhausted_)
|
|
flags |= 4; // bit 2: more response data available.
|
|
*value = flags;
|
|
return true;
|
|
}
|
|
default:
|
|
*value = 0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
|
|
if (size != 4)
|
|
return false;
|
|
switch (offset) {
|
|
case 0x004:
|
|
if (value & 4) {
|
|
dataIndex_ = 0;
|
|
}
|
|
if (value & 2) {
|
|
ProcessCommand();
|
|
}
|
|
break;
|
|
case 0x008:
|
|
if (dataIndex_ + 1 < DATA_SIZE) {
|
|
data_[dataIndex_] = (value >> 8) & 0xFF;
|
|
data_[dataIndex_ + 1] = value & 0xFF;
|
|
}
|
|
dataIndex_ += 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Reset() override {
|
|
memset(data_, 0, sizeof(data_));
|
|
dataIndex_ = 0;
|
|
responseLen_ = 0;
|
|
dataExhausted_ = true;
|
|
alarm_ = 0;
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override {
|
|
auto s = p.Section("SysconSerialMMIO", 1, 1);
|
|
if (!s)
|
|
return;
|
|
DoArray(p, data_, DATA_SIZE);
|
|
Do(p, dataIndex_);
|
|
Do(p, responseLen_);
|
|
Do(p, dataExhausted_);
|
|
Do(p, alarm_);
|
|
}
|
|
|
|
private:
|
|
void ProcessCommand();
|
|
|
|
u8 data_[DATA_SIZE]{};
|
|
int dataIndex_ = 0;
|
|
int responseLen_ = 0;
|
|
bool dataExhausted_ = true;
|
|
// Persistent alarm value across sceSysconReadAlarm/WriteAlarm calls - mirrors JPCSP's
|
|
// sceSyscon.java readAlarm()/writeAlarm(), which are just as simple (a stored int, no real
|
|
// hardware backing).
|
|
u32 alarm_ = 0;
|
|
};
|
|
|
|
void SysconSerialDevice::ProcessCommand() {
|
|
u8 cmd = data_[0];
|
|
|
|
// Sub-status byte: must not be 0x80 or 0x81 (uofw's Syscon_cmd() treats those as
|
|
// SYSCON_RES_80/81 and retries the whole command) - 0x82 is what JPCSP's own fallback
|
|
// path uses for exactly this reason.
|
|
u8 payload[DATA_SIZE] = { 0x82 };
|
|
int payloadLen = 1;
|
|
|
|
switch (cmd) {
|
|
case CMD_READ_CLOCK:
|
|
// No real hardware clock modeled - always reports zero, matching JPCSP's fallback.
|
|
memset(&payload[payloadLen], 0, 4);
|
|
payloadLen += 4;
|
|
break;
|
|
case CMD_READ_ALARM:
|
|
memcpy(&payload[payloadLen], &alarm_, 4);
|
|
payloadLen += 4;
|
|
break;
|
|
case CMD_WRITE_CLOCK:
|
|
// Not modeled - accept and ignore, same as JPCSP's writeClock().
|
|
break;
|
|
case CMD_WRITE_ALARM:
|
|
memcpy(&alarm_, &data_[2], 4);
|
|
break;
|
|
case CMD_NOP:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
data_[0] = kBaryonStatus;
|
|
data_[1] = (u8)(payloadLen + 2);
|
|
memcpy(&data_[2], payload, payloadLen);
|
|
u8 sum = 0;
|
|
for (int i = 0; i < data_[1]; i++)
|
|
sum += data_[i];
|
|
data_[data_[1]] = (u8)~sum;
|
|
|
|
dataIndex_ = 0;
|
|
responseLen_ = data_[1];
|
|
dataExhausted_ = false;
|
|
|
|
// Signals "response ready" the way real hardware raising GPIO4 would - see
|
|
// uofw's Syscon_cmd(), which polls exactly this bit after triggering a command.
|
|
GpioMMIO::SetInterruptStatusBits(0x10);
|
|
}
|
|
|
|
SysconSerialDevice g_device;
|
|
|
|
} // namespace
|
|
|
|
MMIODevice *Device() {
|
|
return &g_device;
|
|
}
|
|
|
|
} // namespace SysconSerialMMIO
|