Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
install.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/* The steps that hook the monitor into the kernel, shared between
28 monitorMain() and hosts that run them from a separate init stage. */
29#pragma once
30
31#include <stdint.h>
32
36
37/* The monitor's exception chain entry, defined in monitor.c. */
38extern struct HandlerInfo s_monitorHandler;
39
40/* The monitor's patch-slot entry and the slot it went into (0: none), both
41 in monitor.c. */
44
45/* Identifies the machine to the host in HELLO, defined in monitor.c. */
47
48/* Fletcher-32 over the 512 KiB at 0xBFC00000, the same sums the frames use
49 (16-bit words low half first, 32-bit accumulators that wrap, each reduced
50 mod 65535 at the end). One 32-bit load per word: the BIOS bus splits it
51 into byte cycles itself, which beats four byte loads. */
52static inline uint32_t monitorBiosChecksum(void) {
53 const volatile uint32_t *p = (const volatile uint32_t *)0xbfc00000;
54 uint32_t s1 = 0, s2 = 0;
55 for (unsigned i = 0; i < 0x80000 / 4; i++) {
56 uint32_t w = p[i];
57 s1 += w & 0xffff;
58 s2 += s1;
59 s1 += w >> 16;
60 s2 += s1;
61 }
62 return ((s2 % 65535u) << 16) | (s1 % 65535u);
63}
64
65/* Copy the installed 0x80 general-exception trampoline down to the 0x40 cop0
66 break vector so a hardware breakpoint routes through the same handler and
67 chain. Nothing is installed at 0x40 by OpenBIOS. */
68static inline void monitorInstallCop0BreakVector(void) {
69 uint32_t *v80 = (uint32_t *)0x80;
70 uint32_t *v40 = (uint32_t *)0x40;
71 for (int i = 0; i < 4; i++) v40[i] = v80[i];
72 syscall_flushCache();
73}
74
75#if defined(OPENBIOS_H2X00_MONITOR) || defined(OPENBIOS_MONITOR)
76/* Built into OpenBIOS: the slot is ours to name (kernel/vectors.s). */
78
79static inline uint32_t *monitorFindSlot(void) { return exceptionHandlerPatchSlot4; }
80#else
81/* B0 0x56, GetC0Table. */
82static inline uint32_t **monitorGetC0Table(void) {
83 register int n asm("t1") = 0x56;
84 __asm__ volatile("" : "=r"(n) : "r"(n));
85 return ((uint32_t * *(*)(void))0xb0)();
86}
87
88/* The kernel exception handler as the CPU reaches it: the 0x80 vector is
89 `lui k0, hi / addiu k0, k0, lo / jr k0` on the retail kernel and on
90 OpenBIOS alike. 0 if it is anything else. */
91static inline uint32_t *monitorHandlerFromVector(void) {
92 const uint32_t *v = (const uint32_t *)0x80;
93 if ((v[0] >> 16) != 0x3c1a || (v[1] >> 16) != 0x275a || v[2] != 0x03400008) return 0;
94 return (uint32_t *)((v[0] << 16) + (int16_t)v[1]);
95}
96
97/* The retail kernel's exception handler (C0 table entry 6) has four
98 4-instruction patch slots at +0x70..+0xAF, between `sw v1, 0x80(k0)` at
99 +0x6C and `sw a0, 0x10(k0)` at +0xB0; OpenBIOS's copy is laid out the
100 same. Slot 1 is the memory card driver's, slot 2 the lightgun's; the
101 monitor takes slot 4 (+0xA0), and only if the instructions around the
102 slots are those and slot 4 is still four nops. Anything else: 0, and the
103 monitor stays on the chain alone.
104
105 On OpenBIOS, GetC0Table runs its patch matcher on the caller, which does
106 not know the monitor's loader and halts the machine; there the handler
107 comes from the 0x80 vector instead (the same place on OpenBIOS). OpenBIOS
108 API 1 and up installs the slot itself, see monitorInstallSlot. */
109static inline uint32_t *monitorFindSlot(void) {
110 uint32_t *h = isOpenBiosPresent() ? monitorHandlerFromVector() : monitorGetC0Table()[6];
111 uintptr_t a = (uintptr_t)h;
112 if ((a & 3) || (a & 0x1fffffff) >= 0x00200000 - 0xb4) return 0; /* not in main RAM */
113 if (h[0x6c / 4] != 0xaf430080 || h[0xb0 / 4] != 0xaf440010) return 0;
114 uint32_t *slot = h + 0xa0 / 4;
115 if (slot[0] | slot[1] | slot[2] | slot[3]) return 0;
116 return slot;
117}
118#endif
119
120/* `lui at, hi / ori at, at, lo / jalr at / nop`: a call to monitorSlotEntry
121 from anywhere in the address space, clobbering at and ra only, both
122 already saved when the slot runs. */
123static inline void monitorPatchSlot(uint32_t *slot) {
125 slot[0] = 0x3c010000 | (e >> 16);
126 slot[1] = 0x34210000 | (e & 0xffff);
127 slot[2] = 0x0020f809;
128 slot[3] = 0;
129 syscall_flushCache();
130 s_monitorSlot = slot;
131}
132
133/* How monitorHook got into the exception handler's fourth patch slot. */
134enum {
135 MONITOR_SLOT_NONE = 0, /* not at all: the chain entry alone */
136 MONITOR_SLOT_FOUND, /* found and patched by the monitor */
137 MONITOR_SLOT_OPENBIOS, /* installed by OpenBIOS (API 1, installExceptionSlot) */
138};
139
140/* Take slot 4. OpenBIOS with API 1 and up does it for us: it knows its own
141 handler, and says no if the slot is taken. Otherwise find it and patch it
142 (the retail kernel, OpenBIOS before API 1, and a monitor built into
143 OpenBIOS, which names the slot directly). */
144static inline int monitorInstallSlot(void) {
145#if !defined(OPENBIOS_H2X00_MONITOR) && !defined(OPENBIOS_MONITOR)
146 if (getOpenBiosApiVersion() >= 1) {
147 if (installOpenBiosExceptionSlot(4, monitorSlotEntry) != 0) return MONITOR_SLOT_NONE;
148 /* Only for the caps and diagnostics: where it went. */
149 uint32_t *h = monitorHandlerFromVector();
150 s_monitorSlot = h ? h + 0xa0 / 4 : (uint32_t *)monitorSlotEntry;
152 }
153#endif
154 uint32_t *slot = monitorFindSlot();
155 if (!slot) return MONITOR_SLOT_NONE;
156 monitorPatchSlot(slot);
157 return MONITOR_SLOT_FOUND;
158}
159
160/* Own the break/fault path: at priority 0 on the kernel's chain, ahead of
161 the syscall verifier, and from the exception handler's fourth patch slot
162 ahead of every chain, so that a program resetting the chains keeps the
163 monitor. The chain entry stays when the slot is taken (and is all there is
164 when it is not); monitor.c keeps the two from handling one exception
165 twice. MONITOR_NO_SLOT builds leave the handler alone. Returns how the
166 slot was taken (MONITOR_SLOT_*). */
167static inline int monitorHook(void) {
168 syscall_sysEnqIntRP(0, &s_monitorHandler);
169 monitorInstallCop0BreakVector();
170#ifndef MONITOR_NO_SLOT
171 return monitorInstallSlot();
172#else
173 return MONITOR_SLOT_NONE;
174#endif
175}
176
177/* For a monitor whose resident core is copied to [start, end): zero if
178 OpenBIOS (API 1 and up) is present and its code cave does not hold that
179 range, as the RAM outside the cave is OpenBIOS's own. Nonzero otherwise,
180 including on the retail kernel and older OpenBIOS, which promise
181 nothing and where the core goes where it always has. */
182static inline int monitorCoreFitsCave(const void *start, const void *end) {
183 if (getOpenBiosApiVersion() < 1) return 1;
185 uintptr_t cave = (uintptr_t)getOpenBiosCodeCave(&size);
186 if (!cave) return 0;
187 return (uintptr_t)start >= cave && (uintptr_t)end <= cave + size && (uintptr_t)start <= (uintptr_t)end;
188}
189
190/* Enter the monitor through the exception handler, so its loop runs there. */
191static inline __attribute__((noreturn)) void monitorEnter(void) {
192 __asm__ volatile("break 4, 1\n" : : : "memory");
194}
uint32_t start
Definition cdlgetlocp.c:252
__attribute__((weak))
Definition clz.c:56
static int size
Definition string.h:142
int i
Definition gte-regio.c:297
uint32_t exceptionHandlerPatchSlot4[]
Definition handlers.c:258
uint32_t * s_monitorSlot
Definition monitor.c:141
void monitorSlotEntry(void)
uint32_t s_biosChecksum
Definition monitor.c:137
struct HandlerInfo s_monitorHandler
Definition monitor.c:535
@ MONITOR_SLOT_NONE
Definition install.h:135
@ MONITOR_SLOT_OPENBIOS
Definition install.h:137
@ MONITOR_SLOT_FOUND
Definition install.h:136
__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")
n
Definition gentable.py:95
cave
Definition mkimage.py:55
int a
Definition mktest.py:134
Definition handlers.h:33
__builtin_unreachable()
static const char * s2
Definition syscalls.h:92
void uint32_t(classId, spec)