diff --git a/docs/ApiSerial.adoc b/docs/ApiSerial.adoc index 995f5c0..f6a2744 100644 --- a/docs/ApiSerial.adoc +++ b/docs/ApiSerial.adoc @@ -31,25 +31,7 @@ correct. On the IIgs a Super Serial Card only interrupts with its interrupt switch (SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but -loses what arrives while the program is busy. The card's 6551 holds a single -received byte, about 1 ms at 9600 baud, which is less than the IIgs's own -worst-case interrupt latency during a present: it runs loss-free at 2400 baud, -and above that relies on the protocol to recover a dropped byte. The built-in -modem and printer ports buffer three bytes and stay loss-free at 9600 through -full-screen presents, joystick reads and sound effects. To get there, JoeyLib -cuts its own long interrupts-off stretches into pieces of at most ~1.7 ms while -a port is open: the present and the full-surface clear, the joystick read -(~2.8 ms otherwise, with the stick at full deflection), and the sound-effect -calls into the NinjaTrackerPlus replayer (~2.4 ms to start an effect, and a -refill interrupt that tops up every streaming effect that is due at once). A -full-screen present gets about 5% slower; a joystick read costs about what it -does with no port open, up to ~0.8 ms more with the stick in a corner, and its -reading can differ from the same stick's reading with no port open by a count. -With no port open all of these run as before. Two stretches are not -JoeyLib's to cut and can still cost bytes: GS/OS disk access (a 3.5-inch -floppy holds interrupts off for 65 to 220 ms at a time), and starting a MOD -song with `jlAudioPlayMod`, which loads its instruments into the sound chip -with interrupts off, roughly 12 ms per KB of samples. +loses what arrives while the program is busy. On the Amiga the port is `serial.device`, which AmigaOS loads from `DEVS:`: every Workbench installation has it, but Kickstart 3.1 on its own does not, so @@ -60,6 +42,71 @@ to boot on its own must do the same. A failed `jlSerialOpen` is non-fatal: it returns `false` and every other call becomes a safe no-op, exactly like a failed audio init. +=== What Can Lose Received Bytes + +Every port receives into a small hardware buffer that an interrupt empties +into the ring. A byte is lost when that buffer fills before the interrupt +runs: when something keeps interrupts off, or keeps the CPU away from the +handler, for longer than the buffer lasts. A byte takes about 1 ms to arrive +at 9600 baud, so: + +[cols="3,2,2", options="header"] +|=== +|Port |Receive buffer |Lasts at 9600 baud +|IIgs modem and printer ports (Zilog 8530) |3 bytes |~3 ms +|IIgs Super Serial Card (6551) |1 byte |~1 ms +|DOS, 16550A UART |16 bytes, interrupt at 14 |~2 ms from the interrupt +|DOS, 8250 or 16450 UART |1 byte |~1 ms +|Amiga (Paula) |1 byte |~1 ms +|Atari ST (68901 MFP) |1 byte |~1 ms +|X68000 (Zilog 8530) |3 bytes |~3 ms +|=== + +At 19200 baud each lasts half as long, and so on. + +JoeyLib's own work is safe while a port is open. On the IIgs, where the +display and sound code masks interrupts for long stretches, opening a port +makes JoeyLib cut them into pieces of at most ~1.7 ms: the present and the +full-surface clear, the joystick read (~2.8 ms otherwise, with the stick at +full deflection), and the sound-effect calls into the NinjaTrackerPlus +replayer (~2.4 ms to start an effect, and a refill interrupt that tops up +every streaming effect that is due at once). A full-screen present gets about +5% slower; a joystick read costs about what it does with no port open, up to +~0.8 ms more with the stick in a corner, and can read one count different +from the same stick with no port open. With no port open all of these run as +before. The other ports mask interrupts only across a few register writes. + +These can still lose bytes, and are the program's to schedule around: + +* *Disk access.* On the IIgs, GS/OS holds interrupts off while it reads or + writes a 3.5-inch floppy: 65 to 220 ms at a time, measured. That is every + file call -- `jlFile*`, `jlSave*`, loading assets and sprite banks, and + `jlAudioInit`, which loads the replayer from disk. The other ports' disk + access goes through the operating system and has not been measured; treat + it as a risk there too. +* *Starting a song on the IIgs.* `jlAudioPlayMod` loads the song's + instruments into the sound chip with interrupts off, roughly 12 ms per KB of + samples. +* *Music playing on the IIgs.* The replayer's tick runs as an interrupt + handler, and has not been measured with a port open. +* *A Super Serial Card above 2400 baud.* The IIgs's own interrupt handling can + outlast the 6551's single byte, so the card runs loss-free at 2400 and above + that relies on the protocol to recover. The built-in modem and printer ports + stay loss-free at 9600 through full-screen presents, clears, joystick reads + and sound effects. +* *The program's own code*, wherever it masks interrupts. + +Ways to live with them: + +* Keep file access and `jlAudioPlayMod` out of the middle of a transfer: do + them between messages, or while the other end is waiting for a reply. +* Ask the other end to pause first. With hardware flow control, drop RTS with + `jlSerialSetRts(false)`, allow a few character times for bytes already on + the way, do the slow operation, then raise RTS again. With software flow + control, send XOFF and later XON. +* For data that must arrive intact, use a protocol with checksums and + retransmission; XMODEM and its descendants exist for exactly this. + === Types [source,c] diff --git a/examples/serloss/serloss.c b/examples/serloss/serloss.c index a3ec9eb..9dc94fb 100644 --- a/examples/serloss/serloss.c +++ b/examples/serloss/serloss.c @@ -210,7 +210,7 @@ static bool sfxStart(void) { for (i = 0u; i < SFX_WAVE_BYTES; i++) { gSfxWave[i] = (uint8_t)(i == 0u ? 1u : i); // 0x00 would stop the DOC } - // The first trigger of a sample converts it for the DOC (~0.2 s for this one); do it before + // The first trigger of a sample converts it for the DOC (~15 ms for this one); do it before // the port opens, so the loop's triggers are the cheap repeats and a slow first frame cannot // overflow the receive ring. jlAudioPlaySfx((uint8_t)SFX_SHOT_SLOT, (const uint8_t *)gSfxShot, SFX_SHOT_BYTES, SFX_SHOT_RATE); diff --git a/include/joey/serial.h b/include/joey/serial.h index c49ada7..4e7367e 100644 --- a/include/joey/serial.h +++ b/include/joey/serial.h @@ -23,6 +23,19 @@ // ring, and the hardware drain where it does not, so calling it every frame is // always correct. Transmit is a bounded poll. // +// Received bytes CAN be lost. Each port's receive hardware holds 1 to 3 bytes +// (16 on a 16550), ~1-3 ms at 9600 baud, and anything that keeps interrupts off +// longer drops what arrives meanwhile. JoeyLib's own work is safe with a port +// open (on the IIgs it cuts its long interrupts-off stretches to ~1.7 ms while +// one is), but these are not: IIgs disk access (GS/OS holds interrupts off +// 65-220 ms per 3.5" floppy access, so every file call, asset load and +// jlAudioInit), IIgs jlAudioPlayMod (~12 ms per KB of samples), a Super Serial +// Card above 2400 baud, and the program's own interrupt masking. Disk access +// on the other ports is unmeasured. Schedule them between messages, pause the +// peer first (jlSerialSetRts(false) or XOFF), or use a protocol that +// retransmits. docs/ApiSerial.adoc, "What Can Lose Received Bytes", has the +// per-port table. +// // A failed jlSerialOpen is non-fatal: it returns false and every other call is // a safe no-op (Available 0, Read 0), exactly like a failed audio init. diff --git a/src/iigs/audio_full.c b/src/iigs/audio_full.c index 96bf811..0a59c7d 100644 --- a/src/iigs/audio_full.c +++ b/src/iigs/audio_full.c @@ -418,6 +418,9 @@ unsigned char gCallStub[16]; // here rather than shared because only the holds below need it. extern uint16_t iigsGetTickWord(void); +// sfxCopyUnsigned's byte loop (sfxCopy.s). +extern void iigsSfxCopyUnsigned(unsigned char *dst, const uint8_t *src, uint16_t length, uint16_t padTo); + // ----- Internal helpers ----- @@ -721,27 +724,19 @@ static void sfxApplyPan(unsigned char *sfx, uint8_t slot, uint8_t vol) { // declare, so the bytes past a short sample get played too. They are // whatever the slot held before, very often zeros. 0x80 is unsigned // silence and plays harmlessly if it is ever reached. -// Safe in place (dst == src), which is how streamChunk uses it. +// Safe in place (dst == src), which is how streamChunk uses it. The bytes +// themselves are iigsSfxCopyUnsigned's (sfxCopy.s): 57 ms for a full slot, +// where the C loop it replaces took 0.73 s. +// length never exceeds SFX_SAMPLE_BYTES (both callers bound it), so it +// and padTo, an even page multiple or SFX_SAMPLE_BYTES itself, fit a word. static void sfxCopyUnsigned(unsigned char *dst, const uint8_t *src, uint32_t length) { uint32_t padTo; - uint32_t i; - - for (i = 0; i < length; i++) { - unsigned char v = (unsigned char)(src[i] ^ 0x80u); - - if (v == 0u) { - v = 1u; - } - dst[i] = v; - } padTo = (length + SFX_STREAM_PAGE - 1u) & ~(SFX_STREAM_PAGE - 1u); if (padTo > (uint32_t)SFX_SAMPLE_BYTES) { padTo = (uint32_t)SFX_SAMPLE_BYTES; } - for (i = length; i < padTo; i++) { - dst[i] = 0x80u; - } + iigsSfxCopyUnsigned(dst, src, (uint16_t)length, (uint16_t)padTo); } @@ -1095,9 +1090,8 @@ void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1 // Cache check: same sample, length, and rate as the prior trigger // for this slot? Then the slot's DOC sample bytes and stream - // struct are already correct -- skip the 4 KB byte-XOR loop and - // the 15-byte struct rebuild, both of which together can run - // ~50 ms per call. + // struct are already correct -- skip the sample conversion (~7 us + // a byte) and the struct rebuild. if (sample == gSfxSlotSample[slot] && length == gSfxSlotLength[slot] && rateHz == gSfxSlotRateHz[slot]) { diff --git a/src/iigs/sfxCopy.s b/src/iigs/sfxCopy.s new file mode 100644 index 0000000..78d7fd4 --- /dev/null +++ b/src/iigs/sfxCopy.s @@ -0,0 +1,150 @@ +; ==================================================================== +; sfxCopy.s - stage a sound effect's sample for the DOC (llvm-mc w65816) +; +; iigsSfxCopyUnsigned(unsigned char *dst, const uint8_t *src, +; uint16_t length, uint16_t padTo) +; +; The inner loop of audio_full.c's sfxCopyUnsigned, which says why: the +; sample is signed 8-bit (the public contract) and DOC RAM wants +; unsigned, so every byte gets its sign bit flipped, a byte that comes +; out 0x00 (the ES5503's wave-end marker) becomes 0x01, and the bytes +; from length up to padTo become 0x80, unsigned silence. padTo must be +; even and at least length; dst == src (in place) is fine, since every +; word is read before it is written. Either pointer may cross a bank. +; +; A word at a time through [dp],y with 16-bit Y, four words a pass with X +; counting the passes, and the 0x00 fix-ups off the straight line. +; Measured under MAME at 2.8 MHz: 7.0 us a byte, 57 ms for a full 8172- +; byte slot, against 90 us a byte for the compiled C loop, whose 32-bit +; index and bound became materialised compares on every byte. +; +; ABI: arg0 dst in A:X (A = low16, X = high16). src (4-byte ptr), length +; and padTo on the stack: src @4,s, length @8,s, padTo @10,s on JSL +; entry, +3 after PHP+PHD. They are copied into a stack (bank-0) +; D-frame: [0] = dst, [4] = src, 8 = length (then its odd-byte flag), +; 10 = padTo, 12 = the four-word pass count. +; A, X and Y are clobbered; P, D and DBR are preserved. +; ==================================================================== +SFX_FRAME_BYTES = 14 + +; One word: flip both sign bits, then turn a 0x00 byte into 0x01 through +; the fix-up stubs SC_FIX places after each loop, off its straight line. +.macro SC_WORD n + lda [4], y + eor #0x8080 + bit #0x00FF + beq scFixLo\n +scBackLo\n: + bit #0xFF00 + beq scFixHi\n +scBackHi\n: + sta [0], y + iny + iny +.endm + +.macro SC_FIX n +scFixLo\n: + ora #0x0001 + bra scBackLo\n +scFixHi\n: + ora #0x0100 + bra scBackHi\n +.endm + +.section .text.iigsSfxCopyUnsigned,"ax" +.globl iigsSfxCopyUnsigned +iigsSfxCopyUnsigned: + php + phd + rep #0x30 + .a16 + .i16 + tay ; park dst low16 + pha ; the pass-count slot (12) + lda 15,s ; padTo + pha + lda 15,s ; length (each push moves the next arg up to 15,s) + pha + lda 15,s ; src high16 + pha + lda 15,s ; src low16 + pha + phx ; dst high16 + phy ; dst low16 + tsc + inc a + tcd ; D -> the frame (pushed data starts at SP+1) + lda 8 + lsr a ; A = whole words, C = an odd last byte + tax + lda #0 + rol a + sta 8 ; 8 = 1 when there is an odd last byte + txa + lsr a + lsr a + sta 12 ; 12 = passes of four words + txa + ldy #0 + and #0x0003 + beq scPasses + tax ; the words left over from the passes, first +scSingle: + SC_WORD 0 + dex + bne scSingle + bra scPasses + SC_FIX 0 +scPasses: + ldx 12 + beq scWordsDone +scPass: + SC_WORD 1 + SC_WORD 2 + SC_WORD 3 + SC_WORD 4 + dex + bne scPass + bra scWordsDone + SC_FIX 1 + SC_FIX 2 + SC_FIX 3 + SC_FIX 4 +scWordsDone: + lda 8 + beq scPad + ; The odd last byte, then one pad byte: padTo is even, so the + ; word pad below starts on an even index again. + sep #0x20 + .a8 + lda [4], y + eor #0x80 + bne scOddStore + lda #0x01 +scOddStore: + sta [0], y + iny + lda #0x80 + sta [0], y + iny + rep #0x20 + .a16 +scPad: + cpy 10 + bcs scDone + lda #0x8080 +scPadWord: + sta [0], y + iny + iny + cpy 10 + bcc scPadWord +scDone: + tsc + clc + adc #SFX_FRAME_BYTES + tcs ; drop the D-frame + pld + plp + rtl diff --git a/toolchains/env.sh b/toolchains/env.sh deleted file mode 100644 index 315b6cd..0000000 --- a/toolchains/env.sh +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by install.sh -- do not edit. For a shell: -# source toolchains/env.sh -export JOEY_TOOLCHAINS='/home/scott/claude/joeylib/toolchains' -export AMIGA_PREFIX='/home/scott/claude/joeylib/toolchains/opt/amigaGcc' -export AMIGA_CC='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/m68k-amigaos-gcc' -export AMIGA_AR='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/m68k-amigaos-ar' -export AMIGA_LD='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/m68k-amigaos-ld' -export AMIGA_AS='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/vasmm68k_mot' -export AMIGA_NDK='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/m68k-amigaos/sys-include' -export AMIGA_SERIAL_DEVICE='/home/scott/claude/joeylib/toolchains/firmware/amiga/Devs/serial.device' -export CADIUS='/home/scott/claude/joeylib/toolchains/opt/cadius/bin/cadius' -export DOS_CWSDPMI='/home/scott/claude/joeylib/toolchains/opt/cwsdpmi/bin' -export DOS_CWSDSTUB='/home/scott/claude/joeylib/toolchains/opt/cwsdpmi/bin/CWSDSTUB.EXE' -export DOS_PREFIX='/home/scott/claude/joeylib/toolchains/opt/djgpp' -export DOS_CC='/home/scott/claude/joeylib/toolchains/opt/djgpp/bin/i586-pc-msdosdjgpp-gcc' -export DOS_AR='/home/scott/claude/joeylib/toolchains/opt/djgpp/bin/i586-pc-msdosdjgpp-ar' -export DOS_LD='/home/scott/claude/joeylib/toolchains/opt/djgpp/bin/i586-pc-msdosdjgpp-ld' -export DOS_STUBEDIT='/home/scott/claude/joeylib/toolchains/opt/djgpp/i586-pc-msdosdjgpp/bin/stubedit' -export DOS_STUBIFY='/home/scott/claude/joeylib/toolchains/opt/djgpp/i586-pc-msdosdjgpp/bin/stubify' -export DOS_EXE2COFF='/home/scott/claude/joeylib/toolchains/opt/djgpp/i586-pc-msdosdjgpp/bin/exe2coff' -export DOS_EMBED_DPMI='/home/scott/claude/joeylib/make/support/dos/embed-dpmi.sh' -export DOSBOX='/home/scott/claude/joeylib/toolchains/opt/dosbox/dosbox' -export X68K_ROOT='/home/scott/claude/joeylib/toolchains/opt/elf2x68k' -export EMUTOS_IMG='/home/scott/claude/joeylib/toolchains/firmware/atarist/emutos-512k.img' -export FSUAE='/usr/bin/fs-uae' -export HATARI='/usr/bin/hatari' -export IIGS_ROMPATH='/home/scott/claude/joeylib/toolchains/firmware/iigs/roms' -export GSOS_SYSTEM_DISK='/home/scott/claude/joeylib/toolchains/firmware/iigs/gsos-system.po' -export KICKSTART_ROM='/home/scott/claude/joeylib/toolchains/firmware/amiga/kickstart.rom' -export LIBXMP_DIR='/home/scott/claude/joeylib/toolchains/opt/libxmp' -export LLVM816_ROOT='/home/scott/claude/joeylib/toolchains/opt/llvm816' -export IIGS_CLANG_BUILD='/home/scott/claude/joeylib/make/support/iigs/clang-build.sh' -export MAME='/home/scott/claude/joeylib/toolchains/opt/mame/bin/mame' -export IIGS_AS='/home/scott/claude/joeylib/toolchains/opt/merlin32/bin/merlin32' -export ST_PREFIX='/home/scott/claude/joeylib/toolchains/opt/mintGcc' -export ST_CC='/home/scott/claude/joeylib/toolchains/opt/mintGcc/bin/m68k-atari-mint-gcc' -export ST_AR='/home/scott/claude/joeylib/toolchains/opt/mintGcc/bin/m68k-atari-mint-ar' -export ST_LD='/home/scott/claude/joeylib/toolchains/opt/mintGcc/bin/m68k-atari-mint-ld' -export DOS_AS='/usr/bin/nasm' -export IIGS_NTP_DIR='/home/scott/claude/joeylib/toolchains/opt/ntp' -export AMIGA_PTPLAYER='/home/scott/claude/joeylib/toolchains/opt/ptplayer' -export X68K_DIR='/home/scott/claude/joeylib/toolchains/firmware/x68000' -case ":$PATH:" in *":/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin:"*) ;; *) PATH='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin':"$PATH" ;; esac -case ":$PATH:" in *":/home/scott/claude/joeylib/toolchains/opt/djgpp/bin:"*) ;; *) PATH='/home/scott/claude/joeylib/toolchains/opt/djgpp/bin':"$PATH" ;; esac -case ":$PATH:" in *":/home/scott/claude/joeylib/toolchains/opt/elf2x68k/bin:"*) ;; *) PATH='/home/scott/claude/joeylib/toolchains/opt/elf2x68k/bin':"$PATH" ;; esac -case ":$PATH:" in *":/home/scott/claude/joeylib/toolchains/opt/mintGcc/bin:"*) ;; *) PATH='/home/scott/claude/joeylib/toolchains/opt/mintGcc/bin':"$PATH" ;; esac -export PATH diff --git a/tools/iigsStart/iigsStart.c b/tools/iigsStart/iigsStart.c index a0dd86e..2d8d8e9 100644 --- a/tools/iigsStart/iigsStart.c +++ b/tools/iigsStart/iigsStart.c @@ -30,34 +30,43 @@ #define JOEY_START_PATH "/JOEYLIB/STAXI" #endif -// Fixed scratch in bank $E1: the $E1:9DC8-$9DFD window that survives a -// present (SCB proper ends at $9DC7). The asm stubs hard-code the two -// parameter-block addresses, so these must agree with iigsStartQuit.s. -// 9DC8 mailbox 9DCC QuitGS parm (8) -// 9DD4 pathname string 9DE4 SetPrefix parm (8) -// 9DEC prefix string +// The harness's marker, in the $E1:9DC8-$9DFD window that survives a +// present (SCB proper ends at $9DC7). #define QUIT_MAILBOX ((volatile uint8_t *)0xE19DC8L) -#define QUIT_PARM ((volatile uint8_t *)0xE19DCCL) -#define QUIT_PATH ((volatile uint8_t *)0xE19DD4L) -#define PREFIX_PARM ((volatile uint8_t *)0xE19DE4L) -#define PREFIX_PATH ((volatile uint8_t *)0xE19DECL) -#define QUIT_PATH_ADDR 0x00E19DD4UL -#define PREFIX_PATH_ADDR 0x00E19DECUL // A GS/OS class-1 input string is a length WORD followed by the characters. #define GSOS_STRING_HEADER 2u +// The parameter blocks: SetPrefixGS {pCount, prefixNum, prefix} and +// QuitGS {pCount, pathname, flags}. +#define PREFIX_PARM_BYTES 8u +#define PREFIX_PARM_PATH 4u +#define QUIT_PARM_BYTES 8u +#define QUIT_PARM_PATH 2u +#define GSOS_PCOUNT_2 2u +#define PREFIX_CURRENT 0u // prefix 0 = the current directory +// Room for the whole path, which is the longest either string gets. +#define PATH_STRING_BYTES (GSOS_STRING_HEADER + sizeof(JOEY_START_PATH)) // Markers for the harness. #define QUIT_REACHED 0xAAu #define QUIT_RETURNED 0xEEu -void iigsStartQuit(void); -void iigsStartSetPrefix(void); +static void gsosString(uint8_t *dst, const char *src, uint16_t len); +void iigsStartQuit(uint8_t *parm); +void iigsStartSetPrefix(uint8_t *parm); +static void putLong(uint8_t *dst, uint32_t v); -// Write a GS/OS input string (length word + characters) and return its length. -static uint16_t gsosString(volatile uint8_t *dst, const char *src, uint16_t len) { +// In the launcher's own data, where GS/OS reads them during each call. +static uint8_t gPrefixParm[PREFIX_PARM_BYTES]; +static uint8_t gPrefixPath[PATH_STRING_BYTES]; +static uint8_t gQuitParm[QUIT_PARM_BYTES]; +static uint8_t gQuitPath[PATH_STRING_BYTES]; + + +// Write a GS/OS input string: length word, then the characters. +static void gsosString(uint8_t *dst, const char *src, uint16_t len) { uint16_t i; dst[0] = (uint8_t)(len & 0xFFu); @@ -65,11 +74,10 @@ static uint16_t gsosString(volatile uint8_t *dst, const char *src, uint16_t len) for (i = 0; i < len; i++) { dst[GSOS_STRING_HEADER + i] = (uint8_t)src[i]; } - return len; } -static void putLong(volatile uint8_t *dst, uint32_t v) { +static void putLong(uint8_t *dst, uint32_t v) { dst[0] = (uint8_t)(v & 0xFFu); dst[1] = (uint8_t)((v >> 8) & 0xFFu); dst[2] = (uint8_t)((v >> 16) & 0xFFu); @@ -94,28 +102,27 @@ int main(void) { } } - gsosString(PREFIX_PATH, path, dir); - PREFIX_PARM[0] = 2u; // pCount - PREFIX_PARM[1] = 0u; - PREFIX_PARM[2] = 0u; // prefixNum 0 = current directory - PREFIX_PARM[3] = 0u; - putLong(PREFIX_PARM + 4, PREFIX_PATH_ADDR); - iigsStartSetPrefix(); + gsosString(gPrefixPath, path, dir); + gPrefixParm[0] = GSOS_PCOUNT_2; + gPrefixParm[1] = 0u; + gPrefixParm[2] = PREFIX_CURRENT; + gPrefixParm[3] = 0u; + putLong(gPrefixParm + PREFIX_PARM_PATH, (uint32_t)gPrefixPath); + iigsStartSetPrefix(gPrefixParm); - gsosString(QUIT_PATH, path, len); - QUIT_PARM[0] = 2u; // pCount - QUIT_PARM[1] = 0u; - putLong(QUIT_PARM + 2, QUIT_PATH_ADDR); // pathname - QUIT_PARM[6] = 0u; // flags - QUIT_PARM[7] = 0u; + gsosString(gQuitPath, path, len); + gQuitParm[0] = GSOS_PCOUNT_2; + gQuitParm[1] = 0u; + putLong(gQuitParm + QUIT_PARM_PATH, (uint32_t)gQuitPath); + gQuitParm[6] = 0u; // flags + gQuitParm[7] = 0u; *QUIT_MAILBOX = QUIT_REACHED; - iigsStartQuit(); + iigsStartQuit(gQuitParm); *QUIT_MAILBOX = QUIT_RETURNED; - // QuitGS only returns on failure. Nothing useful is left to do and the - // llvm-mos post-main exit path is broken on GS/OS, so spin rather than - // crash: the harness reads the mailbox to see what happened. + // QuitGS only returns on failure. Nothing useful is left to do, so + // spin: the harness reads the mailbox to see what happened. for (;;) { } } diff --git a/tools/iigsStart/iigsStartQuit.s b/tools/iigsStart/iigsStartQuit.s index 771d278..31c63ec 100644 --- a/tools/iigsStart/iigsStartQuit.s +++ b/tools/iigsStart/iigsStartQuit.s @@ -1,12 +1,16 @@ ; GS/OS inline-call stubs for iigsStart. ; -; Same shape as iigsQuitGS in src/iigs/joeyDraw.s and for the same reason: -; pure constants, no relocations, so the cross-bank DBR trap that bit the -; first attempt at that routine cannot apply here either. Each parameter -; block lives at a FIXED address in the $E1:9DC8-$9DFD scratch window and -; the C caller fills it in before calling. +; The same inline form and the same self-patch as the llvm816 runtime's +; gsosDispatch (runtime/src/iigsGsos.s): the caller passes the parameter +; block's address in A:X (A = offset, X = bank), and the stub writes it +; into its own inline pBlock operand before the call. The stores target +; the stub's own bank (phk/plb), not the caller's DBR. The blocks used to +; sit at fixed addresses in the $E1:9DC8-$9DFD scratch window, which is 54 +; bytes: a program name of 8 characters or more ran the QuitGS pathname +; over the SetPrefix string, prefix 0 was never set, and the program could +; not open "ntpplayer" or its DATA folder. -; iigsStartSetPrefix -- SetPrefixGS ($2009), parm block at $E1:9DE4. +; iigsStartSetPrefix(parm) -- SetPrefixGS ($2009). ; ; GS/OS does NOT point prefix 0 at the program named in a QuitGS launch: it ; was measured still pointing at the BOOT volume, so the launched example's @@ -17,12 +21,22 @@ .section .text.iigsStartSetPrefix,"ax" .globl iigsStartSetPrefix iigsStartSetPrefix: + phb + phk + plb + sta spPblock + sep #0x20 + txa + sta spPblock+2 + rep #0x20 + plb jsl 0xE100A8 .word 0x2009 - .long 0x00E19DE4 +spPblock: + .long 0 rtl -; iigsStartQuit -- QuitGS ($2029), parm block at $E1:9DCC. +; iigsStartQuit(parm) -- QuitGS ($2029). ; ; Unlike jlpShutdown's pCount=0 "quit to my parent", this is called with ; pCount=2 and a pathname, which tells GS/OS to LAUNCH that program in our @@ -30,7 +44,17 @@ iigsStartSetPrefix: .section .text.iigsStartQuit,"ax" .globl iigsStartQuit iigsStartQuit: + phb + phk + plb + sta qtPblock + sep #0x20 + txa + sta qtPblock+2 + rep #0x20 + plb jsl 0xE100A8 .word 0x2029 - .long 0x00E19DCC +qtPblock: + .long 0 rtl