Sound FX on GS fixed.
This commit is contained in:
parent
70f67d232d
commit
fe84be766f
8 changed files with 316 additions and 128 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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]) {
|
||||
|
|
|
|||
150
src/iigs/sfxCopy.s
Normal file
150
src/iigs/sfxCopy.s
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 (;;) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue