diff --git a/arch/m68k-amiga/devs/cd/cd.conf b/arch/m68k-amiga/devs/cd/cd.conf index 930eea3a2a2..c79313fd0ef 100644 --- a/arch/m68k-amiga/devs/cd/cd.conf +++ b/arch/m68k-amiga/devs/cd/cd.conf @@ -1,6 +1,6 @@ ##begin config basename cd -version 40.3 +version 40.4 libbasetype struct cdBase residentpri 5 beginio_func BeginIO diff --git a/arch/m68k-amiga/devs/cd/cd32.c b/arch/m68k-amiga/devs/cd/cd32.c index 9d818fc9e5a..8076588da19 100644 --- a/arch/m68k-amiga/devs/cd/cd32.c +++ b/arch/m68k-amiga/devs/cd/cd32.c @@ -1522,24 +1522,31 @@ static LONG CD32_DoIO(struct IOStdReq *io, APTR priv) } break; case CD_PLAYTRACK: - if (io->io_Length == 0) { - err = CDERR_BADLENGTH; - } else if (io->io_Offset <= cu->cu_CDTOC[0].Summary.LastTrack) { - ULONG start; - ULONG end; - ULONG last = io->io_Offset + io->io_Length; - - start = bcdmsf2sec(&cu->cu_CDTOC[io->io_Offset].Entry.Position.MSF); - if (last > cu->cu_CDTOC[0].Summary.LastTrack) { - end = bcdmsf2sec(&cu->cu_CDTOC[0].Summary.LeadOut.MSF); - } else { - end = bcdmsf2sec(&cu->cu_CDTOC[last].Entry.Position.MSF); + { + /* CD32 Kickstart consumes word-sized track arguments. Legacy + * callers may initialise only the low words of these fields. */ + UWORD track = (UWORD)io->io_Offset; + UWORD count = (UWORD)io->io_Length; + + if (count == 0) { + err = CDERR_BADLENGTH; + } else if (track <= cu->cu_CDTOC[0].Summary.LastTrack) { + ULONG start; + ULONG end; + ULONG last = (ULONG)track + count; + + start = bcdmsf2sec(&cu->cu_CDTOC[track].Entry.Position.MSF); + if (last > cu->cu_CDTOC[0].Summary.LastTrack) { + end = bcdmsf2sec(&cu->cu_CDTOC[0].Summary.LeadOut.MSF); + } else { + end = bcdmsf2sec(&cu->cu_CDTOC[last].Entry.Position.MSF); + } + err = end > start ? + CD32_StartPlay(cu, io, start, end - start) : + CDERR_BADLENGTH; } - err = end > start ? - CD32_StartPlay(cu, io, start, end - start) : - CDERR_BADLENGTH; + break; } - break; case CD_PLAYMSF: { union LSNMSF start, length; diff --git a/arch/m68k-amiga/devs/cd/mmakefile.src b/arch/m68k-amiga/devs/cd/mmakefile.src index 4c864665e9d..2b2d6194fe9 100644 --- a/arch/m68k-amiga/devs/cd/mmakefile.src +++ b/arch/m68k-amiga/devs/cd/mmakefile.src @@ -13,8 +13,8 @@ FILES := cd cd32 # Native CD32 regressions belong to the CD device component. #MM- test-amiga-m68k : test-devices-cd-amiga-m68k #MM- test-amiga-m68k-quick : test-devices-cd-amiga-m68k-quick -#MM- test-devices-cd-amiga-m68k : test-devices-cdpause -#MM- test-devices-cd-amiga-m68k-quick : test-devices-cdpause-quick +#MM- test-devices-cd-amiga-m68k : test-devices-cdpause test-devices-cdplaytrack +#MM- test-devices-cd-amiga-m68k-quick : test-devices-cdpause-quick test-devices-cdplaytrack-quick #USER_CPPFLAGS += -DDEBUG=1 diff --git a/developer/debug/test/devices/cdplaytrack/README b/developer/debug/test/devices/cdplaytrack/README new file mode 100644 index 00000000000..23f2b8bf2c1 --- /dev/null +++ b/developer/debug/test/devices/cdplaytrack/README @@ -0,0 +1,24 @@ +Copyright (C) 2026, The AROS Development Team. All rights reserved. + +Build test-devices-cdplaytrack or test-devices-cdplaytrack-quick. The native Amiga CD +device test group selects this regression. Run devices/cdplaytrack/cdplaytracktest +with an audio track numbered 2 present. The test checks ordinary and +upper-word-contaminated track/count fields, asynchronous playback, CD_INFO, +and rejection of counts whose low word is zero. + +A disposable fixture can be generated from the repository root: + + python3 developer/debug/test/devices/cd/readahead/make_disc.py /tmp/cd-word-data.iso + python3 - <<'PYTHON' + from pathlib import Path + Path('/tmp/cd-word-audio.bin').write_bytes(bytes(2352 * 75 * 20)) + Path('/tmp/cd-word.cue').write_text( + 'FILE "cd-word-data.iso" BINARY\n' + ' TRACK 01 MODE1/2048\n INDEX 01 00:00:00\n' + 'FILE "cd-word-audio.bin" BINARY\n' + ' TRACK 02 AUDIO\n INDEX 01 00:00:00\n') + PYTHON + +Mount /tmp/cd-word.cue in a stock CD32. Its silent audio track is sufficient: +the assertions inspect public playback state, not subjective sound output. +No game image is required. Exit 0 means all checks passed; failures return 20. diff --git a/developer/debug/test/devices/cdplaytrack/cdplaytracktest.c b/developer/debug/test/devices/cdplaytrack/cdplaytracktest.c new file mode 100644 index 00000000000..19776dffe20 --- /dev/null +++ b/developer/debug/test/devices/cdplaytrack/cdplaytracktest.c @@ -0,0 +1,96 @@ +/* + Copyright (C) 2026, The AROS Development Team. All rights reserved. + + Exercise CD_PLAYTRACK callers that initialise only word-sized track + arguments. Requires an audio track 2 longer than 0.1 seconds. +*/ +#include +#include +#include +#include + +static ULONG checks, failures; + +static void check(BOOL okay, CONST_STRPTR what) +{ + checks++; + if (!okay) + { + bug("CD PLAYTRACK FAIL: %s\n", what); + failures++; + } +} + +static void playcase(struct IOStdReq *play, struct IOStdReq *control, + ULONG track, ULONG count, BOOL valid) +{ + struct CDInfo info = { 0 }; + BOOL completed; + + play->io_Command = CD_PLAYTRACK; + play->io_Offset = track; + play->io_Length = count; + play->io_Data = NULL; + SendIO((struct IORequest *)play); + Delay(5); + completed = CheckIO((struct IORequest *)play) != NULL; + bug("CD PLAYTRACK track %08lx count %08lx completed %ld error %ld\n", + track, count, (LONG)completed, (LONG)play->io_Error); + if (valid) + { + check(!completed && play->io_Error == 0, + "valid low-word track arguments start asynchronous playback"); + control->io_Command = CD_INFO; + control->io_Data = &info; + control->io_Length = sizeof(info); + check(DoIO((struct IORequest *)control) == 0, "read playback state"); + check((info.Status & CDSTSF_PLAYING) != 0, "audio playback is active"); + } + else + check(completed && play->io_Error == CDERR_BADLENGTH, + "zero low-word track count is rejected"); + + if (!completed) + AbortIO((struct IORequest *)play); + WaitIO((struct IORequest *)play); +} + +int main(void) +{ + struct MsgPort *port = CreateMsgPort(); + struct IOStdReq *play = NULL, *control = NULL; + BOOL play_open = FALSE, control_open = FALSE; + + if (!port) + goto setupfail; + play = (struct IOStdReq *)CreateIORequest(port, sizeof(*play)); + control = (struct IOStdReq *)CreateIORequest(port, sizeof(*control)); + if (!play || !control) + goto setupfail; + if (OpenDevice("cd.device", 0, (struct IORequest *)play, 0)) + goto setupfail; + play_open = TRUE; + if (OpenDevice("cd.device", 0, (struct IORequest *)control, 0)) + goto setupfail; + control_open = TRUE; + control->io_Command = CD_PAUSE; + control->io_Length = 0; + check(DoIO((struct IORequest *)control) == 0, "initialise unpaused state"); + + playcase(play, control, 2, 1, TRUE); + playcase(play, control, 0x00010002, 1, TRUE); + playcase(play, control, 0xffff0002, 0x00020001, TRUE); + playcase(play, control, 2, 0, FALSE); + playcase(play, control, 2, 0x00010000, FALSE); + goto end; +setupfail: + check(FALSE, "set up CD requests"); +end: + if (control_open) CloseDevice((struct IORequest *)control); + if (play_open) CloseDevice((struct IORequest *)play); + if (control) DeleteIORequest((struct IORequest *)control); + if (play) DeleteIORequest((struct IORequest *)play); + if (port) DeleteMsgPort(port); + bug("CD PLAYTRACK: %lu/%lu pass, %lu failures\n", checks - failures, checks, failures); + return failures ? 20 : 0; +} diff --git a/developer/debug/test/devices/cdplaytrack/mmakefile.src b/developer/debug/test/devices/cdplaytrack/mmakefile.src new file mode 100644 index 00000000000..bc7bd3ea142 --- /dev/null +++ b/developer/debug/test/devices/cdplaytrack/mmakefile.src @@ -0,0 +1,11 @@ +# Copyright (C) 2026, The AROS Development Team. All rights reserved. + +include $(SRCDIR)/config/aros.cfg +USER_LDFLAGS := -static + +#MM test-devices-cdplaytrack : includes linklibs + +%build_progs mmake=test-devices-cdplaytrack \ + files=cdplaytracktest targetdir=$(AROS_TESTS)/devices/cdplaytrack + +%common