Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
pcsxcmd.h
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27/*
28
29Structured command port, on the single address 0x1f8020a4.
30
31WHY ONE ADDRESS. The EXP2 register space is not ours. Martin Korth's tooling
32and the PS2 backwards-compatibility mechanism have both claimed registers in
33there, and no registry says which. It is a minefield rather than a shortage, so
34the right number of steps to take for all future structured features is one.
35
36WHY THE ANSWER COMES BACK IN MEMORY, NOT IN A REGISTER. The msan helpers in
37pcsxhw.h pass arguments in registers, using a hand-written load/store in an asm
38block with the argument registers pinned. That is deliberate and it is the
39point: the asm block forces the registers rather than hoping the allocator put
40them there, so the emulator can read them out of the register file. It is a
41sound technique for a small fixed-arity call.
42
43It is the wrong shape to generalize, though. Every new operation with a new
44argument list needs another hand-pinned asm block, the arity is capped by the
45argument registers, an operation cannot grow a field later without a new entry
46point, and nothing in it can carry a result back except another pinned
47register. A structured, extensible, versioned protocol wants a struct. So here
48the only channel is memory: results live in a volatile zone inside the command
49struct, ordinary C ordering rules apply, and the port itself is a plain
50volatile store with no register participation at all.
51
52WHY IT IS READABLE AT ALL. A write to an unclaimed address is
53INDISTINGUISHABLE FROM SUCCESS on the guest side: PCSX::HW::write32's default
54case stores into the shadow register file and logs "*Unknown 32bit write". So a
55program built against a newer feature, run on an older emulator, writes into a
56hole and is told nothing. pcsx_present() answers "is this Redux", never "does
57this Redux know command N". The fix needs no return channel: the guest poisons
58results.status before issuing the command, and an emulator that does not know
59the port never overwrites it. Absence of a write is the answer.
60
61The older single-purpose registers (putc, the msan family, the PSYQo heap
62metadata) are NOT migrated and stay valid forever. Breaking them would break
63shipped code, and a command struct is the wrong shape for something like putc
64where building the struct would cost more than the operation.
65
66*/
67
68#pragma once
69
70#include <stdint.h>
71
72#define PCSX_CMD_MAGIC 0x444d4358 /* 'XCMD' */
73#define PCSX_CMD_PROTOCOL_VERSION 1
74#define PCSX_CMD_PORT 0x1f8020a4
75
77 /* Reports protocol version and which operations this build implements.
78 Payload is struct pcsx_QueryResult. Always implemented. */
80 /* Registers a generic allocator descriptor. Payload is
81 struct pcsx_AllocDescriptor (allocdesc.h). May be issued repeatedly to
82 track several arenas. */
84 /* Reads back what the tracker believes about one arena. Payload is
85 struct pcsx_AllocReport (allocdesc.h). Exists so a guest can check the
86 tracker against its own ground truth, which is the only way to find out
87 whether the tracker is right rather than merely plausible. */
89
91};
92
95 /* Poison value. The guest writes this before issuing a command; it
96 surviving means nothing answered, which is what running on an emulator
97 without this port looks like. Never written by the emulator. */
99 /* The header magic did not match: a stale or uninitialized struct. */
101 /* Understood the port, does not implement that operation. */
103 /* Understood the operation, the payload was not acceptable. */
105 /* The struct, or something it points at, is not in mapped guest memory. */
107 /* The header's declared size is too small for the operation's payload. */
109};
110
111/* Everything the emulator writes back lives here, and only here. Volatile
112 because it changes outside the C abstract machine's view, at the volatile
113 store to the port. Kept as its own zone rather than qualifying the whole
114 struct so that filling in the (plain) input fields still generates ordinary
115 stores. */
117 volatile uint32_t status;
118 /* Operation-specific outputs follow in each payload struct, and are
119 likewise volatile. */
120};
121
125 /* Total size of the whole command struct in bytes, header included. This is
126 how a payload grows later without burning a new op code: the emulator
127 reads and writes only the fields the declared size covers. */
130};
131
135 /* Operations 0 .. opCount-1 exist in this build's enum; the bitmap says
136 which are implemented. */
138 /* Bit N set means operation N is implemented. 128 operations. */
139 volatile uint32_t opBitmap[4];
140};
141
142/* Fill in a header and poison the result. Call before pcsx_command. */
143static __inline__ void pcsx_commandInit(struct pcsx_CommandHeader* header, uint32_t op, uint32_t size) {
144 header->magic = PCSX_CMD_MAGIC;
145 header->op = op;
146 header->size = size;
147 header->results.status = PCSX_CMDSTATUS_NO_ANSWER;
148}
149
150/* Issue a command. The answer is in cmd->results and in any operation-specific
151 volatile fields; nothing is returned here. The memory clobber keeps the
152 input-field stores from sinking past the port write, since the volatile
153 store alone only orders itself against other volatile accesses. */
154static __inline__ void pcsx_command(void* cmd) {
155 __asm__ volatile("" ::: "memory");
156 *((void* volatile* const)PCSX_CMD_PORT) = cmd;
157 __asm__ volatile("" ::: "memory");
158}
159
160/* Non-zero when this emulator implements the given operation. */
161static __inline__ int pcsx_commandSupported(uint32_t op) {
162 struct pcsx_QueryResult query;
163 if (op >= 128) return 0;
164 pcsx_commandInit(&query.header, PCSX_CMD_QUERY, sizeof(query));
165 query.protocolVersion = 0;
166 pcsx_command(&query);
167 if (query.header.results.status != PCSX_CMDSTATUS_OK) return 0;
168 if (query.protocolVersion == 0) return 0;
169 return (query.opBitmap[op >> 5] >> (op & 31)) & 1;
170}
static int size
Definition string.h:144
uint16_t cmd[MON_CMD_WORDS]
Definition monitor.c:75
__asm__(" .section .text.monitorSlotEntry, \"ax\", @progbits\n" " .align 2\n" " .global monitorSlotEntry\n" " .type monitorSlotEntry, @function\n" " .set push\n" " .set noreorder\n" " .set noat\n" "monitorSlotEntry:\n" " mfc0 $v0, $13\n" " nop\n" " andi $v0, $v0, 0x7c\n" " beqz $v0, 1f\n" " srl $v0, $v0, 2\n" " li $v1, " MON_STR(MON_SLOT_EXCMASK) "\n" " srlv $v1, $v1, $v0\n" " andi $v1, $v1, 1\n" " bnez $v1, 2f\n" " nop\n" " jr $ra\n" " nop\n" "1:\n" MON_SLOT_RX_TEST " jr $ra\n" " nop\n" "2:\n" " sw $a0, 0x10($k0)\n" " sw $a1, 0x14($k0)\n" " sw $a2, 0x18($k0)\n" " sw $a3, 0x1c($k0)\n" " mfc0 $a0, $12\n" " nop\n" " sw $a0, 0x8c($k0)\n" " mfc0 $a1, $13\n" " nop\n" " sw $a1, 0x90($k0)\n" " sw $k1, 0x6c($k0)\n" " sw $s0, 0x40($k0)\n" " sw $s1, 0x44($k0)\n" " sw $s2, 0x48($k0)\n" " sw $s3, 0x4c($k0)\n" " sw $s4, 0x50($k0)\n" " sw $s5, 0x54($k0)\n" " sw $s6, 0x58($k0)\n" " sw $s7, 0x5c($k0)\n" " sw $t0, 0x20($k0)\n" " sw $t1, 0x24($k0)\n" " sw $t2, 0x28($k0)\n" " sw $t3, 0x2c($k0)\n" " sw $t4, 0x30($k0)\n" " sw $t5, 0x34($k0)\n" " sw $t6, 0x38($k0)\n" " sw $t7, 0x3c($k0)\n" " sw $t8, 0x60($k0)\n" " sw $t9, 0x64($k0)\n" " sw $gp, 0x70($k0)\n" " sw $sp, 0x74($k0)\n" " sw $fp, 0x78($k0)\n" " mfhi $a0\n" " nop\n" " sw $a0, 0x84($k0)\n" " mflo $a0\n" " nop\n" " sw $a0, 0x88($k0)\n" " move $s0, $ra\n" " la $sp, monitorSlotStack + " MON_STR(MONITOR_SLOT_STACK_WORDS) " * 4 - 16\n" " jal monitorSlotDispatch\n" " nop\n" " lw $k0, 0x108($zero)\n" " move $ra, $s0\n" " lw $k0, 0($k0)\n" " nop\n" " addiu $k0, $k0, 8\n" " lw $a0, 0x84($k0)\n" " lw $a1, 0x88($k0)\n" " mthi $a0\n" " mtlo $a1\n" " lw $a0, 0x10($k0)\n" " lw $a1, 0x14($k0)\n" " lw $a2, 0x18($k0)\n" " lw $a3, 0x1c($k0)\n" " lw $t0, 0x20($k0)\n" " lw $t1, 0x24($k0)\n" " lw $t2, 0x28($k0)\n" " lw $t3, 0x2c($k0)\n" " lw $t4, 0x30($k0)\n" " lw $t5, 0x34($k0)\n" " lw $t6, 0x38($k0)\n" " lw $t7, 0x3c($k0)\n" " lw $t8, 0x60($k0)\n" " lw $t9, 0x64($k0)\n" " lw $s0, 0x40($k0)\n" " lw $sp, 0x74($k0)\n" " jr $ra\n" " nop\n" " .set pop\n" " .size monitorSlotEntry, . - monitorSlotEntry\n" " .previous\n")
pcsx_CommandStatus
Definition pcsxcmd.h:93
@ PCSX_CMDSTATUS_SHORT_STRUCT
Definition pcsxcmd.h:108
@ PCSX_CMDSTATUS_UNKNOWN_OP
Definition pcsxcmd.h:102
@ PCSX_CMDSTATUS_BAD_MAGIC
Definition pcsxcmd.h:100
@ PCSX_CMDSTATUS_UNMAPPED
Definition pcsxcmd.h:106
@ PCSX_CMDSTATUS_NO_ANSWER
Definition pcsxcmd.h:98
@ PCSX_CMDSTATUS_OK
Definition pcsxcmd.h:94
@ PCSX_CMDSTATUS_BAD_ARGUMENT
Definition pcsxcmd.h:104
pcsx_Command
Definition pcsxcmd.h:76
@ PCSX_CMD_REGISTER_ALLOCATOR
Definition pcsxcmd.h:83
@ PCSX_CMD_COUNT
Definition pcsxcmd.h:90
@ PCSX_CMD_ALLOC_REPORT
Definition pcsxcmd.h:88
@ PCSX_CMD_QUERY
Definition pcsxcmd.h:79
#define PCSX_CMD_PORT
Definition pcsxcmd.h:74
#define PCSX_CMD_MAGIC
Definition pcsxcmd.h:72
Definition pcsxcmd.h:122
uint32_t op
Definition pcsxcmd.h:124
uint32_t size
Definition pcsxcmd.h:128
struct pcsx_CommandResults results
Definition pcsxcmd.h:129
uint32_t magic
Definition pcsxcmd.h:123
Definition pcsxcmd.h:116
volatile uint32_t status
Definition pcsxcmd.h:117
Definition pcsxcmd.h:132
struct pcsx_CommandHeader header
Definition pcsxcmd.h:133
volatile uint32_t protocolVersion
Definition pcsxcmd.h:134
volatile uint32_t opCount
Definition pcsxcmd.h:137
volatile uint32_t opBitmap[4]
Definition pcsxcmd.h:139
void uint32_t(classId, spec)