Files
ppsspp/Core/HW/GpioMMIO.cpp
T
Henrik RydgårdandClaude Opus 5.5 0b30638e65 HLE: Fix SAS thread wakeup, SAS raw output size, ME and syscon bounds
- The SAS thread waited without checking the state first, so a mix
  queued before it got to the wait was missed, and the next drain hung.
  The state is now atomic too.
- SAS raw output mode writes four planes, twice the size it asked for.
- DMACplus colour conversion with a huge stride overflowed its size
  math. The syscon serial data index wasn't bounded on reads, writes or
  load.
- Replace_memcpy_swizzled copies whole 16x8 blocks, more than the
  pitch * h it asked for. The God Eater depth hook tested the color
  buffer twice instead of the depth buffer, and its size could wrap.
- GranularMixer used 1/0 before the first FPS sample, and read the
  granule being written next when the queue was empty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 10:38:32 +01:00

266 lines
6.7 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:
{
// Writes can move the index anywhere.
*value = (dataIndex_ >= 0 && dataIndex_ + 1 < DATA_SIZE) ? (data_[dataIndex_] << 8) | data_[dataIndex_ + 1] : 0;
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;
}
if (dataIndex_ < DATA_SIZE)
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_);
if (p.mode == PointerWrap::MODE_READ && (dataIndex_ < 0 || dataIndex_ > DATA_SIZE)) {
dataIndex_ = 0;
}
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