Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
memcard-bios.c
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 BIOS memory card calls, against the card in MEMCARD_PORT (0 = slot 1,
28// 1 = slot 2): the low level sector calls _card_read / _card_write and the
29// "bu" device behind open / read / write / firstfile / erase / format. In the
30// emulator that is OpenBIOS; on a console it is the retail kernel, so the two
31// are held to the same expectations. THIS TEST ERASES THE CARD, after the same
32// check psyqo's memcard test makes that it holds nothing but this suite's own
33// files. Nothing here addresses the other port: _bu_init, which touches both,
34// is never called.
35//
36// Grading: every check prints PASS or FAIL, and the run ends on
37// "memcard-bios port N: all K checks passed" only when every check passed.
38
39#include <stdint.h>
40
44
45#ifndef MEMCARD_PORT
46#define MEMCARD_PORT 0
47#endif
48
49#define DEVICE (MEMCARD_PORT << 4)
50#define BU_PATH(name) (MEMCARD_PORT == 0 ? "bu00:" name : "bu10:" name)
51
52NUGGET_BIOS_THUNK(initCard, 0xb0, 0x4a)
54NUGGET_BIOS_THUNK(buFormat, 0xb0, 0x41)
56NUGGET_BIOS_THUNK(nextFile, 0xb0, 0x43)
57NUGGET_BIOS_THUNK(erase, 0xb0, 0x45)
58int __nugget_bios_initCard(int padEnable);
60int __nugget_bios_buFormat(const char *dev);
61struct DirEntry *__nugget_bios_firstFile(const char *pattern, struct DirEntry *entry);
63int __nugget_bios_erase(const char *name);
64#define initCard __nugget_bios_initCard
65#define startCard __nugget_bios_startCard
66#define buFormat __nugget_bios_buFormat
67#define firstFile __nugget_bios_firstFile
68#define nextFile __nugget_bios_nextFile
69#define erase __nugget_bios_erase
70
71// Low level completion is signalled on the hardware card event class.
72#define EVENT_HWCARD 0xf0000011
74static const uint32_t s_specs[EVENTS] = {0x0004, 0x8000, 0x0100, 0x2000};
75static const char *const s_names[EVENTS] = {"done", "error", "timeout", "new card"};
76static uint32_t s_events[EVENTS];
77
78static unsigned s_checks = 0;
79static unsigned s_failures = 0;
80
81static int check(int pass, const char *name) {
82 s_checks++;
83 if (!pass) s_failures++;
84 ramsyscall_printf("%s %s\n", pass ? "PASS" : "FAIL", name);
85 return pass;
86}
87
88static int same(const uint8_t *a, const uint8_t *b, uint32_t len) {
89 for (uint32_t i = 0; i < len; i++) {
90 if (a[i] != b[i]) return 0;
91 }
92 return 1;
93}
94
95static int streq(const char *a, const char *b) {
96 for (uint32_t i = 0;; i++) {
97 if (a[i] != b[i]) return 0;
98 if (a[i] == 0) return 1;
99 }
100}
101
102static void drainEvents() {
103 for (int i = 0; i < EVENTS; i++) syscall_testEvent(s_events[i]);
104}
105
106// Which event ended the last low level operation, or -1 if none came.
107static int waitCard() {
108 for (uint32_t spin = 0; spin < 0x200000; spin++) {
109 for (int i = 0; i < EVENTS; i++) {
110 if (syscall_testEvent(s_events[i])) return i;
111 }
112 }
113 return -1;
114}
115
116static const char *eventName(int e) { return e < 0 ? "no event" : s_names[e]; }
117
118// A card reports itself new on every access until something writes to it,
119// and the kernel turns that into a "new card" event. _new_card tells the
120// kernel to let the next access through; the access is then retried once.
121static int cardRead(int sector, uint8_t *buffer) {
122 for (int attempt = 0;; attempt++) {
123 drainEvents();
124 if (!syscall_mcReadSector(DEVICE, sector, buffer)) return -2;
125 int e = waitCard();
126 if (e != NEWCARD || attempt) return e;
127 syscall_mcAllowNewCard();
128 }
129}
130
131static int cardWrite(int sector, const uint8_t *buffer) {
132 for (int attempt = 0;; attempt++) {
133 drainEvents();
134 if (!syscall_mcWriteSector(DEVICE, sector, buffer)) return -2;
135 int e = waitCard();
136 if (e != NEWCARD || attempt) return e;
137 syscall_mcAllowNewCard();
138 }
139}
140
141static uint8_t s_frame[128];
142static uint8_t s_frame2[128];
143static uint8_t s_data[2048];
144static uint8_t s_readback[2048];
145
146static void fill(uint8_t *buf, uint32_t len, uint32_t seed) {
147 uint32_t x = seed * 2654435761u + 7;
148 for (uint32_t i = 0; i < len; i++) {
149 x = x * 1103515245u + 12345u;
150 buf[i] = (uint8_t)(x >> 16);
151 }
152}
153
154static int startsWith(const uint8_t *s, const char *prefix) {
155 for (uint32_t i = 0; prefix[i]; i++) {
156 if (s[i] != (uint8_t)prefix[i]) return 0;
157 }
158 return 1;
159}
160
161// The same rule as tests/memcard: blank, this suite's own title frame over
162// frame 0, or a Sony directory naming nothing but BIMEMTEST files.
163static int cardIsOursToErase() {
164 static const uint8_t titleSjis[] = {0x82, 0x6c, 0x82, 0x73};
165 int e = cardRead(0, s_frame);
166 if (e != DONE) {
167 ramsyscall_printf("REFUSE: frame 0 read ended on %s\n", e == -2 ? "a refused call" : eventName(e));
168 return 0;
169 }
170 int zeros = 1, ones = 1;
171 for (int i = 0; i < 128; i++) {
172 zeros = zeros && s_frame[i] == 0x00;
173 ones = ones && s_frame[i] == 0xff;
174 }
175 if (zeros || ones) {
176 ramsyscall_printf("card: blank (frame 0 all %s)\n", zeros ? "00" : "ff");
177 return 1;
178 }
179 if (s_frame[0] == 'S' && s_frame[1] == 'C' && same(s_frame + 4, titleSjis, sizeof(titleSjis))) {
180 ramsyscall_printf("card: frame 0 holds the memcard test's title frame, from an earlier run\n");
181 return 1;
182 }
183 if (s_frame[0] != 'M' || s_frame[1] != 'C') {
184 ramsyscall_printf("REFUSE: frame 0 is neither blank nor a Sony header\n");
185 return 0;
186 }
187 for (int s = 1; s <= 15; s++) {
188 e = cardRead(s, s_frame);
189 if (e != DONE) {
190 ramsyscall_printf("REFUSE: directory frame %d read ended on %s\n", s, eventName(e));
191 return 0;
192 }
193 if (s_frame[0] == 0x51 && !startsWith(s_frame + 0x0a, "BIMEMTEST")) {
194 s_frame[0x0a + 20] = 0;
195 ramsyscall_printf("REFUSE: block %d holds a save this test did not write: %s\n", s,
196 (const char *)s_frame + 0x0a);
197 return 0;
198 }
199 }
200 ramsyscall_printf("card: Sony filesystem, no save but this suite's\n");
201 return 1;
202}
203
204static void testSectors() {
205 int ok = 1;
206 for (int s = 64; s < 67; s++) {
207 fill(s_frame, 128, s);
208 int e = cardWrite(s, s_frame);
209 if (e != DONE) ramsyscall_printf("_card_write %d ended on %s\n", s, eventName(e)), ok = 0;
210 }
211 for (int s = 64; s < 67; s++) {
212 fill(s_frame, 128, s);
213 int e = cardRead(s, s_frame2);
214 if (e != DONE || !same(s_frame, s_frame2, 128)) {
215 ramsyscall_printf("_card_read %d ended on %s%s\n", s, eventName(e), e == DONE ? ", data differs" : "");
216 ok = 0;
217 }
218 }
219 check(ok, "_card_write then _card_read, 3 sectors");
220
221 // The last sector is 1023. The retail kernel's range check lets 1024
222 // through (measured on an SCPH-5501, and OpenBIOS matches it), and the
223 // card is what refuses it, with an error event.
224 drainEvents();
225 int accepted = syscall_mcReadSector(DEVICE, 1024, s_frame2);
226 int e = accepted ? waitCard() : -2;
227 ramsyscall_printf("_card_read 1024: %s, ended on %s\n", accepted ? "accepted" : "refused", eventName(e));
228 check(accepted, "_card_read passes sector 1024 to the card");
229#ifndef PCSX_TESTS
230 // PCSX-Redux's card answers sector 1024 with 128 FF bytes and a good end
231 // byte, so the kernel reports it done.
232 check(e == ERROR, "the card refuses sector 1024");
233#endif
234 drainEvents();
235 accepted = syscall_mcReadSector(DEVICE, 1023, s_frame2);
236 e = accepted ? waitCard() : -2;
237 check(accepted && e == DONE, "_card_read takes sector 1023");
238}
239
240static void testDevice() {
241 struct DirEntry entry;
242 check(buFormat(BU_PATH("")) == 1, "format");
243 check(firstFile(BU_PATH("*"), &entry) == NULL, "firstfile on a formatted card finds nothing");
244
245 int fd = syscall_open(BU_PATH("BIMEMTEST-BIOS"), PSXF_CREAT | (1 << 16));
246 check(fd >= 0, "open O_CREAT, 1 block");
247 if (fd >= 0) syscall_close(fd);
248
249 fill(s_data, 2048, 1);
250 fd = syscall_open(BU_PATH("BIMEMTEST-BIOS"), PSXF_WRITE);
251 int n = fd >= 0 ? (int)syscall_write(fd, s_data, 1024) : -1;
252 if (fd >= 0) syscall_close(fd);
253 check(n == 1024, "write 1024 bytes");
254
255 fd = syscall_open(BU_PATH("BIMEMTEST-BIOS2"), PSXF_CREAT | (2 << 16));
256 check(fd >= 0, "open O_CREAT, 2 blocks");
257 if (fd >= 0) syscall_close(fd);
258 fill(s_data, 2048, 2);
259 fd = syscall_open(BU_PATH("BIMEMTEST-BIOS2"), PSXF_WRITE);
260 n = fd >= 0 ? (int)syscall_write(fd, s_data, 2048) : -1;
261 if (fd >= 0) syscall_close(fd);
262 check(n == 2048, "write 2048 bytes");
263
264 int found = 0, sizes = 1;
265 for (struct DirEntry *p = firstFile(BU_PATH("*"), &entry); p; p = nextFile(&entry)) {
266 found++;
267 ramsyscall_printf("firstfile/nextfile: %s, %d bytes\n", entry.name, (int)entry.size);
268 if (streq(entry.name, "BIMEMTEST-BIOS"))
269 sizes = sizes && entry.size == 8192;
270 else if (streq(entry.name, "BIMEMTEST-BIOS2"))
271 sizes = sizes && entry.size == 16384;
272 else
273 sizes = 0;
274 if (found > 15) break;
275 }
276 check(found == 2 && sizes, "firstfile/nextfile list both files and their sizes");
277
278 fill(s_data, 2048, 1);
279 __builtin_memset(s_readback, 0, 1024);
280 fd = syscall_open(BU_PATH("BIMEMTEST-BIOS"), PSXF_READ);
281 n = fd >= 0 ? syscall_read(fd, s_readback, 1024) : -1;
282 if (fd >= 0) syscall_close(fd);
283 check(n == 1024 && same(s_data, s_readback, 1024), "read back the first file");
284 fill(s_data, 2048, 2);
285 __builtin_memset(s_readback, 0, 2048);
286 fd = syscall_open(BU_PATH("BIMEMTEST-BIOS2"), PSXF_READ);
287 n = fd >= 0 ? syscall_read(fd, s_readback, 2048) : -1;
288 if (fd >= 0) syscall_close(fd);
289 check(n == 2048 && same(s_data, s_readback, 2048), "read back the second file");
290
291 check(erase(BU_PATH("BIMEMTEST-BIOS")) == 1, "erase the first file");
292 struct DirEntry *p = firstFile(BU_PATH("*"), &entry);
293 check(p && streq(entry.name, "BIMEMTEST-BIOS2") && nextFile(&entry) == NULL, "only the second file is left");
294 check(syscall_open(BU_PATH("BIMEMTEST-BIOS"), PSXF_READ) < 0, "open an erased file fails");
295
296 // What the kernel left in the directory, read raw: block 1 deleted, blocks
297 // 2-3 the second file's chain, in the Sony layout psyqo's filesystem reads.
298 int e1 = cardRead(1, s_frame);
299 int dir1 = e1 == DONE ? s_frame[0] : -1;
300 int e2 = cardRead(2, s_frame);
301 int dir2 = e2 == DONE ? s_frame[0] : -1;
302 int named = e2 == DONE && startsWith(s_frame + 0x0a, "BIMEMTEST-BIOS2");
303 int e3 = cardRead(3, s_frame);
304 int dir3 = e3 == DONE ? s_frame[0] : -1;
305 ramsyscall_printf("directory states: block 1 %02x, block 2 %02x, block 3 %02x\n", dir1, dir2, dir3);
306 check(dir1 == 0xa1 && dir2 == 0x51 && named && dir3 == 0x53, "directory: a1 / 51 named / 53");
307
308 check(erase(BU_PATH("BIMEMTEST-BIOS2")) == 1, "erase the second file");
309 check(firstFile(BU_PATH("*"), &entry) == NULL, "card is empty");
310 check(buFormat(BU_PATH("")) == 1, "cleanup: format");
311}
312
313int main() {
314 ramsyscall_printf("BIOS memory card calls, port %d (slot %d)\n", MEMCARD_PORT, MEMCARD_PORT + 1);
315 initCard(0);
316 startCard();
317 for (int i = 0; i < EVENTS; i++) {
318 s_events[i] = syscall_openEvent(EVENT_HWCARD, s_specs[i], 0x2000, NULL);
319 syscall_enableEvent(s_events[i]);
320 }
321
322 if (cardIsOursToErase()) {
323 testSectors();
324 testDevice();
325 } else {
326 s_failures++;
327 }
328
329 if (s_failures == 0) {
330 ramsyscall_printf("memcard-bios port %d: all %d checks passed\n", MEMCARD_PORT, s_checks);
331 } else {
332 ramsyscall_printf("memcard-bios port %d: %d of %d checks FAILED\n", MEMCARD_PORT, s_failures, s_checks);
333 }
334 return s_failures == 0 ? 0 : 1;
335}
int ok
Definition cdlstop.c:75
ramsyscall_printf("T21 phase=%s t=%u v=%02x cause=%u size=%u stat=%02x\n", phase, t/1000, response[0], cause, size,(nopCause==3 &&nopSize >=1) ? stat[0] :0xff)
__builtin_memset(response, 0xff, 16)
@ PSXF_READ
Definition stdio.h:75
@ PSXF_CREAT
Definition stdio.h:82
@ PSXF_WRITE
Definition stdio.h:76
uint8_t b
Definition gte-depthcue.c:41
int i
Definition gte-regio.c:299
#define erase
Definition memcard-bios.c:69
int __nugget_bios_startCard(void)
#define buFormat
Definition memcard-bios.c:66
int __nugget_bios_erase(const char *name)
#define nextFile
Definition memcard-bios.c:68
#define BU_PATH(name)
Definition memcard-bios.c:50
#define initCard
Definition memcard-bios.c:64
#define DEVICE
Definition memcard-bios.c:49
#define firstFile
Definition memcard-bios.c:67
struct DirEntry * __nugget_bios_nextFile(struct DirEntry *entry)
struct DirEntry * __nugget_bios_firstFile(const char *pattern, struct DirEntry *entry)
@ ERROR
Definition memcard-bios.c:73
@ EVENTS
Definition memcard-bios.c:73
@ DONE
Definition memcard-bios.c:73
@ TIMEOUT
Definition memcard-bios.c:73
@ NEWCARD
Definition memcard-bios.c:73
#define EVENT_HWCARD
Definition memcard-bios.c:72
#define startCard
Definition memcard-bios.c:65
int __nugget_bios_initCard(int padEnable)
#define MEMCARD_PORT
Definition memcard-bios.c:46
int __nugget_bios_buFormat(const char *dev)
int main()
Definition memcard-bios.c:313
n
Definition gentable.py:95
check
Definition mkimage.py:54
entry
Definition mkimage.py:61
int fd
Definition pcdrv-net.c:67
char * s
Definition string.c:48
char e[1]
Definition string.c:119
Definition direntry.h:33
static void * buffer
Definition syscalls.h:274
#define NUGGET_BIOS_THUNK(name, table, number)
Definition syscalls.h:70
static int sector
Definition syscalls.h:490
static const void * buf
Definition syscalls.h:114