Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
xa.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// clang-format off
28
29// XA-ADPCM real-time playback, against the XA region of the test disc (see
30// create-test-iso.lua). Filler sectors carry their LBA in their first 3
31// bytes, Form 2 data sectors carry their LBA and 'D'. The tests read sector
32// heads at every data ready and check which slots of each 16-sector group
33// reached the host, and the ADPBUSY bit of the status register.
34//
35// The disc is read at double speed and the handler is slower than the drive,
36// so some data sectors are missed: the tests check which sectors arrive,
37// never how many.
38
40 struct XAReadStats {
41 unsigned events;
42 unsigned nonDataCauses;
43 unsigned unstamped;
44 uint32_t slotMask;
45 unsigned form2Seen;
46 unsigned audioRealTime;
47 unsigned audioNotRealTime;
48 unsigned busySet;
49 unsigned busyClear;
50 uint32_t lastLBABusy;
51 uint32_t firstLBAClear;
52 unsigned busyAfterClear;
53 uint32_t minLBA;
54 uint32_t maxLBA;
55 };
56
57 static int xaCommand(uint8_t command, const uint8_t* params, unsigned count, uint8_t* response,
58 uint8_t* responseSize) {
59 CDROM_REG0 = 0;
60 for (unsigned i = 0; i < count; i++) CDROM_REG2 = params[i];
62 uint32_t timeout = 2000000;
63 if (!waitCDRomIRQWithTimeout(&timeout)) return -1;
64 uint8_t cause = ackCDRomCause();
65 uint8_t buffer[16];
68 return cause;
69 }
70
71 static int xaWaitCause(uint8_t expected, uint32_t timeoutUs) {
72 uint8_t response[16];
73 uint32_t timeout = timeoutUs;
74 if (!waitCDRomIRQWithTimeout(&timeout)) return 0;
75 uint8_t cause = ackCDRomCause();
77 return cause == expected;
78 }
79
80 static int xaSeekL(uint32_t lba) {
81 uint32_t total = lba + 150;
82 uint8_t msf[3] = {itob(total / 4500), itob((total / 75) % 60), itob(total % 75)};
83 if (xaCommand(CDL_SETLOC, msf, 3, NULL, NULL) != 3) return 0;
84 if (xaCommand(CDL_SEEKL, NULL, 0, NULL, NULL) != 3) return 0;
85 return xaWaitCause(2, 4000000);
86 }
87
88 // Pause, then drain: data ready can still be queued ahead of the
89 // acknowledge, so anything but the complete is skipped, bounded.
90 static void xaStop() {
91 CDROM_REG0 = 0;
93 for (unsigned i = 0; i < 64; i++) {
94 uint8_t response[16];
95 uint32_t timeout = 1000000;
96 if (!waitCDRomIRQWithTimeout(&timeout)) return;
97 uint8_t cause = ackCDRomCause();
99 if (cause == 2) return;
100 }
101 }
102
103 static void xaReadHead(uint8_t* head, unsigned count) {
104 CDROM_REG0 = 0;
105 CDROM_REG3 = 0x80;
106 for (unsigned t = 0; t < 50000; t++) {
107 if (CDROM_REG0 & 0x40) break;
108 }
109 for (unsigned i = 0; i < count; i++) head[i] = CDROM_REG2;
110 CDROM_REG0 = 0;
111 CDROM_REG3 = 0;
112 }
113
114 // Sets the mode and the filter, ReadS from startLBA for up to durationUs,
115 // and records what reached the host. stopAfterClear ends the window once
116 // that many events after ADPBUSY cleared have been seen.
117 static int xaRead(uint8_t mode, int filter, uint8_t file, uint8_t channel, uint32_t startLBA,
118 uint32_t durationUs, unsigned stopAfterClear, struct XAReadStats* stats) {
119 __builtin_memset(stats, 0, sizeof(*stats));
120 stats->minLBA = 0xffffffff;
121 if (!resetCDRom()) return 0;
122 if (xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL) != 3) return 0;
123 if (filter) {
124 uint8_t params[2] = {file, channel};
125 if (xaCommand(CDL_SETFILTER, params, 2, NULL, NULL) != 3) return 0;
126 }
127 if (!xaSeekL(startLBA)) return 0;
128 if (xaCommand(CDL_READS, NULL, 0, NULL, NULL) != 3) return 0;
129
131 uint32_t elapsed = 0;
132 while ((elapsed < durationUs) && (stats->events < 2000)) {
133 uint32_t slice = 30000;
134 int got = waitCDRomIRQWithTimeout(&slice);
135 elapsed = slice;
136 if (!got) continue;
137 int busy = (CDROM_REG0 & 0x04) != 0;
138 uint8_t cause = ackCDRomCause();
139 uint8_t response[16];
141 stats->events++;
142 if (cause != 1) {
143 stats->nonDataCauses++;
144 continue;
145 }
146 // With mode bit 5 the whole sector from its header is read, so the
147 // subheader comes first and the payload starts at byte 12.
148 uint8_t head[16];
149 const uint8_t* payload = head;
150 if (mode & 0x20) {
151 xaReadHead(head, 16);
152 payload = head + 12;
153 uint8_t submode = head[6];
154 if (submode & 0x04) {
155 if (submode & 0x40) {
156 stats->audioRealTime++;
157 } else {
158 stats->audioNotRealTime++;
159 }
160 continue;
161 }
162 } else {
163 xaReadHead(head, 4);
164 }
165 uint32_t lba = payload[0] | (payload[1] << 8) | (payload[2] << 16);
166 // Anything without an LBA stamp is a sector that should not have
167 // reached the host, most likely an ADPCM one.
168 if (lba < startLBA || lba > startLBA + 20000) {
169 stats->unstamped++;
170 continue;
171 }
172 if (lba < stats->minLBA) stats->minLBA = lba;
173 if (lba > stats->maxLBA) stats->maxLBA = lba;
174 stats->slotMask |= 1 << ((lba - startLBA) % 16);
175 if (payload[3] == 'D') stats->form2Seen++;
176 if (busy) {
177 stats->busySet++;
178 stats->lastLBABusy = lba;
179 if (stats->busyClear) stats->busyAfterClear++;
180 } else {
181 if (!stats->busyClear) stats->firstLBAClear = lba;
182 stats->busyClear++;
183 if (stopAfterClear && (stats->busyClear >= stopAfterClear)) break;
184 }
185 }
186 xaStop();
188 "XA mode %02x filter %i %i/%i from %i: %i events, %i not data ready, %i unstamped, slots %04x, LBA %i-%i, "
189 "form 2 %i, audio RT %i not RT %i, ADPBUSY set %i clear %i (last set %i, first clear %i)\n",
190 mode, filter, file, channel, startLBA, stats->events, stats->nonDataCauses, stats->unstamped, stats->slotMask,
191 stats->minLBA, stats->maxLBA, stats->form2Seen, stats->audioRealTime, stats->audioNotRealTime, stats->busySet, stats->busyClear, stats->lastLBABusy,
192 stats->firstLBAClear);
193 return 1;
194 }
195)
196
197CESTER_TEST(xaGetParamAfterSetFilter, test_instance,
198 int resetDone = resetCDRom();
199 if (!resetDone) {
201 return;
202 }
203 uint8_t mode = 0xc8;
204 uint8_t filter[2] = {1, 3};
205 uint8_t response[16];
206 uint8_t size = 0;
207 cester_assert_int_eq(3, xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL));
208 cester_assert_int_eq(3, xaCommand(CDL_SETFILTER, filter, 2, NULL, NULL));
209 cester_assert_int_eq(3, xaCommand(CDL_GETMODE, NULL, 0, response, &size));
215 ramsyscall_printf("GetParam: %02x %02x %02x %02x %02x\n", response[0], response[1], response[2], response[3],
216 response[4]);
217)
218
219// LBA 135000: 8 channels of file 1 in slots 0-7 of each 16, Form 1 fillers
220// in slots 8-15. With RT, SF or both, audio sectors never reach the host,
221// whether or not they match the filter. ADPBUSY is set while RT is on.
222
223CESTER_TEST(xaRealTimeFilteredWithholdsAudio, test_instance,
224 struct XAReadStats stats;
225 int done = xaRead(0xc8, 1, 1, 0, 135000, 1500000, 0, &stats);
227 uint32_t fillers = stats.slotMask & 0xff00;
228 uint32_t audio = stats.slotMask & 0x00ff;
231 cester_assert_uint_eq(0, stats.nonDataCauses);
232 cester_assert_uint_eq(0, stats.unstamped);
233 cester_assert_uint_eq(0, stats.busyClear);
234)
235
236CESTER_TEST(xaRealTimeOtherChannelWithholdsAudio, test_instance,
237 struct XAReadStats stats;
238 int done = xaRead(0xc8, 1, 1, 3, 135000, 1500000, 0, &stats);
240 uint32_t fillers = stats.slotMask & 0xff00;
241 uint32_t audio = stats.slotMask & 0x00ff;
244 cester_assert_uint_eq(0, stats.nonDataCauses);
245 cester_assert_uint_eq(0, stats.unstamped);
246 cester_assert_uint_eq(0, stats.busyClear);
247)
248
249CESTER_TEST(xaFilterWithoutRealTimeWithholdsAudio, test_instance,
250 struct XAReadStats stats;
251 int done = xaRead(0x88, 1, 1, 0, 135000, 1500000, 0, &stats);
253 uint32_t fillers = stats.slotMask & 0xff00;
254 uint32_t audio = stats.slotMask & 0x00ff;
257 cester_assert_uint_eq(0, stats.nonDataCauses);
258 cester_assert_uint_eq(0, stats.unstamped);
259 cester_assert_uint_eq(0, stats.busySet);
260)
261
262CESTER_TEST(xaRealTimeUnfilteredWithholdsAudio, test_instance,
263 struct XAReadStats stats;
264 int done = xaRead(0xc0, 0, 0, 0, 135000, 1500000, 0, &stats);
266 uint32_t fillers = stats.slotMask & 0xff00;
267 uint32_t audio = stats.slotMask & 0x00ff;
270 cester_assert_uint_eq(0, stats.nonDataCauses);
271 cester_assert_uint_eq(0, stats.unstamped);
272 cester_assert_uint_eq(0, stats.busyClear);
273)
274
275// LBA 152744, file 1: slot 0 audio on channel 0, slot 4 Form 2 data,
276// slot 8 audio without the RT submode bit, slot 12 audio on channel 0xff.
277// Read whole sectors so the subheader of whatever arrives can be checked:
278// the Form 2 data and the audio without RT reach the host, the RT audio does
279// not, on either channel.
280
281CESTER_TEST(xaForm2AndNonRealTimeAudioReachHost, test_instance,
282 struct XAReadStats stats;
283 int done = xaRead(0xe8, 1, 1, 0, 152744, 1500000, 0, &stats);
285 cester_assert_uint_ne(0, stats.form2Seen);
286 cester_assert_uint_ne(0, stats.audioNotRealTime);
287 cester_assert_uint_eq(0, stats.audioRealTime);
288 cester_assert_uint_eq(0, stats.nonDataCauses);
289 cester_assert_uint_eq(0, stats.unstamped);
290)
291
292// LBA 155960, file 1 channel 3 in slot 3 of every 16: EOF alone on its
293// sector 24 (LBA 156344), EOR alone on 34 (156504), both on 49 (156744).
294// Only the sector with both stops ADPCM: ADPBUSY drops after it and stays
295// down, no interrupt other than data ready, and reading carries on.
296
297CESTER_TEST(xaEndOfFileAndRecordStopsADPCM, test_instance,
298 struct XAReadStats stats;
299 int done = xaRead(0xc8, 1, 1, 3, 155960, 7000000, 20, &stats);
301 cester_assert_uint_eq(0, stats.nonDataCauses);
302 cester_assert_uint_eq(0, stats.unstamped);
304 cester_assert_uint_ne(0, stats.busyClear);
305 cester_assert_uint_eq(0, stats.busyAfterClear);
306 cester_assert_uint_ge(stats.lastLBABusy, 156504);
307 cester_assert_uint_gt(stats.firstLBAClear, 156744);
308)
309
310// XA level through the ATV matrix, read back from the SPU CD capture buffer
311// (helpers in cdda.c). Channel 0 of file 1 at LBA 135000 is a mono sine.
313 static int xaPeak(const uint8_t atv[4], int demute, unsigned* signChanges) {
314 if (!cddaAudioSetup(atv, demute)) return -1;
315 uint8_t mode = 0xc8;
316 if (xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL) != 3) return -1;
317 uint8_t filter[2] = {1, 0};
318 if (xaCommand(CDL_SETFILTER, filter, 2, NULL, NULL) != 3) return -1;
319 if (!xaSeekL(135016)) return -1;
320 if (xaCommand(CDL_READS, NULL, 0, NULL, NULL) != 3) return -1;
321
323 uint32_t elapsed = 0;
324 while (elapsed < 600000u) {
325 uint32_t budget = 10000u;
326 int ok = waitCDRomIRQWithTimeout(&budget);
327 elapsed = budget;
328 if (!ok) continue;
329 uint8_t response[16];
332 }
333
334 int peak = cddaCapturePeak(signChanges);
335 xaStop();
336 return peak;
337 }
338)
339
340CESTER_TEST(xaLevelUnity, test_instance,
341 static const uint8_t atv[4] = {0x80, 0x00, 0x80, 0x00};
342 unsigned changes = 0;
343 int peak = xaPeak(atv, 1, &changes);
344 ramsyscall_printf("XA level at unity ATV: peak %i, %u sign changes\n", peak, changes);
348)
349
351 static const uint8_t atv[4] = {0x80, 0x80, 0x80, 0x80};
352 unsigned changes = 0;
353 int peak = xaPeak(atv, 1, &changes);
354 ramsyscall_printf("XA level at double ATV: peak %i, %u sign changes\n", peak, changes);
358)
359
360CESTER_TEST(xaLevelMuted, test_instance,
361 static const uint8_t atv[4] = {0x80, 0x00, 0x80, 0x00};
362 unsigned changes = 0;
363 int peak = xaPeak(atv, 0, &changes);
364 ramsyscall_printf("XA level when muted: peak %i\n", peak);
366)
int resetDone
Definition cdlforward.c:60
initializeTime()
uint8_t responseSize
Definition cdlgetlocl.c:85
readResponse(response)
ackCDRomCause()
int ok
Definition cdlstop.c:75
uint8_t cause
Definition cdltest.c:207
@ CDL_READS
Definition cdrom.h:69
@ CDL_SETMODE
Definition cdrom.h:56
@ CDL_SEEKL
Definition cdrom.h:63
@ CDL_PAUSE
Definition cdrom.h:51
@ CDL_SETFILTER
Definition cdrom.h:55
@ CDL_GETMODE
Definition cdrom.h:57
@ CDL_SETLOC
Definition cdrom.h:44
#define CDROM_REG0
Definition cdrom.h:31
#define CDROM_REG3
Definition cdrom.h:34
#define CDROM_REG1
Definition cdrom.h:32
#define CDROM_REG2
Definition cdrom.h:33
#define head
Definition alloc.c:111
uint32_t t
Definition cop0.c:79
ramsyscall_printf("=== e01_kseg1_reads_no_fill ===\n")
struct EventInfo * events
Definition globals.h:49
int i
Definition gte-regio.c:297
file
Definition gen-sjis-encode-table.py:51
payload
Definition genjob.py:327
int total
Definition gentable.py:106
constexpr uint8_t itob(uint8_t i)
Definition msf.hh:36
uint32_t timeout
Definition race.c:103
__builtin_memset(sectorData, 0, sizeof(sectorData))
Definition gte-timing.c:164
static void * buffer
Definition syscalls.h:231
static const void size_t count
Definition syscalls.h:146
void uint32_t(classId, spec)
cester_assert_true(done)
cester_assert_uint_ne(0, fillers)
cester_assert_uint_gt(stats.firstLBAClear, 156744)
int peak
Definition xa.c:343
uint8_t mode
Definition xa.c:203
uint8_t response[16]
Definition xa.c:205
CESTER_TEST(xaRealTimeFilteredWithholdsAudio, test_instance, struct XAReadStats stats;int done=xaRead(0xc8, 1, 1, 0, 135000, 1500000, 0, &stats);cester_assert_true(done);uint32_t fillers=stats.slotMask &0xff00;uint32_t audio=stats.slotMask &0x00ff;cester_assert_uint_ne(0, fillers);cester_assert_uint_eq(0, audio);cester_assert_uint_eq(0, stats.nonDataCauses);cester_assert_uint_eq(0, stats.unstamped);cester_assert_uint_eq(0, stats.busyClear);) CESTER_TEST(xaRealTimeOtherChannelWithholdsAudio
int done
Definition xa.c:238
uint8_t filter[2]
Definition xa.c:204
cester_assert_int_ge(peak, 22400)
test_instance
Definition xa.c:236
struct XAReadStats stats
Definition xa.c:237
cester_assert_int_le(peak, 22900)
uint32_t audio
Definition xa.c:241
cester_assert_int_eq(3, xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL))
cester_assert_uint_ge(stats.lastLBABusy, 156504)
uint32_t fillers
Definition xa.c:240
unsigned changes
Definition xa.c:342
cester_assert_uint_eq(5, size)
uint8_t size
Definition xa.c:206
CESTER_BODY(struct XAReadStats { unsigned events;unsigned nonDataCauses;unsigned unstamped;uint32_t slotMask;unsigned form2Seen;unsigned audioRealTime;unsigned audioNotRealTime;unsigned busySet;unsigned busyClear;uint32_t lastLBABusy;uint32_t firstLBAClear;unsigned busyAfterClear;uint32_t minLBA;uint32_t maxLBA;};static int xaCommand(uint8_t command, const uint8_t *params, unsigned count, uint8_t *response, uint8_t *responseSize) { CDROM_REG0=0;for(unsigned i=0;i< count;i++) CDROM_REG2=params[i];CDROM_REG1=command;uint32_t timeout=2000000;if(!waitCDRomIRQWithTimeout(&timeout)) return -1;uint8_t cause=ackCDRomCause();uint8_t buffer[16];uint8_t size=readResponse(response ? response :buffer);if(responseSize) *responseSize=size;return cause;} static int xaWaitCause(uint8_t expected, uint32_t timeoutUs) { uint8_t response[16];uint32_t timeout=timeoutUs;if(!waitCDRomIRQWithTimeout(&timeout)) return 0;uint8_t cause=ackCDRomCause();readResponse(response);return cause==expected;} static int xaSeekL(uint32_t lba) { uint32_t total=lba+150;uint8_t msf[3]={itob(total/4500), itob((total/75) % 60), itob(total % 75)};if(xaCommand(CDL_SETLOC, msf, 3, NULL, NULL) !=3) return 0;if(xaCommand(CDL_SEEKL, NULL, 0, NULL, NULL) !=3) return 0;return xaWaitCause(2, 4000000);} static void xaStop() { CDROM_REG0=0;CDROM_REG1=CDL_PAUSE;for(unsigned i=0;i< 64;i++) { uint8_t response[16];uint32_t timeout=1000000;if(!waitCDRomIRQWithTimeout(&timeout)) return;uint8_t cause=ackCDRomCause();readResponse(response);if(cause==2) return;} } static void xaReadHead(uint8_t *head, unsigned count) { CDROM_REG0=0;CDROM_REG3=0x80;for(unsigned t=0;t< 50000;t++) { if(CDROM_REG0 &0x40) break;} for(unsigned i=0;i< count;i++) head[i]=CDROM_REG2;CDROM_REG0=0;CDROM_REG3=0;} static int xaRead(uint8_t mode, int filter, uint8_t file, uint8_t channel, uint32_t startLBA, uint32_t durationUs, unsigned stopAfterClear, struct XAReadStats *stats) { __builtin_memset(stats, 0, sizeof(*stats));stats->minLBA=0xffffffff;if(!resetCDRom()) return 0;if(xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL) !=3) return 0;if(filter) { uint8_t params[2]={file, channel};if(xaCommand(CDL_SETFILTER, params, 2, NULL, NULL) !=3) return 0;} if(!xaSeekL(startLBA)) return 0;if(xaCommand(CDL_READS, NULL, 0, NULL, NULL) !=3) return 0;initializeTime();uint32_t elapsed=0;while((elapsed< durationUs) &&(stats->events< 2000)) { uint32_t slice=30000;int got=waitCDRomIRQWithTimeout(&slice);elapsed=slice;if(!got) continue;int busy=(CDROM_REG0 &0x04) !=0;uint8_t cause=ackCDRomCause();uint8_t response[16];readResponse(response);stats->events++;if(cause !=1) { stats->nonDataCauses++;continue;} uint8_t head[16];const uint8_t *payload=head;if(mode &0x20) { xaReadHead(head, 16);payload=head+12;uint8_t submode=head[6];if(submode &0x04) { if(submode &0x40) { stats->audioRealTime++;} else { stats->audioNotRealTime++;} continue;} } else { xaReadHead(head, 4);} uint32_t lba=payload[0]|(payload[1]<< 8)|(payload[2]<< 16);if(lba< startLBA||lba > startLBA+20000) { stats->unstamped++;continue;} if(lba< stats->minLBA) stats->minLBA=lba;if(lba > stats->maxLBA) stats->maxLBA=lba;stats->slotMask|=1<<((lba - startLBA) % 16);if(payload[3]=='D') stats->form2Seen++;if(busy) { stats->busySet++;stats->lastLBABusy=lba;if(stats->busyClear) stats->busyAfterClear++;} else { if(!stats->busyClear) stats->firstLBAClear=lba;stats->busyClear++;if(stopAfterClear &&(stats->busyClear >=stopAfterClear)) break;} } xaStop();ramsyscall_printf("XA mode %02x filter %i %i/%i from %i: %i events, %i not data ready, %i unstamped, slots %04x, LBA %i-%i, " "form 2 %i, audio RT %i not RT %i, ADPBUSY set %i clear %i (last set %i, first clear %i)\n", mode, filter, file, channel, startLBA, stats->events, stats->nonDataCauses, stats->unstamped, stats->slotMask, stats->minLBA, stats->maxLBA, stats->form2Seen, stats->audioRealTime, stats->audioNotRealTime, stats->busySet, stats->busyClear, stats->lastLBABusy, stats->firstLBAClear);return 1;})
Definition xa.c:39