Files
ppsspp/Core/HW/GpioMMIO.cpp
Henrik Rydgård 6d14a10d83 HW: implement real Syscon serial protocol emulation (SysconSerialMMIO)
Extends the SysconSerialMMIO stub added for VSH boot into a real command/
response protocol matching uofw's Syscon_cmd() reference exactly (packet
framing, checksum, GPIO4 "response ready" handshake via a new GpioMMIO
cross-module hook), with handling for NOP/read-write clock/read-write
alarm commands.

Still hitting a SIGSEGV though.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
(cherry picked from commit 11887bf9e1fecd1eac56ec705c01b6fcfac09b2e)
2026-08-24 10:58:35 +02:00

192 lines
5.0 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 "Core/HW/GpioMMIO.h"
namespace GpioMMIO {
namespace {
u32 g_regs[SIZE / 4];
constexpr u32 REG_INTR_ACK = 0x024;
} // namespace
u32 Read32(u32 address) {
u32 offset = address - BASE_ADDRESS;
return g_regs[offset / 4];
}
void Write32(u32 address, u32 value) {
u32 offset = address - BASE_ADDRESS;
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().
g_regs[REG_INTR_STATUS / 4] &= ~value;
return;
}
g_regs[offset / 4] = value;
}
void SetInterruptStatusBits(u32 bits) {
g_regs[REG_INTR_STATUS / 4] |= 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;
u8 g_data[DATA_SIZE];
int g_dataIndex = 0;
int g_responseLen = 0;
bool g_dataExhausted = true;
// 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;
// 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 g_alarm = 0;
void ProcessCommand() {
u8 cmd = g_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], &g_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(&g_alarm, &g_data[2], 4);
break;
case CMD_NOP:
default:
break;
}
g_data[0] = kBaryonStatus;
g_data[1] = (u8)(payloadLen + 2);
memcpy(&g_data[2], payload, payloadLen);
u8 sum = 0;
for (int i = 0; i < g_data[1]; i++)
sum += g_data[i];
g_data[g_data[1]] = (u8)~sum;
g_dataIndex = 0;
g_responseLen = g_data[1];
g_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);
}
} // namespace
u32 Read32(u32 address) {
u32 offset = address - BASE_ADDRESS;
switch (offset) {
case 0x004:
return 0;
case 0x008:
{
u32 value = (g_data[g_dataIndex] << 8) | g_data[g_dataIndex + 1];
g_dataIndex += 2;
if (g_dataIndex > g_responseLen) {
g_dataIndex = 0;
g_dataExhausted = true;
}
return value;
}
case 0x00C:
{
u32 flags = 1; // bit 0: no error.
if (!g_dataExhausted)
flags |= 4; // bit 2: more response data available.
return flags;
}
case 0x018:
return 0;
default:
return 0;
}
}
void Write32(u32 address, u32 value) {
u32 offset = address - BASE_ADDRESS;
switch (offset) {
case 0x004:
if (value & 4) {
g_dataIndex = 0;
}
if (value & 2) {
ProcessCommand();
}
break;
case 0x008:
if (g_dataIndex + 1 < DATA_SIZE) {
g_data[g_dataIndex] = (value >> 8) & 0xFF;
g_data[g_dataIndex + 1] = value & 0xFF;
}
g_dataIndex += 2;
break;
default:
break;
}
}
} // namespace SysconSerialMMIO