Lots of work, but mostly serial related.
This commit is contained in:
parent
8b9590ddb2
commit
70f67d232d
59 changed files with 3953 additions and 1225 deletions
11
LICENSES.md
11
LICENSES.md
|
|
@ -215,9 +215,9 @@ the source of each before shipping anything that depends on them.
|
||||||
|
|
||||||
### Downloaded by the installer
|
### Downloaded by the installer
|
||||||
|
|
||||||
- **Apple IIgs MAME ROM sets** (`apple2gs`, ROM 03, and `apple2gsr1`,
|
- **Apple IIgs MAME ROM sets** (`apple2gs`, ROM 03, `apple2gsr1`, ROM 01,
|
||||||
ROM 01): Apple Inc. proprietary firmware, from the archive.org MAME 0.240
|
and `a2ssc`, the Super Serial Card): Apple Inc. proprietary firmware, from
|
||||||
split set. Component `iigsFirmware`.
|
the archive.org MAME 0.240 split set. Component `iigsFirmware`.
|
||||||
- **Apple IIgs System 6.0.4** (`System.Disk`): Apple Inc. proprietary OS, from
|
- **Apple IIgs System 6.0.4** (`System.Disk`): Apple Inc. proprietary OS, from
|
||||||
the Apple II archive (<https://mirrors.apple2.org.za/>). Component
|
the Apple II archive (<https://mirrors.apple2.org.za/>). Component
|
||||||
`iigsFirmware`.
|
`iigsFirmware`.
|
||||||
|
|
@ -236,6 +236,11 @@ project hosts (`JOEY_BUNDLE_URL`); matched by the checksums in
|
||||||
- **Amiga Kickstart 3.1 ROM**: Cloanto / Amiga Corp. proprietary, sold today
|
- **Amiga Kickstart 3.1 ROM**: Cloanto / Amiga Corp. proprietary, sold today
|
||||||
(Amiga Forever). Required by the Amiga test gates; `joey-run` falls back to
|
(Amiga Forever). Required by the Amiga test gates; `joey-run` falls back to
|
||||||
FS-UAE's AROS without it. Component `kickstart`.
|
FS-UAE's AROS without it. Component `kickstart`.
|
||||||
|
- **Amiga serial.device 37.4** (Workbench 3.1's `Devs/serial.device`):
|
||||||
|
Cloanto / Amiga Corp. proprietary, part of Workbench 3.1 (Amiga Forever).
|
||||||
|
Kickstart 3.1 has no serial.device in ROM, so `joey-run` and the Amiga
|
||||||
|
serial gate put this copy on the boot drive; `import` also finds it inside
|
||||||
|
a Workbench `.adf`. Component `amigaSerial`.
|
||||||
- **X68000 CG (font) ROM**: not part of Sharp's free release. MAME's x68000
|
- **X68000 CG (font) ROM**: not part of Sharp's free release. MAME's x68000
|
||||||
driver needs it. Component `x68kCgrom`.
|
driver needs it. Component `x68kCgrom`.
|
||||||
- **Space Taxi C64 memory image** (`stuff/spacetaxi/raw.bin`): the original
|
- **Space Taxi C64 memory image** (`stuff/spacetaxi/raw.bin`): the original
|
||||||
|
|
|
||||||
12
Makefile
12
Makefile
|
|
@ -9,6 +9,7 @@
|
||||||
# make x68000 builds Sharp X68000 only
|
# make x68000 builds Sharp X68000 only
|
||||||
# make docs renders the manual to build/docs (HTML + PDF)
|
# make docs renders the manual to build/docs (HTML + PDF)
|
||||||
# make verify-audio runs every port's audio gate (needs emulators)
|
# make verify-audio runs every port's audio gate (needs emulators)
|
||||||
|
# make verify-serial runs every port's serial loopback gate
|
||||||
# make clean removes all build outputs
|
# make clean removes all build outputs
|
||||||
#
|
#
|
||||||
# Cross tools come from ./install.sh, which records their paths in
|
# Cross tools come from ./install.sh, which records their paths in
|
||||||
|
|
@ -43,7 +44,7 @@ ifeq ($(HAVE_X68000),1)
|
||||||
ALL_TARGETS += x68000
|
ALL_TARGETS += x68000
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify-audio docs
|
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify-audio verify-serial docs
|
||||||
|
|
||||||
all: $(ALL_TARGETS) tools
|
all: $(ALL_TARGETS) tools
|
||||||
ifeq ($(ALL_TARGETS),)
|
ifeq ($(ALL_TARGETS),)
|
||||||
|
|
@ -92,7 +93,13 @@ docs:
|
||||||
# non-zero on purpose, so a run that tested nothing cannot look green.
|
# non-zero on purpose, so a run that tested nothing cannot look green.
|
||||||
# Takes a subset: make verify-audio PORTS="atarist amiga"
|
# Takes a subset: make verify-audio PORTS="atarist amiga"
|
||||||
verify-audio:
|
verify-audio:
|
||||||
@$(REPO_DIR)/scripts/verify-audio-all.sh $(PORTS)
|
@$(REPO_DIR)/scripts/verify-all.sh audio $(PORTS)
|
||||||
|
|
||||||
|
# The host check of the shared serial layer, then every port's loopback gate
|
||||||
|
# (banner out, probe echoed back), sequentially, with the same exit codes.
|
||||||
|
# Takes a subset: make verify-serial PORTS="host dos"
|
||||||
|
verify-serial:
|
||||||
|
@$(REPO_DIR)/scripts/verify-all.sh serial $(PORTS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(REPO_DIR)/build
|
rm -rf $(REPO_DIR)/build
|
||||||
|
|
@ -110,5 +117,6 @@ help:
|
||||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||||
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
||||||
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
||||||
|
@echo " verify-serial (host check + all 5 serial loopback gates; PORTS= subset)"
|
||||||
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
||||||
@echo "Install toolchains first: ./install.sh install toolchains"
|
@echo "Install toolchains first: ./install.sh install toolchains"
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -65,9 +65,10 @@ the emulators and their firmware:
|
||||||
./joey-run --headless --seconds 30 --snapshot draw.png --port atarist draw
|
./joey-run --headless --seconds 30 --snapshot draw.png --port atarist draw
|
||||||
```
|
```
|
||||||
|
|
||||||
Three files are never downloaded -- the Amiga Kickstart, the X68000 font ROM
|
Four files are never downloaded -- the Amiga Kickstart, the Amiga
|
||||||
and Space Taxi's C64 memory image -- so supply your copies once with
|
`serial.device` from Workbench 3.1, the X68000 font ROM and Space Taxi's C64
|
||||||
`./install.sh import PATH`. The manual's Getting Started chapter
|
memory image -- so supply your copies once with `./install.sh import PATH`
|
||||||
|
(files, folders, `.zip` archives or Amiga `.adf` disk images). The manual's Getting Started chapter
|
||||||
covers `joey-run`'s options and the private-file bundle.
|
covers `joey-run`'s options and the private-file bundle.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -386,13 +387,9 @@ re-indexing on the device.
|
||||||
Two blob formats:
|
Two blob formats:
|
||||||
|
|
||||||
* **`.tbk` (tile bank)** -- one or more 8x8 tiles in per-target
|
* **`.tbk` (tile bank)** -- one or more 8x8 tiles in per-target
|
||||||
planar layout (Amiga plane-major; Atari ST row-major-with-planes-
|
planar layout (Amiga and X68000 plane-major; Atari ST row-major-with-
|
||||||
per-row; DOS / IIgs chunky 4bpp). The loader `jlTileBankLoad`
|
planes-per-row; DOS / IIgs chunky 4bpp). The loader `jlTileBankLoad`
|
||||||
rejects files baked for the wrong target. There is no X68000 target:
|
rejects files baked for the wrong target.
|
||||||
`assetbake.py` bakes only the four above, and the loader still expects
|
|
||||||
the DOS chunky target byte there (`src/core/assetLoad.c`) even though
|
|
||||||
that port's surfaces and tile ops are now planar -- so `.tbk` baking
|
|
||||||
is not wired up for the X68000 yet.
|
|
||||||
* **`.spr` (sprite cel set)** -- one or more uniform-sized sprite
|
* **`.spr` (sprite cel set)** -- one or more uniform-sized sprite
|
||||||
cels in cross-target chunky 4bpp. The Phase 11 walker reads chunky
|
cels in cross-target chunky 4bpp. The Phase 11 walker reads chunky
|
||||||
and converts to planar at draw time, so the same blob serves every
|
and converts to planar at draw time, so the same blob serves every
|
||||||
|
|
@ -401,7 +398,7 @@ Two blob formats:
|
||||||
Both formats embed an optional 16-entry `$0RGB` palette.
|
Both formats embed an optional 16-entry `$0RGB` palette.
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/assetbake/assetbake.py --type tile --target {amiga|atarist|dos|iigs} in.png out.tbk
|
tools/assetbake/assetbake.py --type tile --target {amiga|atarist|dos|iigs|x68000} in.png out.tbk
|
||||||
tools/assetbake/assetbake.py --type sprite --cell WxH in.png out.spr
|
tools/assetbake/assetbake.py --type sprite --cell WxH in.png out.spr
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -411,7 +408,8 @@ per target so `make <target>` produces baked blobs under
|
||||||
runtime tree at `build/<target>/.../DATA/`. `make/iigs.mk` runs no
|
runtime tree at `build/<target>/.../DATA/`. `make/iigs.mk` runs no
|
||||||
`assetbake.py`: its `generated/iigs` tree is produced out-of-band and the
|
`assetbake.py`: its `generated/iigs` tree is produced out-of-band and the
|
||||||
fragment only bakes `.spc` from the committed `.spr`. `make/x68000.mk`
|
fragment only bakes `.spc` from the committed `.spr`. `make/x68000.mk`
|
||||||
has no asset rules at all.
|
builds only what Space Taxi loads there: its levels and pre-compiled cel
|
||||||
|
banks.
|
||||||
|
|
||||||
For runtime-extracted content (sprites peeled out of a procedural
|
For runtime-extracted content (sprites peeled out of a procedural
|
||||||
or captured surface), use `jlSpriteCreateFromSurface` -- the
|
or captured surface), use `jlSpriteCreateFromSurface` -- the
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,44 @@ the module entirely.
|
||||||
One byte-stream API spans every port while the HAL hides very different
|
One byte-stream API spans every port while the HAL hides very different
|
||||||
hardware: the IIgs drives a Zilog 8530 SCC directly or a 6551 card in a slot;
|
hardware: the IIgs drives a Zilog 8530 SCC directly or a 6551 card in a slot;
|
||||||
DOS uses an interrupt-driven 8250/16550 UART; the Amiga uses `serial.device`;
|
DOS uses an interrupt-driven 8250/16550 UART; the Amiga uses `serial.device`;
|
||||||
the ST goes through the BIOS Aux channel.
|
the ST goes through the BIOS Aux channel; the X68000 transmits through IOCS
|
||||||
|
and receives on its own SCC interrupt.
|
||||||
|
|
||||||
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
||||||
background ring -- a hardware interrupt on DOS, the OS on Amiga and ST, or
|
background ring -- a hardware interrupt on DOS, the X68000 and the IIgs, the
|
||||||
`jlSerialPoll` on the IIgs -- and reads never block. Call `jlSerialPoll` once
|
OS on Amiga and ST -- and reads never block. Call `jlSerialPoll` once per
|
||||||
per frame: it is a no-op where the interrupt or OS already fills the ring, and
|
frame: it is next to a no-op where the interrupt or OS already fills the ring,
|
||||||
the hardware drain where it does not, so calling it every frame is always
|
and the hardware drain where nothing does, so calling it every frame is always
|
||||||
correct.
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
a program booted without Workbench cannot open the port. `joey-run` puts the
|
||||||
|
installer's copy (component `amigaSerial`) on the boot drive; a game shipped
|
||||||
|
to boot on its own must do the same.
|
||||||
|
|
||||||
A failed `jlSerialOpen` is non-fatal: it returns `false` and every other call
|
A failed `jlSerialOpen` is non-fatal: it returns `false` and every other call
|
||||||
becomes a safe no-op, exactly like a failed audio init.
|
becomes a safe no-op, exactly like a failed audio init.
|
||||||
|
|
||||||
|
|
@ -37,8 +66,8 @@ becomes a safe no-op, exactly like a failed audio init.
|
||||||
----
|
----
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JL_SERIAL_DEFAULT = 0, // the platform's natural RS-232 port
|
JL_SERIAL_DEFAULT = 0, // the platform's natural RS-232 port
|
||||||
JL_SERIAL_MODEM, // IIgs modem port (SCC channel A)
|
JL_SERIAL_MODEM, // IIgs modem port (SCC channel B)
|
||||||
JL_SERIAL_PRINTER, // IIgs printer port (SCC channel B)
|
JL_SERIAL_PRINTER, // IIgs printer port (SCC channel A)
|
||||||
JL_SERIAL_SLOT // card/port selected by config->unit
|
JL_SERIAL_SLOT // card/port selected by config->unit
|
||||||
} jlSerialDeviceE;
|
} jlSerialDeviceE;
|
||||||
|
|
||||||
|
|
@ -64,7 +93,19 @@ typedef struct {
|
||||||
|
|
||||||
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
||||||
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
||||||
the IIgs modem port, DOS COM1, the ST Modem port or Amiga unit 0.
|
the IIgs modem port, DOS COM1, the ST Modem port, Amiga unit 0 or the X68000's
|
||||||
|
RS-232C port.
|
||||||
|
|
||||||
|
On the X68000 the line is set by the RS-232C routines in charge. With
|
||||||
|
Human68k's RS-232C driver, RSDRV.SYS, loaded (the stock Human68k 3.02 system
|
||||||
|
disk's CONFIG.SYS loads it, and so do JoeyLib's own X68000 boot disks), the
|
||||||
|
port runs at 75 to 38400 bps and `JL_SERIAL_FLOW_RTSCTS` makes transmit wait
|
||||||
|
for CTS; RTS stays up, since JoeyLib's receive interrupt, not the driver's,
|
||||||
|
fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
||||||
|
usual tolerance of a UART at the far end. Without RSDRV.SYS the ROM's
|
||||||
|
routines allow 75 to 9600 bps: a faster request clamps to 9600, and
|
||||||
|
`JL_SERIAL_FLOW_RTSCTS` falls back to no handshaking. JoeyLib checks for the
|
||||||
|
driver at every `jlSerialOpen`.
|
||||||
|
|
||||||
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
||||||
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
||||||
|
|
@ -96,7 +137,37 @@ platform has no serial support -- and then every other call no-ops.
|
||||||
void jlSerialClose(void);
|
void jlSerialClose(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Closes the port and releases its resources. Safe when not open.
|
Closes the port and releases its resources. Safe when not open. A port still
|
||||||
|
open when the program exits is closed then: the first successful
|
||||||
|
`jlSerialOpen` registers an `atexit` handler, because on DOS, the X68000 and
|
||||||
|
the IIgs the port's receive interrupt vector points into the program until it
|
||||||
|
is closed.
|
||||||
|
|
||||||
|
Closing does *not* hang up: `jlSerialClose` leaves DTR exactly as it is, so
|
||||||
|
a modem call or a BBS caller survives the program handing the line back: a
|
||||||
|
door returning to its BBS, or a terminal program exiting while the call stays
|
||||||
|
up. RTS is not part of that promise; on the Amiga, `serial.device` puts back
|
||||||
|
whatever RTS was before the open.
|
||||||
|
|
||||||
|
To hang up, drop DTR first: call `jlSerialSetDtr(false)`, wait longer than
|
||||||
|
the modem needs to notice (a Hayes modem's S25 setting, 50 ms by default),
|
||||||
|
then close. The line stays down.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
Two ports cannot keep the line perfectly steady, though neither gap is long
|
||||||
|
enough to hang up a modem (a Hayes modem ignores DTR drops shorter than its
|
||||||
|
S25 setting, 50 ms by default):
|
||||||
|
|
||||||
|
* On the Amiga, when another program had the port open first, the last close
|
||||||
|
can drop DTR (`serial.device` puts back the lines as that program found
|
||||||
|
them), and JoeyLib raises it again straight away: the line is off for under
|
||||||
|
a millisecond. Otherwise JoeyLib raises DTR before its own open, so the
|
||||||
|
close keeps it up with no gap.
|
||||||
|
* On the IIgs, opening a port resets its channel, which drops DTR briefly
|
||||||
|
even if it was already up: about a quarter of a millisecond on the modem
|
||||||
|
and printer ports, about a millisecond on a Super Serial Card.
|
||||||
|
====
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -107,7 +178,9 @@ Closes the port and releases its resources. Safe when not open.
|
||||||
void jlSerialPoll(void);
|
void jlSerialPoll(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Pumps the receive and transmit buffers. Call once per game frame.
|
Pumps the receive and transmit buffers. Call once per game frame. Where an
|
||||||
|
interrupt fills the ring this does next to nothing; it is the drain for a port
|
||||||
|
that cannot interrupt.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -188,6 +261,47 @@ Discards any buffered received bytes.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
=== jlSerialSetDtr
|
||||||
|
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
void jlSerialSetDtr(bool on);
|
||||||
|
----
|
||||||
|
|
||||||
|
Raises or drops DTR. `jlSerialOpen` raises DTR and `jlSerialClose` leaves it
|
||||||
|
as it is, so this is how a program hangs up:
|
||||||
|
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
jlSerialSetDtr(false); // hang up
|
||||||
|
// ...wait longer than the modem's DTR-drop detection time (Hayes S25,
|
||||||
|
// 50 ms by default)...
|
||||||
|
jlSerialClose(); // DTR stays down
|
||||||
|
----
|
||||||
|
|
||||||
|
[cols="1,1,4", options="header"]
|
||||||
|
|===
|
||||||
|
|Parameter |Type |Description
|
||||||
|
|`on` |`bool` |`true` raises DTR, `false` drops it.
|
||||||
|
|===
|
||||||
|
|
||||||
|
*Returns:* nothing. A closed port no-ops, so this can be driven
|
||||||
|
unconditionally.
|
||||||
|
|
||||||
|
Two ports tie DTR to more than the line:
|
||||||
|
|
||||||
|
* On an IIgs Super Serial Card the 6551 has no DTR that is only a line:
|
||||||
|
dropping DTR also stops the card's receiver until DTR is raised again.
|
||||||
|
* On the X68000 the line setup in the ROM and in RSDRV.SYS raises DTR
|
||||||
|
whenever it runs, so the next `jlSerialOpen` -- or another program setting
|
||||||
|
the line -- raises it again.
|
||||||
|
|
||||||
|
Closing after a hang-up keeps DTR down, but on two ports it rises briefly on
|
||||||
|
the way: on the Amiga for under a millisecond (`serial.device` puts back the
|
||||||
|
lines as they were at its open) and on the X68000 for about 0.2 ms (close puts
|
||||||
|
back the line settings, which raises DTR), before JoeyLib drops it again. A
|
||||||
|
modem that has hung up ignores that.
|
||||||
|
|
||||||
=== jlSerialSetRts
|
=== jlSerialSetRts
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,12 @@ $ python3 tools/assetbake/assetbake.py --type tile --target atarist \
|
||||||
----
|
----
|
||||||
|
|
||||||
Tile banks are *per-target*, because the 8x8 block layout is the platform's
|
Tile banks are *per-target*, because the 8x8 block layout is the platform's
|
||||||
native pixel format -- plane-major on Amiga, row-major-with-planes-per-row on
|
native pixel format -- plane-major on Amiga and X68000, row-major-with-planes-
|
||||||
ST, chunky 4bpp on DOS and IIgs. You must bake one `.tbk` per platform, and the
|
per-row on ST, chunky 4bpp on DOS and IIgs. You must bake one `.tbk` per platform, and the
|
||||||
loader refuses a file baked for a different target rather than drawing garbage.
|
loader refuses a file baked for a different target rather than drawing garbage.
|
||||||
|
|
||||||
`--target` is required for tiles, and accepts `amiga`, `atarist`, `dos` or
|
`--target` is required for tiles, and accepts `amiga`, `atarist`, `dos`,
|
||||||
`iigs`.
|
`iigs` or `x68000`.
|
||||||
|
|
||||||
==== Baking Sprites
|
==== Baking Sprites
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ such as FS-UAE without a Kickstart.
|
||||||
|
|
||||||
| `import PATH...`
|
| `import PATH...`
|
||||||
| Installs the files that are not downloaded (see <<Firmware that is not
|
| Installs the files that are not downloaded (see <<Firmware that is not
|
||||||
downloaded>>) from files, folders or `.zip` archives, matched by checksum.
|
downloaded>>) from files, folders, `.zip` archives or Amiga `.adf` disk
|
||||||
|
images, matched by checksum.
|
||||||
|
|
||||||
| `bundle FILE`
|
| `bundle FILE`
|
||||||
| Packs those files, as present on this machine, into `FILE` (a `.tar.gz`)
|
| Packs those files, as present on this machine, into `FILE` (a `.tar.gz`)
|
||||||
|
|
@ -392,18 +393,22 @@ toolchain.
|
||||||
|
|
||||||
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
||||||
the Apple IIgs ROMs, GS/OS 6.0.4 and the CFFA2 card's firmware, and Sharp's own
|
the Apple IIgs ROMs, GS/OS 6.0.4 and the CFFA2 card's firmware, and Sharp's own
|
||||||
free release of the X68000 IPL-ROM and Human68k 3.02. Three files are not,
|
free release of the X68000 IPL-ROM and Human68k 3.02. Four files are not,
|
||||||
because they were never released for free: the Amiga Kickstart 3.1 ROM, the
|
because they were never released for free: the Amiga Kickstart 3.1 ROM, the
|
||||||
X68000 CG (font) ROM, and the memory image of the original Space Taxi that its
|
Amiga `serial.device` from Workbench 3.1 (Kickstart 3.1 has none in ROM, and
|
||||||
levels are extracted from. Supply your copies once:
|
a program cannot open the serial port without it), the X68000 CG (font) ROM,
|
||||||
|
and the memory image of the original Space Taxi that its levels are extracted
|
||||||
|
from. Supply your copies once:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ ./install.sh import ~/my-roms
|
$ ./install.sh import ~/my-roms
|
||||||
----
|
----
|
||||||
|
|
||||||
`import` takes files, folders and `.zip` archives, identifies each file by its
|
`import` takes files, folders, `.zip` archives and Amiga `.adf` disk images
|
||||||
checksum (the list is `installer/files.sha1`) and puts it in place.
|
(point it at your Workbench 3.1 disk for `serial.device`), identifies each
|
||||||
|
file by its checksum (the list is `installer/files.sha1`) and puts it in
|
||||||
|
place.
|
||||||
|
|
||||||
A project that hands its setup to testers can host these files instead:
|
A project that hands its setup to testers can host these files instead:
|
||||||
`./install.sh bundle joeylib-private.tar.gz` packs the ones present on
|
`./install.sh bundle joeylib-private.tar.gz` packs the ones present on
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,24 @@
|
||||||
#define COLOR_LINK_OPEN 6
|
#define COLOR_LINK_OPEN 6
|
||||||
#define COLOR_LINK_SHUT 7
|
#define COLOR_LINK_SHUT 7
|
||||||
|
|
||||||
|
// The line, overridable at build time (-DSERIAL_BAUD=19200u ...) so an emulator
|
||||||
|
// gate can run the example on another line (scripts/verify-x68000-serial.sh,
|
||||||
|
// X68K_SERIAL_LINE).
|
||||||
|
#ifndef SERIAL_BAUD
|
||||||
#define SERIAL_BAUD 9600u
|
#define SERIAL_BAUD 9600u
|
||||||
|
#endif
|
||||||
|
#ifndef SERIAL_DATABITS
|
||||||
#define SERIAL_DATABITS 8u
|
#define SERIAL_DATABITS 8u
|
||||||
|
#endif
|
||||||
|
#ifndef SERIAL_STOPBITS
|
||||||
#define SERIAL_STOPBITS 1u
|
#define SERIAL_STOPBITS 1u
|
||||||
|
#endif
|
||||||
|
#ifndef SERIAL_PARITY
|
||||||
|
#define SERIAL_PARITY JL_SERIAL_PARITY_NONE
|
||||||
|
#endif
|
||||||
|
#ifndef SERIAL_FLOW
|
||||||
|
#define SERIAL_FLOW JL_SERIAL_FLOW_NONE
|
||||||
|
#endif
|
||||||
|
|
||||||
#define RX_CHUNK 32
|
#define RX_CHUNK 32
|
||||||
|
|
||||||
|
|
@ -184,8 +199,8 @@ int main(void) {
|
||||||
serialCfg.baud = SERIAL_BAUD;
|
serialCfg.baud = SERIAL_BAUD;
|
||||||
serialCfg.dataBits = SERIAL_DATABITS;
|
serialCfg.dataBits = SERIAL_DATABITS;
|
||||||
serialCfg.stopBits = SERIAL_STOPBITS;
|
serialCfg.stopBits = SERIAL_STOPBITS;
|
||||||
serialCfg.parity = JL_SERIAL_PARITY_NONE;
|
serialCfg.parity = SERIAL_PARITY;
|
||||||
serialCfg.flow = JL_SERIAL_FLOW_NONE;
|
serialCfg.flow = SERIAL_FLOW;
|
||||||
serialCfg.unit = 0u;
|
serialCfg.unit = 0u;
|
||||||
linkOpen = jlSerialOpen(JL_SERIAL_DEFAULT, &serialCfg);
|
linkOpen = jlSerialOpen(JL_SERIAL_DEFAULT, &serialCfg);
|
||||||
if (linkOpen) {
|
if (linkOpen) {
|
||||||
|
|
|
||||||
408
examples/serloss/serloss.c
Normal file
408
examples/serloss/serloss.c
Normal file
|
|
@ -0,0 +1,408 @@
|
||||||
|
// Does a serial receiver survive the longest deaf window a JoeyLib program has?
|
||||||
|
//
|
||||||
|
// The host peer (scripts/verify-iigs-serial-loss.sh) waits for "READY" from
|
||||||
|
// this program, then streams SERLOSS_BYTES bytes whose value is the low byte of
|
||||||
|
// their index, paced by the emulated line at 9600 baud. Meanwhile every loop
|
||||||
|
// iteration dirties the whole stage and presents it -- a full-screen present is
|
||||||
|
// the longest stretch a program spends away from its own code -- and then reads
|
||||||
|
// whatever the port buffered, calling jlSerialPoll once per frame as the API
|
||||||
|
// asks. A receiver that fills its ring from an interrupt loses nothing; one that
|
||||||
|
// is drained only when the program gets around to it loses the bytes that
|
||||||
|
// arrive while the hardware FIFO is full.
|
||||||
|
//
|
||||||
|
// Build variants (the harness passes these with -D):
|
||||||
|
// SERLOSS_SLOT=n open a Super Serial Card in slot n instead of the modem port
|
||||||
|
// SERLOSS_CLEAR dirty the stage with jlSurfaceClear rather than jlFillRect
|
||||||
|
// SERLOSS_IDLE no drawing at all: only the interrupts the system itself takes
|
||||||
|
// SERLOSS_BAUD=n line rate (default 9600; the harness sets the peer to match)
|
||||||
|
// SERLOSS_JOY also poll the input (joystick included) once per frame and
|
||||||
|
// report the spread of the calibrated stick readings; the
|
||||||
|
// harness holds the paddles still (SERLOSS_PADDLE), so any
|
||||||
|
// spread is read jitter
|
||||||
|
// SERLOSS_SFX also keep a sound effect streaming and retrigger a one-shot
|
||||||
|
// effect once per frame, so NinjaTrackerPlus's entry calls and
|
||||||
|
// its DOC refill interrupt run throughout the receive
|
||||||
|
// SERLOSS_LOOSE drop the serial add-on's hold on the tight interrupt-latency
|
||||||
|
// bound once the port is open: the negative control, which
|
||||||
|
// must lose bytes wherever a masked stretch outlasts the FIFO
|
||||||
|
//
|
||||||
|
// Results go to a mailbox in the unused tail of the SHR SCB page, which the
|
||||||
|
// present never writes and the harness reads straight out of emulated memory.
|
||||||
|
|
||||||
|
#include <joey/joey.h>
|
||||||
|
#include <joey/serial.h>
|
||||||
|
|
||||||
|
#define MB_BASE ((volatile uint8_t *)0x00E19DD0L)
|
||||||
|
#define MB_SIG_0 0
|
||||||
|
#define MB_SIG_1 1
|
||||||
|
#define MB_PHASE 2
|
||||||
|
#define MB_RECEIVED 4
|
||||||
|
#define MB_IN_SEQUENCE 6
|
||||||
|
#define MB_BREAKS 8
|
||||||
|
#define MB_PRESENTS 10
|
||||||
|
#define MB_TICKS 12
|
||||||
|
#define MB_OVERRUNS 14
|
||||||
|
#define MB_DROPPED 16
|
||||||
|
#define MB_SUM 18
|
||||||
|
#define MB_NOW 20
|
||||||
|
#define MB_FILL_TICKS 22
|
||||||
|
#define MB_PRESENT_TICKS 24
|
||||||
|
#define MB_JOY_POLLS 26
|
||||||
|
#define MB_JOY_MIN_X 28 // stick readings, biased by MB_JOY_BIAS
|
||||||
|
#define MB_JOY_MAX_X 29
|
||||||
|
#define MB_JOY_MIN_Y 30
|
||||||
|
#define MB_JOY_MAX_Y 31
|
||||||
|
#define MB_SFX_SHOTS 32
|
||||||
|
#define MB_SIG_VALUE_0 0xA5u
|
||||||
|
#define MB_SIG_VALUE_1 0x5Au
|
||||||
|
#define MB_JOY_BIAS 128
|
||||||
|
|
||||||
|
#define PHASE_INIT 1u
|
||||||
|
#define PHASE_OPEN 2u
|
||||||
|
#define PHASE_READY 3u
|
||||||
|
#define PHASE_RECEIVING 4u
|
||||||
|
#define PHASE_DONE 5u
|
||||||
|
#define PHASE_FAILED 0xEEu
|
||||||
|
|
||||||
|
#ifndef SERLOSS_BAUD
|
||||||
|
#define SERLOSS_BAUD 9600u
|
||||||
|
#endif
|
||||||
|
#define SERLOSS_BYTES 4096u
|
||||||
|
#define IDLE_TICKS 120u // done once streaming has been silent for 2 s
|
||||||
|
#define START_TICKS 3600u // give up if nothing arrives in 60 s
|
||||||
|
#define READ_CHUNK 64u
|
||||||
|
#define BENCH_FRAMES 60u // fills (+ presents) timed before the port opens
|
||||||
|
#define SCREEN_W 320
|
||||||
|
#define SCREEN_H 200
|
||||||
|
|
||||||
|
// SERLOSS_SFX: a streamed effect on one slot plus a one-shot retriggered once
|
||||||
|
// per frame on another. The stream rate sets how often the replayer's DOC
|
||||||
|
// interrupt refills it (every 256 samples). The stream is raw (DOC-ready,
|
||||||
|
// 0x00-free bytes) and filled by jlMemCopy, as Space Taxi's speech is, so
|
||||||
|
// producing it costs the loop next to nothing.
|
||||||
|
#define SFX_STREAM_SLOT 4u
|
||||||
|
#define SFX_STREAM_RATE 22050u
|
||||||
|
#define SFX_SHOT_SLOT 3u
|
||||||
|
#define SFX_SHOT_RATE 11025u
|
||||||
|
#define SFX_SHOT_BYTES 2048u
|
||||||
|
#define SFX_SHOT_PERIOD 32u // one-shot square wave period, in samples
|
||||||
|
#define SFX_LEVEL 96 // one-shot square amplitude
|
||||||
|
#define SFX_WAVE_BYTES 256u // one period of the stream's sawtooth
|
||||||
|
|
||||||
|
// Slot I/O select: bit n routes slot n's $Cn00 space and I/O to the card rather
|
||||||
|
// than the built-in port (slot 2 defaults to the internal modem port, which
|
||||||
|
// shadows a card's 6551 at $C0A8).
|
||||||
|
#define SLTROMSEL ((volatile uint8_t *)0x00C02DL)
|
||||||
|
|
||||||
|
extern uint16_t gIigsRxOverruns;
|
||||||
|
extern uint16_t gIigsRxDropped;
|
||||||
|
#ifdef SERLOSS_LOOSE
|
||||||
|
extern void iigsIrqLatencyRelax(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
typedef struct {
|
||||||
|
uint16_t polls;
|
||||||
|
int8_t minX;
|
||||||
|
int8_t maxX;
|
||||||
|
int8_t minY;
|
||||||
|
int8_t maxY;
|
||||||
|
} JoySpreadT;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
static int8_t gSfxShot[SFX_SHOT_BYTES];
|
||||||
|
static uint8_t gSfxWave[SFX_WAVE_BYTES];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static uint16_t benchLoop(jlSurfaceT *stage, bool present);
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
static void joyPoll(JoySpreadT *spread);
|
||||||
|
#endif
|
||||||
|
static void mailbox16(uint16_t slot, uint16_t value);
|
||||||
|
static void publish(uint16_t received, uint16_t inSequence, uint16_t breaks, uint16_t presents, uint16_t ticks, uint16_t sum, uint16_t now);
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
static bool sfxStart(void);
|
||||||
|
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Ticks for BENCH_FRAMES full-stage fills, each followed by a present if asked: the difference
|
||||||
|
// between the two runs is what a full-screen present costs.
|
||||||
|
static uint16_t benchLoop(jlSurfaceT *stage, bool present) {
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t frame;
|
||||||
|
|
||||||
|
start = jlFrameCount();
|
||||||
|
for (frame = 0u; frame < BENCH_FRAMES; frame++) {
|
||||||
|
jlFillRect(stage, 0, 0, SCREEN_W, SCREEN_H, (uint8_t)(frame & 0x0Fu));
|
||||||
|
if (present) {
|
||||||
|
jlStagePresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (uint16_t)(jlFrameCount() - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
// One input poll (the joystick read is the long part), folded into the spread and published. The
|
||||||
|
// stick was calibrated before the loop, so a still stick reads 0 and anything else is jitter.
|
||||||
|
static void joyPoll(JoySpreadT *spread) {
|
||||||
|
int8_t x;
|
||||||
|
int8_t y;
|
||||||
|
|
||||||
|
jlInputPoll();
|
||||||
|
x = jlJoystickX(JOYSTICK_0);
|
||||||
|
y = jlJoystickY(JOYSTICK_0);
|
||||||
|
if (spread->polls == 0u || x < spread->minX) {
|
||||||
|
spread->minX = x;
|
||||||
|
}
|
||||||
|
if (spread->polls == 0u || x > spread->maxX) {
|
||||||
|
spread->maxX = x;
|
||||||
|
}
|
||||||
|
if (spread->polls == 0u || y < spread->minY) {
|
||||||
|
spread->minY = y;
|
||||||
|
}
|
||||||
|
if (spread->polls == 0u || y > spread->maxY) {
|
||||||
|
spread->maxY = y;
|
||||||
|
}
|
||||||
|
spread->polls++;
|
||||||
|
mailbox16(MB_JOY_POLLS, spread->polls);
|
||||||
|
MB_BASE[MB_JOY_MIN_X] = (uint8_t)(spread->minX + MB_JOY_BIAS);
|
||||||
|
MB_BASE[MB_JOY_MAX_X] = (uint8_t)(spread->maxX + MB_JOY_BIAS);
|
||||||
|
MB_BASE[MB_JOY_MIN_Y] = (uint8_t)(spread->minY + MB_JOY_BIAS);
|
||||||
|
MB_BASE[MB_JOY_MAX_Y] = (uint8_t)(spread->maxY + MB_JOY_BIAS);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static void mailbox16(uint16_t slot, uint16_t value) {
|
||||||
|
MB_BASE[slot] = (uint8_t)(value & 0xFFu);
|
||||||
|
MB_BASE[slot + 1u] = (uint8_t)(value >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((noinline)) static void publish(uint16_t received, uint16_t inSequence, uint16_t breaks, uint16_t presents, uint16_t ticks, uint16_t sum, uint16_t now) {
|
||||||
|
mailbox16(MB_RECEIVED, received);
|
||||||
|
mailbox16(MB_IN_SEQUENCE, inSequence);
|
||||||
|
mailbox16(MB_BREAKS, breaks);
|
||||||
|
mailbox16(MB_PRESENTS, presents);
|
||||||
|
mailbox16(MB_TICKS, ticks);
|
||||||
|
mailbox16(MB_OVERRUNS, gIigsRxOverruns);
|
||||||
|
mailbox16(MB_DROPPED, gIigsRxDropped);
|
||||||
|
mailbox16(MB_SUM, sum);
|
||||||
|
mailbox16(MB_NOW, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
// Bring up audio, build the one-shot's square wave and the stream's sawtooth, and start the
|
||||||
|
// endless stream.
|
||||||
|
static bool sfxStart(void) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
if (!jlAudioInit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0u; i < SFX_SHOT_BYTES; i++) {
|
||||||
|
gSfxShot[i] = (int8_t)(((i / (SFX_SHOT_PERIOD / 2u)) & 1u) != 0u ? SFX_LEVEL : -SFX_LEVEL);
|
||||||
|
}
|
||||||
|
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 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);
|
||||||
|
jlAudioPlaySfxStreamRaw((uint8_t)SFX_STREAM_SLOT, sfxStreamFill, (void *)0, SFX_STREAM_RATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Endless sawtooth, whole periods: never returns 0, so the stream runs until the program ends.
|
||||||
|
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count) {
|
||||||
|
uint32_t done;
|
||||||
|
|
||||||
|
(void)ctx;
|
||||||
|
done = 0u;
|
||||||
|
while (count - done >= SFX_WAVE_BYTES) {
|
||||||
|
jlMemCopy(dst + done, gSfxWave, SFX_WAVE_BYTES);
|
||||||
|
done += SFX_WAVE_BYTES;
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
static const char ready[] = "READY\r\n";
|
||||||
|
jlConfigT config;
|
||||||
|
jlSerialConfigT cfg;
|
||||||
|
jlSurfaceT *stage;
|
||||||
|
uint8_t buf[READ_CHUNK];
|
||||||
|
uint16_t received;
|
||||||
|
uint16_t inSequence;
|
||||||
|
uint16_t breaks;
|
||||||
|
uint16_t presents;
|
||||||
|
uint16_t sum;
|
||||||
|
uint16_t lastTick;
|
||||||
|
uint16_t firstTick;
|
||||||
|
uint16_t now;
|
||||||
|
uint16_t got;
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t n;
|
||||||
|
uint8_t expect;
|
||||||
|
uint8_t color;
|
||||||
|
bool started;
|
||||||
|
#if defined(SERLOSS_JOY) || defined(SERLOSS_SFX)
|
||||||
|
uint16_t frameTick;
|
||||||
|
#endif
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
JoySpreadT spread;
|
||||||
|
#endif
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
uint16_t shots;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
config.codegenBytes = 0u;
|
||||||
|
config.audioBytes = 0u;
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
||||||
|
for (;;) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Signed only after jlInit, which rewrites the SCB page this mailbox shares.
|
||||||
|
MB_BASE[MB_SIG_0] = MB_SIG_VALUE_0;
|
||||||
|
MB_BASE[MB_SIG_1] = MB_SIG_VALUE_1;
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_INIT;
|
||||||
|
mailbox16(MB_JOY_POLLS, 0u);
|
||||||
|
mailbox16(MB_SFX_SHOTS, 0u);
|
||||||
|
stage = jlStageGet();
|
||||||
|
mailbox16(MB_FILL_TICKS, benchLoop(stage, false));
|
||||||
|
mailbox16(MB_PRESENT_TICKS, benchLoop(stage, true));
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
if (!sfxStart()) {
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
||||||
|
for (;;) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shots = 0u;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cfg.baud = SERLOSS_BAUD;
|
||||||
|
cfg.dataBits = 8u;
|
||||||
|
cfg.stopBits = 1u;
|
||||||
|
cfg.parity = JL_SERIAL_PARITY_NONE;
|
||||||
|
cfg.flow = JL_SERIAL_FLOW_NONE;
|
||||||
|
#ifdef SERLOSS_SLOT
|
||||||
|
cfg.unit = SERLOSS_SLOT;
|
||||||
|
*SLTROMSEL = (uint8_t)(*SLTROMSEL | (uint8_t)(1u << SERLOSS_SLOT));
|
||||||
|
started = jlSerialOpen(JL_SERIAL_SLOT, &cfg);
|
||||||
|
#else
|
||||||
|
cfg.unit = 0u;
|
||||||
|
started = jlSerialOpen(JL_SERIAL_MODEM, &cfg);
|
||||||
|
#endif
|
||||||
|
if (!started) {
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
||||||
|
for (;;) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef SERLOSS_LOOSE
|
||||||
|
iigsIrqLatencyRelax();
|
||||||
|
#endif
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
// Calibrate on the first poll, in the latency mode the rest will run in, so a still stick
|
||||||
|
// reads 0 from then on.
|
||||||
|
jlJoystickReset(JOYSTICK_0, 0u);
|
||||||
|
spread.polls = 0u;
|
||||||
|
joyPoll(&spread);
|
||||||
|
#endif
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_OPEN;
|
||||||
|
(void)jlSerialWrite((const uint8_t *)ready, (uint16_t)(sizeof(ready) - 1u));
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_READY;
|
||||||
|
|
||||||
|
received = 0u;
|
||||||
|
inSequence = 0u;
|
||||||
|
breaks = 0u;
|
||||||
|
presents = 0u;
|
||||||
|
sum = 0u;
|
||||||
|
expect = 0u;
|
||||||
|
color = 1u;
|
||||||
|
started = false;
|
||||||
|
firstTick = jlFrameCount();
|
||||||
|
lastTick = firstTick;
|
||||||
|
now = firstTick;
|
||||||
|
#if defined(SERLOSS_JOY) || defined(SERLOSS_SFX)
|
||||||
|
frameTick = firstTick;
|
||||||
|
#endif
|
||||||
|
for (;;) {
|
||||||
|
// Published at the top so every exit below is a bare compare-and-branch: llvm816 has
|
||||||
|
// been seen hoisting this function's byte splits between a cmp and its branch,
|
||||||
|
// clobbering the flags the branch reads (see the report).
|
||||||
|
publish(received, inSequence, breaks, presents, (uint16_t)(lastTick - firstTick), sum, now);
|
||||||
|
if (received >= SERLOSS_BYTES) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if defined(SERLOSS_CLEAR)
|
||||||
|
jlSurfaceClear(stage, color);
|
||||||
|
jlStagePresent();
|
||||||
|
#elif !defined(SERLOSS_IDLE)
|
||||||
|
jlFillRect(stage, 0, 0, SCREEN_W, SCREEN_H, color);
|
||||||
|
jlStagePresent();
|
||||||
|
#endif
|
||||||
|
presents++;
|
||||||
|
color = (uint8_t)((color & 0x0Fu) + 1u);
|
||||||
|
#if defined(SERLOSS_JOY) || defined(SERLOSS_SFX)
|
||||||
|
// Once per frame, like a game: an idle loop would otherwise run these back to back.
|
||||||
|
if (jlFrameCount() != frameTick) {
|
||||||
|
frameTick = jlFrameCount();
|
||||||
|
#ifdef SERLOSS_JOY
|
||||||
|
joyPoll(&spread);
|
||||||
|
#endif
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
jlAudioPlaySfx((uint8_t)SFX_SHOT_SLOT, (const uint8_t *)gSfxShot, SFX_SHOT_BYTES, SFX_SHOT_RATE);
|
||||||
|
jlAudioFrameTick();
|
||||||
|
shots++;
|
||||||
|
mailbox16(MB_SFX_SHOTS, shots);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
jlSerialPoll();
|
||||||
|
got = 0u;
|
||||||
|
n = jlSerialRead(buf, READ_CHUNK);
|
||||||
|
while (n > 0u) {
|
||||||
|
for (i = 0u; i < n; i++) {
|
||||||
|
if (buf[i] == expect) {
|
||||||
|
inSequence++;
|
||||||
|
} else {
|
||||||
|
breaks++;
|
||||||
|
}
|
||||||
|
expect = (uint8_t)(buf[i] + 1u);
|
||||||
|
sum = (uint16_t)(sum + buf[i]);
|
||||||
|
}
|
||||||
|
got = (uint16_t)(got + n);
|
||||||
|
n = jlSerialRead(buf, READ_CHUNK);
|
||||||
|
}
|
||||||
|
received = (uint16_t)(received + got);
|
||||||
|
now = jlFrameCount();
|
||||||
|
if (got > 0u) {
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
firstTick = now;
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_RECEIVING;
|
||||||
|
}
|
||||||
|
lastTick = now;
|
||||||
|
}
|
||||||
|
if (started) {
|
||||||
|
if ((uint16_t)(now - lastTick) > IDLE_TICKS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if ((uint16_t)(now - firstTick) > START_TICKS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publish(received, inSequence, breaks, presents, (uint16_t)(lastTick - firstTick), sum, now);
|
||||||
|
MB_BASE[MB_PHASE] = PHASE_DONE;
|
||||||
|
for (;;) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,354 +0,0 @@
|
||||||
; stMarshalIigs.s - Space Taxi's stSimMarshal for the Apple IIgs.
|
|
||||||
;
|
|
||||||
; GAS syntax for the llvm-mos w65816 toolchain (see src/iigs/peislam.s
|
|
||||||
; for the conventions). The C version copies eight sprites' shadow
|
|
||||||
; registers into the displayed frame through far pointers, which the
|
|
||||||
; w65816 backend turns into ~5000 cycles of pointer re-materialisation
|
|
||||||
; per tick (5% of a ride frame). This is the same copy as straight-line
|
|
||||||
; [dp],y moves: ~700 cycles.
|
|
||||||
;
|
|
||||||
; void stSimMarshalIigs(const StMarshalArgsT *args)
|
|
||||||
; args (A = low word, X = bank) points at { spr, frame, multi }:
|
|
||||||
; +0 const StSpriteT *spr (32-bit: low word, bank word)
|
|
||||||
; +4 StFrameT *frame
|
|
||||||
; +8 uint8_t multi
|
|
||||||
; StSpriteT (6 bytes): col, msb, row, enable, ptr, color.
|
|
||||||
; StFrameT: x[8] (u16, +0), y[8] (+16), ptr[8] (+24), color[8] (+32),
|
|
||||||
; enableMask (+40), multiMask (+41).
|
|
||||||
; stSim.c checks these layouts at compile time.
|
|
||||||
;
|
|
||||||
; Every store runs with M=16 and writes two bytes: the byte after each
|
|
||||||
; single-byte field gets a junk high byte that the NEXT store repairs,
|
|
||||||
; so the blocks run in order x, y, ptr, color and finish with the
|
|
||||||
; enable/multi word. The last colour store's junk lands on enableMask,
|
|
||||||
; which the final word overwrites.
|
|
||||||
;
|
|
||||||
; Direct-page $E0-$EF are the compiler's own per-function scratch
|
|
||||||
; (re-set before every use, never live across a call); $C0 is left alone.
|
|
||||||
.section .text.stSimMarshalIigs,"ax"
|
|
||||||
.globl stSimMarshalIigs
|
|
||||||
stSimMarshalIigs:
|
|
||||||
php
|
|
||||||
rep #0x30
|
|
||||||
.a16
|
|
||||||
.i16
|
|
||||||
sta 0xe0 ; args low
|
|
||||||
txa
|
|
||||||
sta 0xe2 ; args bank
|
|
||||||
ldy #0
|
|
||||||
lda [0xe0],y
|
|
||||||
sta 0xe4 ; spr low
|
|
||||||
ldy #2
|
|
||||||
lda [0xe0],y
|
|
||||||
sta 0xe6 ; spr bank
|
|
||||||
ldy #4
|
|
||||||
lda [0xe0],y
|
|
||||||
sta 0xe8 ; frame low
|
|
||||||
ldy #6
|
|
||||||
lda [0xe0],y
|
|
||||||
sta 0xea ; frame bank
|
|
||||||
ldy #8
|
|
||||||
lda [0xe0],y
|
|
||||||
and #0xff
|
|
||||||
sta 0xec ; multi
|
|
||||||
stz 0xee ; enable mask accumulator
|
|
||||||
; sprite 0: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #0
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX0clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX0st
|
|
||||||
mX0clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX0st:
|
|
||||||
ldy #0
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #3
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE0off
|
|
||||||
lda 0xee
|
|
||||||
ora #1
|
|
||||||
sta 0xee
|
|
||||||
mE0off:
|
|
||||||
; sprite 1: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #6
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX1clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX1st
|
|
||||||
mX1clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX1st:
|
|
||||||
ldy #2
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #9
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE1off
|
|
||||||
lda 0xee
|
|
||||||
ora #2
|
|
||||||
sta 0xee
|
|
||||||
mE1off:
|
|
||||||
; sprite 2: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #12
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX2clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX2st
|
|
||||||
mX2clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX2st:
|
|
||||||
ldy #4
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #15
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE2off
|
|
||||||
lda 0xee
|
|
||||||
ora #4
|
|
||||||
sta 0xee
|
|
||||||
mE2off:
|
|
||||||
; sprite 3: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #18
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX3clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX3st
|
|
||||||
mX3clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX3st:
|
|
||||||
ldy #6
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #21
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE3off
|
|
||||||
lda 0xee
|
|
||||||
ora #8
|
|
||||||
sta 0xee
|
|
||||||
mE3off:
|
|
||||||
; sprite 4: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #24
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX4clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX4st
|
|
||||||
mX4clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX4st:
|
|
||||||
ldy #8
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #27
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE4off
|
|
||||||
lda 0xee
|
|
||||||
ora #16
|
|
||||||
sta 0xee
|
|
||||||
mE4off:
|
|
||||||
; sprite 5: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #30
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX5clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX5st
|
|
||||||
mX5clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX5st:
|
|
||||||
ldy #10
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #33
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE5off
|
|
||||||
lda 0xee
|
|
||||||
ora #32
|
|
||||||
sta 0xee
|
|
||||||
mE5off:
|
|
||||||
; sprite 6: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #36
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX6clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX6st
|
|
||||||
mX6clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX6st:
|
|
||||||
ldy #12
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #39
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE6off
|
|
||||||
lda 0xee
|
|
||||||
ora #64
|
|
||||||
sta 0xee
|
|
||||||
mE6off:
|
|
||||||
; sprite 7: x = (msb ? 0x100 : 0) | col; enable bit
|
|
||||||
ldy #42
|
|
||||||
lda [0xe4],y ; col | msb<<8
|
|
||||||
sta 0xf0
|
|
||||||
and #0xff00
|
|
||||||
beq mX7clr
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
ora #0x0100
|
|
||||||
bra mX7st
|
|
||||||
mX7clr:
|
|
||||||
lda 0xf0
|
|
||||||
and #0x00ff
|
|
||||||
mX7st:
|
|
||||||
ldy #14
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #45
|
|
||||||
lda [0xe4],y ; enable | ptr<<8
|
|
||||||
and #0xff
|
|
||||||
beq mE7off
|
|
||||||
lda 0xee
|
|
||||||
ora #128
|
|
||||||
sta 0xee
|
|
||||||
mE7off:
|
|
||||||
; y[] block (16-bit stores; the junk high byte is repaired by the next store)
|
|
||||||
ldy #2
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #16
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #8
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #17
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #14
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #18
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #20
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #19
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #26
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #20
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #32
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #21
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #38
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #22
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #44
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #23
|
|
||||||
sta [0xe8],y
|
|
||||||
; ptr[] block (16-bit stores; the junk high byte is repaired by the next store)
|
|
||||||
ldy #4
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #24
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #10
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #25
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #16
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #26
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #22
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #27
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #28
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #28
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #34
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #29
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #40
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #30
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #46
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #31
|
|
||||||
sta [0xe8],y
|
|
||||||
; color[] block (16-bit stores; the junk high byte is repaired by the next store)
|
|
||||||
ldy #5
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #32
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #11
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #33
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #17
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #34
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #23
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #35
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #29
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #36
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #35
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #37
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #41
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #38
|
|
||||||
sta [0xe8],y
|
|
||||||
ldy #47
|
|
||||||
lda [0xe4],y
|
|
||||||
ldy #39
|
|
||||||
sta [0xe8],y
|
|
||||||
; enableMask | multiMask<<8
|
|
||||||
lda 0xec
|
|
||||||
xba
|
|
||||||
and #0xff00
|
|
||||||
ora 0xee
|
|
||||||
ldy #40
|
|
||||||
sta [0xe8],y
|
|
||||||
plp
|
|
||||||
rtl
|
|
||||||
|
|
@ -1489,31 +1489,7 @@ void stSimMarkChar(StSimT *sim, uint8_t ch) {
|
||||||
|
|
||||||
// $4253 + $4293 -- snapshot the sprite shadows into the frame the VIC
|
// $4253 + $4293 -- snapshot the sprite shadows into the frame the VIC
|
||||||
// shows next. The title and level-intro loops flush the same way.
|
// shows next. The title and level-intro loops flush the same way.
|
||||||
#if defined(__W65816__)
|
|
||||||
// The IIgs copies the frame in asm (stMarshalIigs.s); it takes one far
|
|
||||||
// pointer to this block so no stack-argument convention is involved.
|
|
||||||
typedef struct {
|
|
||||||
const StSpriteT *spr;
|
|
||||||
StFrameT *frame;
|
|
||||||
uint8_t multi;
|
|
||||||
} StMarshalArgsT;
|
|
||||||
extern void stSimMarshalIigs(const StMarshalArgsT *args);
|
|
||||||
// The asm hardcodes these layouts; a mismatch fails the build here.
|
|
||||||
typedef char stMarshalCheckSprite[(sizeof(StSpriteT) == 6u && offsetof(StSpriteT, col) == 0u && offsetof(StSpriteT, msb) == 1u && offsetof(StSpriteT, row) == 2u && offsetof(StSpriteT, enable) == 3u && offsetof(StSpriteT, ptr) == 4u && offsetof(StSpriteT, color) == 5u) ? 1 : -1];
|
|
||||||
typedef char stMarshalCheckFrame[(offsetof(StFrameT, x) == 0u && offsetof(StFrameT, y) == 16u && offsetof(StFrameT, ptr) == 24u && offsetof(StFrameT, color) == 32u && offsetof(StFrameT, enableMask) == 40u && offsetof(StFrameT, multiMask) == 41u) ? 1 : -1];
|
|
||||||
typedef char stMarshalCheckArgs[(sizeof(void *) == 4u && offsetof(StMarshalArgsT, frame) == 4u && offsetof(StMarshalArgsT, multi) == 8u) ? 1 : -1];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
void stSimMarshal(StSimT *sim) {
|
void stSimMarshal(StSimT *sim) {
|
||||||
#if defined(__W65816__)
|
|
||||||
static StMarshalArgsT args;
|
|
||||||
|
|
||||||
args.spr = sim->spr;
|
|
||||||
args.frame = &sim->frame;
|
|
||||||
args.multi = sim->multiColorMask;
|
|
||||||
stSimMarshalIigs(&args);
|
|
||||||
#else
|
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
uint8_t enable = 0u;
|
uint8_t enable = 0u;
|
||||||
|
|
||||||
|
|
@ -1528,7 +1504,6 @@ void stSimMarshal(StSimT *sim) {
|
||||||
}
|
}
|
||||||
sim->frame.enableMask = enable;
|
sim->frame.enableMask = enable;
|
||||||
sim->frame.multiMask = sim->multiColorMask;
|
sim->frame.multiMask = sim->multiColorMask;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,8 @@
|
||||||
#define JL_HAS_SERIAL_WRITE
|
#define JL_HAS_SERIAL_WRITE
|
||||||
#define JL_HAS_SERIAL_AVAILABLE
|
#define JL_HAS_SERIAL_AVAILABLE
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
#define JL_HAS_SERIAL_SET_RTS
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// save files + disk space: delete (GS/OS Destroy), dir-ensure (GS/OS Create
|
// save files + disk space: delete (GS/OS Destroy), dir-ensure (GS/OS Create
|
||||||
|
|
@ -243,6 +245,8 @@
|
||||||
#define JL_HAS_SERIAL_WRITE
|
#define JL_HAS_SERIAL_WRITE
|
||||||
#define JL_HAS_SERIAL_AVAILABLE
|
#define JL_HAS_SERIAL_AVAILABLE
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
// save files + disk space (CreateDir/DeleteFile/Info via dos.library)
|
// save files + disk space (CreateDir/DeleteFile/Info via dos.library)
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
|
|
@ -313,6 +317,8 @@
|
||||||
#define JL_HAS_SERIAL_WRITE
|
#define JL_HAS_SERIAL_WRITE
|
||||||
#define JL_HAS_SERIAL_AVAILABLE
|
#define JL_HAS_SERIAL_AVAILABLE
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
// save files + disk space (POSIX mkdir/remove + GEMDOS Dfree)
|
// save files + disk space (POSIX mkdir/remove + GEMDOS Dfree)
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
|
|
@ -361,6 +367,8 @@
|
||||||
#define JL_HAS_SERIAL_WRITE
|
#define JL_HAS_SERIAL_WRITE
|
||||||
#define JL_HAS_SERIAL_AVAILABLE
|
#define JL_HAS_SERIAL_AVAILABLE
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
#define JL_HAS_SERIAL_SET_RTS
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// save files + disk space (POSIX mkdir/remove + int21h/36h disk-free)
|
// save files + disk space (POSIX mkdir/remove + int21h/36h disk-free)
|
||||||
|
|
@ -427,6 +435,8 @@
|
||||||
#define JL_HAS_SERIAL_READ
|
#define JL_HAS_SERIAL_READ
|
||||||
#define JL_HAS_SERIAL_WRITE
|
#define JL_HAS_SERIAL_WRITE
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
// Audio. CHIP side on the YM2151 (OPM): tone voices + noise, which is what
|
// Audio. CHIP side on the YM2151 (OPM): tone voices + noise, which is what
|
||||||
// the portable JYM1 tracker rides, with no CPU mixing.
|
// the portable JYM1 tracker rides, with no CPU mixing.
|
||||||
#define JL_HAS_AUDIO_INIT
|
#define JL_HAS_AUDIO_INIT
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,18 @@
|
||||||
// One byte-stream API spans every port; the per-port HAL hides the very
|
// One byte-stream API spans every port; the per-port HAL hides the very
|
||||||
// different hardware:
|
// different hardware:
|
||||||
// Apple IIgs -- direct Zilog 8530 SCC (modem/printer ports) or a 6551
|
// Apple IIgs -- direct Zilog 8530 SCC (modem/printer ports) or a 6551
|
||||||
// Super Serial Card in a slot; polled, drained on
|
// Super Serial Card in a slot; its own RX interrupt fills
|
||||||
// jlSerialPoll().
|
// the ring (a card needs its interrupt switch, SW2-6, on).
|
||||||
// DOS -- 8250/16550 UART on a COM port; interrupt-driven RX ring.
|
// DOS -- 8250/16550 UART on a COM port; interrupt-driven RX ring.
|
||||||
// Amiga -- serial.device (async exec IO); the OS buffers RX.
|
// Amiga -- serial.device (async exec IO); the OS buffers RX.
|
||||||
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
||||||
|
// X68000 -- SCC channel A: IOCS for transmit and line setup (75-9600
|
||||||
|
// bps, no RTS/CTS; 75-38400 bps and RTS/CTS with Human68k's
|
||||||
|
// RSDRV.SYS loaded); its own RX interrupt fills the ring.
|
||||||
//
|
//
|
||||||
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
||||||
// ring (a hardware IRQ on DOS, the OS on Amiga/ST, or jlSerialPoll() on the
|
// ring (a hardware IRQ on DOS, the X68000 and the IIgs, the OS on Amiga/ST);
|
||||||
// IIgs); jlSerialRead / jlSerialAvailable never block. Call jlSerialPoll()
|
// jlSerialRead / jlSerialAvailable never block. Call jlSerialPoll()
|
||||||
// once per game frame -- it is a no-op where the IRQ/OS already fills the
|
// once per game frame -- it is a no-op where the IRQ/OS already fills the
|
||||||
// ring, and the hardware drain where it does not, so calling it every frame is
|
// ring, and the hardware drain where it does not, so calling it every frame is
|
||||||
// always correct. Transmit is a bounded poll.
|
// always correct. Transmit is a bounded poll.
|
||||||
|
|
@ -30,8 +33,8 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Which physical interface to open. The IIgs is the oddball with three:
|
// Which physical interface to open. The IIgs is the oddball with three:
|
||||||
// JL_SERIAL_MODEM -- IIgs modem port (SCC channel A). Elsewhere == DEFAULT.
|
// JL_SERIAL_MODEM -- IIgs modem port (SCC channel B). Elsewhere == DEFAULT.
|
||||||
// JL_SERIAL_PRINTER -- IIgs printer port (SCC channel B). Only the IIgs has
|
// JL_SERIAL_PRINTER -- IIgs printer port (SCC channel A). Only the IIgs has
|
||||||
// a second built-in port; elsewhere == DEFAULT.
|
// a second built-in port; elsewhere == DEFAULT.
|
||||||
// JL_SERIAL_SLOT -- a card/port selected by jlSerialConfigT.unit: IIgs
|
// JL_SERIAL_SLOT -- a card/port selected by jlSerialConfigT.unit: IIgs
|
||||||
// Super Serial Card slot (1..7); DOS COM number (1..4);
|
// Super Serial Card slot (1..7); DOS COM number (1..4);
|
||||||
|
|
@ -75,12 +78,23 @@ typedef struct {
|
||||||
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
||||||
|
|
||||||
// Close the open port and release its resources. Safe to call when not open.
|
// Close the open port and release its resources. Safe to call when not open.
|
||||||
|
// A port still open when the program exits is closed then (the first
|
||||||
|
// successful jlSerialOpen registers an atexit handler): where the port's RX
|
||||||
|
// interrupt is taken over (DOS, X68000, IIgs), the vector points into the
|
||||||
|
// program until it is closed.
|
||||||
|
//
|
||||||
|
// Closing does NOT hang up: DTR is left exactly as it was, so a modem call or a
|
||||||
|
// BBS caller survives a program handing the line back -- a door returning to
|
||||||
|
// its BBS, a terminal exiting. RTS is not part of that promise (on the Amiga,
|
||||||
|
// serial.device puts back what it found at open). To hang up, drop DTR with
|
||||||
|
// jlSerialSetDtr(false), wait longer than the modem's DTR-drop detection time
|
||||||
|
// (Hayes S25: 50 ms by default), then close: the line stays down.
|
||||||
void jlSerialClose(void);
|
void jlSerialClose(void);
|
||||||
|
|
||||||
// Pump the RX/TX buffers. Call once per game frame. No-op where a hardware IRQ
|
// Pump the RX/TX buffers. Call once per game frame. Next to a no-op where a
|
||||||
// or the OS already services the port (DOS/Amiga/ST); drains the hardware FIFO
|
// hardware IRQ or the OS already services the port; drains the hardware into
|
||||||
// into the RX ring where it does not (IIgs). Calling it every frame is always
|
// the RX ring where nothing else does (an IIgs Super Serial Card whose
|
||||||
// correct.
|
// interrupt switch is off). Calling it every frame is always correct.
|
||||||
void jlSerialPoll(void);
|
void jlSerialPoll(void);
|
||||||
|
|
||||||
// Bytes waiting in the RX buffer, readable now without blocking.
|
// Bytes waiting in the RX buffer, readable now without blocking.
|
||||||
|
|
@ -101,6 +115,20 @@ int16_t jlSerialReadByte(void);
|
||||||
// Discard any buffered received bytes.
|
// Discard any buffered received bytes.
|
||||||
void jlSerialFlush(void);
|
void jlSerialFlush(void);
|
||||||
|
|
||||||
|
// RAISE OR DROP DTR -- how a program hangs up.
|
||||||
|
//
|
||||||
|
// jlSerialOpen raises DTR and jlSerialClose leaves it as it is, so this is the one call that drops
|
||||||
|
// it. To hang up a modem: jlSerialSetDtr(false), wait longer than the modem's DTR-drop detection
|
||||||
|
// time (Hayes S25: 50 ms by default), then jlSerialClose -- the line stays down. `on` = true raises
|
||||||
|
// it again.
|
||||||
|
//
|
||||||
|
// Two ports tie DTR to more than the line. An IIgs Super Serial Card's 6551 has no separate DTR
|
||||||
|
// bit: dropping DTR also stops the card's receiver until DTR is raised again. On the X68000 the
|
||||||
|
// line setup in the ROM and in RSDRV.SYS raises DTR whenever it runs, so the next jlSerialOpen
|
||||||
|
// (or anything else that sets the line) raises it again. A closed port no-ops, so a caller can
|
||||||
|
// drive it unconditionally.
|
||||||
|
void jlSerialSetDtr(bool on);
|
||||||
|
|
||||||
// ASSERT OR DROP RTS, for a caller that does its own receive-side flow control.
|
// ASSERT OR DROP RTS, for a caller that does its own receive-side flow control.
|
||||||
//
|
//
|
||||||
// JL_SERIAL_FLOW_RTSCTS asks the PORT to handshake; this is the other half - the application saying
|
// JL_SERIAL_FLOW_RTSCTS asks the PORT to handshake; this is the other half - the application saying
|
||||||
|
|
|
||||||
34
installer/components/amigaSerial.sh
Normal file
34
installer/components/amigaSerial.sh
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Amiga serial.device (37.4, from Workbench 3.1's Devs drawer). Kickstart 3.1
|
||||||
|
# on the A500 has no serial.device in ROM: AmigaOS loads it from DEVS:, so a
|
||||||
|
# JoeyLib program that opens the serial port needs it on the boot drive, and
|
||||||
|
# joey-run and the Amiga serial gate put it there. Cloanto still sells
|
||||||
|
# Workbench 3.1 (Amiga Forever), so it is never downloaded from a public
|
||||||
|
# source: it comes from the private bundle or `install.sh import`, which also
|
||||||
|
# finds it inside a Workbench .adf disk image.
|
||||||
|
|
||||||
|
compDescription="Amiga firmware: serial.device from Workbench 3.1 (private: bundle or import)"
|
||||||
|
compGroups="emulator:amiga"
|
||||||
|
compRequires=""
|
||||||
|
compHostDeps=""
|
||||||
|
compPrivateFile="toolchains/firmware/amiga/Devs/serial.device"
|
||||||
|
compPin="$(privateSha1For "$compPrivateFile")"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
privateEnsure "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compVerify() {
|
||||||
|
privatePresent "$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar AMIGA_SERIAL_DEVICE "$JOEY_REPO/$compPrivateFile"
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
# Apple IIgs firmware and system software for MAME:
|
# Apple IIgs firmware and system software for MAME:
|
||||||
# - the MAME apple2gs (ROM 03) and apple2gsr1 (ROM 01) ROM sets, from the
|
# - the MAME apple2gs (ROM 03) and apple2gsr1 (ROM 01) ROM sets, and a2ssc
|
||||||
# archive.org MAME 0.240 split set (the source llvm816 also uses)
|
# (the Super Serial Card firmware the serial gates' slot card needs), from
|
||||||
|
# the archive.org MAME 0.240 split set (the source llvm816 also uses)
|
||||||
# - the CFFA2 card firmware, freely redistributed by dreher.net, needed
|
# - the CFFA2 card firmware, freely redistributed by dreher.net, needed
|
||||||
# to boot images larger than an 800 KB floppy
|
# to boot images larger than an 800 KB floppy
|
||||||
# - the GS/OS 6.0.4 System.Disk, from the Apple II archive
|
# - the GS/OS 6.0.4 System.Disk, from the Apple II archive
|
||||||
# Everything lands in toolchains/firmware/iigs.
|
# Everything lands in toolchains/firmware/iigs.
|
||||||
|
|
||||||
compDescription="IIgs firmware: MAME ROM sets (ROM 01 + 03), CFFA2 firmware, GS/OS 6.0.4"
|
compDescription="IIgs firmware: MAME ROM sets (ROM 01 + 03, Super Serial Card), CFFA2 firmware, GS/OS 6.0.4"
|
||||||
compGroups="emulator:iigs"
|
compGroups="emulator:iigs"
|
||||||
compRequires="host"
|
compRequires="host"
|
||||||
compHostDeps="unzip"
|
compHostDeps="unzip"
|
||||||
|
|
@ -19,6 +20,7 @@ fwDir="$JOEY_FIRMWARE/iigs"
|
||||||
romSets=(
|
romSets=(
|
||||||
"apple2gs.zip eddce4a9f9f6e73ffed06c5aede6fbcdaa01dda5568b81f90d865858a3d7d9bc"
|
"apple2gs.zip eddce4a9f9f6e73ffed06c5aede6fbcdaa01dda5568b81f90d865858a3d7d9bc"
|
||||||
"apple2gsr1.zip 1c16fec7b9e785c4118909e409e61dcc38d0921f774b8708bae176bc5eb27307"
|
"apple2gsr1.zip 1c16fec7b9e785c4118909e409e61dcc38d0921f774b8708bae176bc5eb27307"
|
||||||
|
"a2ssc.zip 2e46808466d578006d9e12f86af8e831aa4dffb554149e184c044a1d14ed8a20"
|
||||||
)
|
)
|
||||||
cffaSha256="48242b9e2e87d87bfef1f1b1d64d1091bbfca182d745c1d031690fac0f80c0fe"
|
cffaSha256="48242b9e2e87d87bfef1f1b1d64d1091bbfca182d745c1d031690fac0f80c0fe"
|
||||||
gsosSha256="e42a4f1f50c1aae0cc17dc555415a2fbf21dc0b7b6b3e33e0d73bf22ef97506a"
|
gsosSha256="e42a4f1f50c1aae0cc17dc555415a2fbf21dc0b7b6b3e33e0d73bf22ef97506a"
|
||||||
|
|
@ -44,12 +46,22 @@ compInstall() {
|
||||||
|
|
||||||
|
|
||||||
compInstalled() {
|
compInstalled() {
|
||||||
[ -f "$fwDir/roms/apple2gs.zip" ] && [ -f "$fwDir/roms/apple2gsr1.zip" ] &&
|
local entry
|
||||||
[ -f "$fwDir/roms/a2cffa2/cffa20eec02.bin" ] && [ -f "$fwDir/gsos-system.po" ]
|
for entry in "${romSets[@]}"; do
|
||||||
|
[ -f "$fwDir/roms/${entry%% *}" ] || return 1
|
||||||
|
done
|
||||||
|
[ -f "$fwDir/roms/a2cffa2/cffa20eec02.bin" ] && [ -f "$fwDir/gsos-system.po" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compVerify() {
|
compVerify() {
|
||||||
|
local entry
|
||||||
|
local name
|
||||||
|
local sha
|
||||||
|
for entry in "${romSets[@]}"; do
|
||||||
|
read -r name sha <<< "$entry"
|
||||||
|
[ "$(sha256Of "$fwDir/roms/$name")" = "$sha" ] || return 1
|
||||||
|
done
|
||||||
[ "$(sha1sum "$fwDir/roms/a2cffa2/cffa20eec02.bin" | cut -d' ' -f1)" = "080ff88f19de22328e162954ee2b51ee65f9d5cd" ] &&
|
[ "$(sha1sum "$fwDir/roms/a2cffa2/cffa20eec02.bin" | cut -d' ' -f1)" = "080ff88f19de22328e162954ee2b51ee65f9d5cd" ] &&
|
||||||
[ "$(sha1sum "$fwDir/gsos-system.po" | cut -d' ' -f1)" = "a57940a0b724fc7dc545236a4f9d4c6dd68f3e81" ]
|
[ "$(sha1sum "$fwDir/gsos-system.po" | cut -d' ' -f1)" = "a57940a0b724fc7dc545236a4f9d4c6dd68f3e81" ]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
#
|
#
|
||||||
# sha1 destination (repository-relative) what it is
|
# sha1 destination (repository-relative) what it is
|
||||||
3b7f1493b27e212830f989f26ca76c02049f09ca toolchains/firmware/amiga/kickstart.rom Amiga Kickstart 3.1 r40.63 (A500/A600/A2000)
|
3b7f1493b27e212830f989f26ca76c02049f09ca toolchains/firmware/amiga/kickstart.rom Amiga Kickstart 3.1 r40.63 (A500/A600/A2000)
|
||||||
|
d814d6ce8efb4a87966dae581eddf4dca528094a toolchains/firmware/amiga/Devs/serial.device Amiga serial.device 37.4 (Workbench 3.1 Devs/)
|
||||||
8d72c5b4d63bb14c5dbdac495244d659aa1498b6 toolchains/firmware/x68000/roms/x68000/cgrom.dat X68000 character-generator (font) ROM
|
8d72c5b4d63bb14c5dbdac495244d659aa1498b6 toolchains/firmware/x68000/roms/x68000/cgrom.dat X68000 character-generator (font) ROM
|
||||||
5bfa4ba70431135575dab610a73dde02b8095953 stuff/spacetaxi/raw.bin Space Taxi C64 memory image (level source)
|
5bfa4ba70431135575dab610a73dde02b8095953 stuff/spacetaxi/raw.bin Space Taxi C64 memory image (level source)
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,10 @@ importOneFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# importPaths PATH... -- scan files, directories and .zip archives and
|
# importPaths PATH... -- scan files, directories, .zip archives and Amiga
|
||||||
# install every private file found. Prints what was installed.
|
# .adf disk images and install every private file found. Prints what was
|
||||||
|
# installed. (Workbench's own disk is how most people own AmigaOS files
|
||||||
|
# such as Devs/serial.device.)
|
||||||
importPaths() {
|
importPaths() {
|
||||||
local path
|
local path
|
||||||
local file
|
local file
|
||||||
|
|
@ -63,6 +65,15 @@ importPaths() {
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
*.adf|*.ADF)
|
||||||
|
rm -rf "$tmp/adf"
|
||||||
|
mkdir -p "$tmp/adf"
|
||||||
|
if python3 "$JOEY_REPO/tools/adfTool.py" extractall "$file" "$tmp/adf" 2>/dev/null; then
|
||||||
|
find "$tmp/adf" -type f -print0 | while IFS= read -r -d '' inner; do
|
||||||
|
importOneFile "$inner"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
importOneFile "$file"
|
importOneFile "$file"
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,37 @@
|
||||||
--- a/src/devices/machine/z80scc.cpp
|
--- a/src/devices/machine/z80scc.cpp
|
||||||
+++ b/src/devices/machine/z80scc.cpp
|
+++ b/src/devices/machine/z80scc.cpp
|
||||||
@@ -1045,7 +1045,7 @@
|
@@ -1045,7 +1045,18 @@
|
||||||
|
|
||||||
m_uart->m_wr0_ptrbits = 0;
|
m_uart->m_wr0_ptrbits = 0;
|
||||||
|
|
||||||
- m_rx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 8 : 3;
|
- m_rx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 8 : 3;
|
||||||
+ m_rx_fifo_sz = 512; // RetroNet patch: see z80scc.h - emulation-side depth for polled receivers
|
+ // RetroNet patch: see z80scc.h. JOEY_Z80SCC_RX_FIFO=N sets the depth, clamped to 3..512
|
||||||
|
+ // (3 = real Z8530; an ESCC keeps at least its real 8). Unset, it is the whole 512.
|
||||||
|
+ m_rx_fifo_sz = std::size(m_rx_data_fifo);
|
||||||
|
+ if (char const *const depth = osd_getenv("JOEY_Z80SCC_RX_FIFO"); depth && *depth)
|
||||||
|
+ {
|
||||||
|
+ m_rx_fifo_sz = std::clamp(atoi(depth), 3, m_rx_fifo_sz);
|
||||||
|
+ if (m_uart->m_variant & z80scc_device::SET_ESCC)
|
||||||
|
+ {
|
||||||
|
+ m_rx_fifo_sz = std::max(m_rx_fifo_sz, 8);
|
||||||
|
+ }
|
||||||
|
+ osd_printf_info("%s: RX FIFO depth %d (JOEY_Z80SCC_RX_FIFO)\n", tag(), m_rx_fifo_sz);
|
||||||
|
+ }
|
||||||
|
|
||||||
m_tx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 4 : 1;
|
m_tx_fifo_sz = (m_uart->m_variant & z80scc_device::SET_ESCC) ? 4 : 1;
|
||||||
|
|
||||||
--- a/src/devices/machine/z80scc.h
|
--- a/src/devices/machine/z80scc.h
|
||||||
+++ b/src/devices/machine/z80scc.h
|
+++ b/src/devices/machine/z80scc.h
|
||||||
@@ -256,8 +256,10 @@
|
@@ -256,8 +256,12 @@
|
||||||
unsigned int m_delayed_tx_brg_change;
|
unsigned int m_delayed_tx_brg_change;
|
||||||
|
|
||||||
// receiver state
|
// receiver state
|
||||||
- uint8_t m_rx_data_fifo[8]; // receive data FIFO
|
- uint8_t m_rx_data_fifo[8]; // receive data FIFO
|
||||||
- uint8_t m_rx_error_fifo[8]; // receive error FIFO
|
- uint8_t m_rx_error_fifo[8]; // receive error FIFO
|
||||||
+ // RetroNet patch: widened from 8 so a POLLED receiver (no RX interrupt) survives its deaf
|
+ // RetroNet patch: widened from 8 so a POLLED receiver (no RX interrupt) survives its deaf
|
||||||
+ // windows under emulation; real silicon depth is 3/8 and real clients need interrupt RX.
|
+ // windows under emulation. The depth in use is m_rx_fifo_sz: all 512 unless the
|
||||||
|
+ // JOEY_Z80SCC_RX_FIFO environment variable sets it (3 = real silicon, where clients need
|
||||||
|
+ // interrupt RX). See device_start().
|
||||||
+ uint8_t m_rx_data_fifo[512]; // receive data FIFO
|
+ uint8_t m_rx_data_fifo[512]; // receive data FIFO
|
||||||
+ uint8_t m_rx_error_fifo[512]; // receive error FIFO
|
+ uint8_t m_rx_error_fifo[512]; // receive error FIFO
|
||||||
uint8_t m_rx_error; // current receive error
|
uint8_t m_rx_error; // current receive error
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,32 @@ drivers. The gates depend on every one of them.
|
||||||
| Patch | What it fixes | Needed by |
|
| Patch | What it fixes | Needed by |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 0001-apple2gs-scc-clock | The IIgs SCC clock, so serial RX works at rates other than 230400 baud | `verify-iigs-serial.sh` |
|
| 0001-apple2gs-scc-clock | The IIgs SCC clock, so serial RX works at rates other than 230400 baud | `verify-iigs-serial.sh` |
|
||||||
| 0002-z80scc-rx-fifo | Widens the Z8530 receive FIFO from 8 to 512 bytes, so a POLLED receiver (no RX interrupt) survives the emulator's deaf windows. Real silicon holds 3 bytes; real clients need interrupt-driven RX. Originally written for RetroNet. | IIgs serial gates |
|
| 0002-z80scc-rx-fifo | Makes the Z8530 receive FIFO depth selectable at run time (see below). By default it is 512 bytes, so a POLLED receiver (no RX interrupt) survives the emulator's deaf windows; `JOEY_Z80SCC_RX_FIFO=3` restores the real chip's depth. Originally written for RetroNet. | IIgs serial gates (512); `verify-iigs-serial-loss.sh` (3) |
|
||||||
| 0003-x68k-crtc-timing | The X68000 CRTC: the graphic high-speed clear's timing (the main defect), and the text raster copy firing on both hsync edges instead of only the front porch | X68000 gates and timing |
|
| 0003-x68k-crtc-timing | The X68000 CRTC: the graphic high-speed clear's timing (the main defect), and the text raster copy firing on both hsync edges instead of only the front porch | X68000 gates and timing |
|
||||||
| 0004-x68k-waitstates-rs232-mouse | Three X68000 changes that edit the same files: VRAM wait states (MAME modelled none), the RS-232C port wired to a slot so `-rs232c null_modem` works, and a backport of upstream commit ab6b6fc9e797 (Patrick Mackinlay, "x68k: add serial mouse"), which provides the `:mouse_port:x68k:X/Y/BTN` inputs | `verify-x68000-serial.sh`, `verify-x68000-mouse.sh`, X68000 timing |
|
| 0004-x68k-waitstates-rs232-mouse | Three X68000 changes that edit the same files: VRAM wait states (MAME modelled none), the RS-232C port wired to a slot so `-rs232c null_modem` works, and a backport of upstream commit ab6b6fc9e797 (Patrick Mackinlay, "x68k: add serial mouse"), which provides the `:mouse_port:x68k:X/Y/BTN` inputs | `verify-x68000-serial.sh`, `verify-x68000-mouse.sh`, X68000 timing |
|
||||||
|
|
||||||
|
## Receive FIFO depth (0002)
|
||||||
|
|
||||||
|
Real Z8530 silicon holds 3 received bytes (an ESCC, 8), so a real client
|
||||||
|
needs interrupt-driven receive. Under emulation a polled receiver sees deaf
|
||||||
|
windows no real machine has, so by default the patched MAME gives every
|
||||||
|
Z8530 channel (IIgs and X68000 alike) a 512-byte receive FIFO. The
|
||||||
|
environment variable `JOEY_Z80SCC_RX_FIFO` overrides that for one run:
|
||||||
|
|
||||||
|
| `JOEY_Z80SCC_RX_FIFO` | Receive FIFO depth |
|
||||||
|
|---|---|
|
||||||
|
| unset or empty | 512 (the default the gates rely on) |
|
||||||
|
| `3` | the real Z8530 (stock MAME's value); an ESCC keeps its real 8 |
|
||||||
|
| `N` | N, clamped to 3..512; an ESCC never gets fewer than 8 |
|
||||||
|
|
||||||
|
MAME's ring of size N holds N-1 unread bytes, so 3 overruns on the third
|
||||||
|
unread byte, one byte earlier than silicon (whose 3-byte FIFO fills first):
|
||||||
|
a slightly stricter test than the real chip. When the variable is set, MAME
|
||||||
|
prints `<channel tag>: RX FIFO depth N (JOEY_Z80SCC_RX_FIFO)` for each
|
||||||
|
channel at start-up, so a log shows the depth really was applied.
|
||||||
|
`scripts/verify-iigs-serial-loss.sh` runs with 3, because a 512-byte FIFO
|
||||||
|
would hide exactly the receive loss that gate looks for.
|
||||||
|
|
||||||
## Why the X68000 changes are one patch
|
## Why the X68000 changes are one patch
|
||||||
|
|
||||||
They were first kept as three separate patches, but the upstream mouse
|
They were first kept as three separate patches, but the upstream mouse
|
||||||
|
|
|
||||||
83
joey-run
83
joey-run
|
|
@ -109,53 +109,6 @@ magicPort() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# exampleProgram NAME PORT -- the built file for an example name.
|
|
||||||
exampleProgram() {
|
|
||||||
local name="$1"
|
|
||||||
local bin="$repo/build/$2/bin"
|
|
||||||
local up="${name^^}"
|
|
||||||
local cap
|
|
||||||
local candidate
|
|
||||||
case "$2" in
|
|
||||||
iigs)
|
|
||||||
printf '%s\n' "$bin/$up"
|
|
||||||
;;
|
|
||||||
amiga)
|
|
||||||
cap="${name,,}"
|
|
||||||
case "$cap" in
|
|
||||||
staxi|spacetaxi) cap=taxi ;;
|
|
||||||
esac
|
|
||||||
cap="${cap^}"
|
|
||||||
for candidate in "$bin/$cap/$cap" "$bin/$cap"; do
|
|
||||||
if [ -f "$candidate" ]; then
|
|
||||||
printf '%s\n' "$candidate"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
printf '%s\n' "$bin/$cap"
|
|
||||||
;;
|
|
||||||
atarist|dos)
|
|
||||||
[ "$up" = "SPACETAXI" ] && up=STAXI
|
|
||||||
local ext=".PRG"
|
|
||||||
[ "$2" = "dos" ] && ext=".EXE"
|
|
||||||
for candidate in "$bin/$up/$up$ext" "$bin/$up$ext"; do
|
|
||||||
if [ -f "$candidate" ]; then
|
|
||||||
printf '%s\n' "$candidate"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
printf '%s\n' "$bin/$up$ext"
|
|
||||||
;;
|
|
||||||
x68000)
|
|
||||||
printf '%s\n' "$bin/$up.X"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
fail "unknown port '$2' (iigs, amiga, atarist, dos or x68000)"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# folderProgram DIR -- the one program file inside an app folder.
|
# folderProgram DIR -- the one program file inside an app folder.
|
||||||
folderProgram() {
|
folderProgram() {
|
||||||
local file
|
local file
|
||||||
|
|
@ -350,6 +303,15 @@ runAmiga() {
|
||||||
stageFolder "$hd"
|
stageFolder "$hd"
|
||||||
mkdir -p "$hd/s"
|
mkdir -p "$hd/s"
|
||||||
printf ':%s\n' "$name" > "$hd/s/startup-sequence"
|
printf ':%s\n' "$name" > "$hd/s/startup-sequence"
|
||||||
|
# Kickstart 3.1 has no serial.device in ROM; AmigaOS loads it from
|
||||||
|
# DEVS: (SYS:Devs), so a program that opens the serial port needs it on
|
||||||
|
# this boot drive. Harmless for everything else.
|
||||||
|
if [ -n "${AMIGA_SERIAL_DEVICE:-}" ] && [ -f "$AMIGA_SERIAL_DEVICE" ]; then
|
||||||
|
mkdir -p "$hd/Devs"
|
||||||
|
cp "$AMIGA_SERIAL_DEVICE" "$hd/Devs/serial.device"
|
||||||
|
else
|
||||||
|
echo "joey-run: no serial.device installed (./install.sh install amigaSerial); serial will not open on the Amiga" >&2
|
||||||
|
fi
|
||||||
args=(--amiga_model=A500 --fast_memory=2048
|
args=(--amiga_model=A500 --fast_memory=2048
|
||||||
--hard_drive_0="$hd" --hard_drive_0_label=JOEYLIB
|
--hard_drive_0="$hd" --hard_drive_0_label=JOEYLIB
|
||||||
--automatic_input_grab=0 --middle_click_ungrab=1 --mouse_integration=1
|
--automatic_input_grab=0 --middle_click_ungrab=1 --mouse_integration=1
|
||||||
|
|
@ -428,28 +390,18 @@ runDos() {
|
||||||
|
|
||||||
runX68000() {
|
runX68000() {
|
||||||
local name
|
local name
|
||||||
local xdf="$repo/tools/xdftool.py"
|
|
||||||
local stage="$work/files"
|
local stage="$work/files"
|
||||||
local f
|
|
||||||
local rel
|
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
|
# shellcheck source=scripts/lib/x68kDisk.sh
|
||||||
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
name="$(basename "$program")"
|
name="$(basename "$program")"
|
||||||
# Human68k boots from a trimmed copy of the system disk whose
|
# Human68k boots from a trimmed copy of the system disk whose
|
||||||
# AUTOEXEC.BAT runs the program from the data disk in drive B.
|
# AUTOEXEC.BAT runs the program from the data disk in drive B.
|
||||||
printf 'B:\r\nB:\\%s\r\n\x1a' "$name" > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/boot.xdf" 'B:' "B:\\$name" || fail "cannot build the boot disk"
|
||||||
cp "$X68K_DIR/HUMAN302.XDF" "$work/boot.xdf"
|
x68kDataDisk "$work/data.xdf" || fail "cannot build the data disk"
|
||||||
cp "$X68K_DIR/HUMAN302.XDF" "$work/data.xdf"
|
x68kXdf add "$work/data.xdf" "$program" "$name" >/dev/null || fail "cannot copy $name onto the data disk"
|
||||||
python3 "$xdf" empty "$work/data.xdf" >/dev/null
|
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$xdf" delete "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
for f in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$xdf" deltree "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
python3 "$xdf" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
python3 "$xdf" add "$work/data.xdf" "$program" "$name" >/dev/null
|
|
||||||
rm -rf "$stage"
|
rm -rf "$stage"
|
||||||
mkdir -p "$stage"
|
mkdir -p "$stage"
|
||||||
if [ -d "$(dirname "$program")/DATA" ]; then
|
if [ -d "$(dirname "$program")/DATA" ]; then
|
||||||
|
|
@ -460,10 +412,7 @@ runX68000() {
|
||||||
cp -r "$dataDir/." "$stage/DATA/"
|
cp -r "$dataDir/." "$stage/DATA/"
|
||||||
fi
|
fi
|
||||||
if [ -d "$stage/DATA" ]; then
|
if [ -d "$stage/DATA" ]; then
|
||||||
while IFS= read -r f; do
|
x68kAddTree "$work/data.xdf" "$stage/DATA" DATA || fail "the DATA files do not fit on the 1232 KB data disk"
|
||||||
rel="${f#"$stage"/}"
|
|
||||||
python3 "$xdf" add "$work/data.xdf" "$f" "${rel^^}" >/dev/null
|
|
||||||
done < <(find "$stage/DATA" -type f | sort)
|
|
||||||
fi
|
fi
|
||||||
echo "joey-run: booting the X68000 (Human68k 3.02); $name runs from drive B. Close MAME to stop."
|
echo "joey-run: booting the X68000 (Human68k 3.02); $name runs from drive B. Close MAME to stop."
|
||||||
runMame x68000 -bios ipl10 -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf"
|
runMame x68000 -bios ipl10 -rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf"
|
||||||
|
|
@ -519,7 +468,7 @@ if [ -d "$program" ]; then
|
||||||
program="$(folderProgram "$program")"
|
program="$(folderProgram "$program")"
|
||||||
elif [ ! -e "$program" ]; then
|
elif [ ! -e "$program" ]; then
|
||||||
[ -n "$port" ] || fail "$program is not a file; for an example name add --port"
|
[ -n "$port" ] || fail "$program is not a file; for an example name add --port"
|
||||||
program="$(exampleProgram "$program" "$port")"
|
program="$(joeyExampleProgram "$program" "$port")" || exit 1
|
||||||
[ -e "$program" ] || fail "$program is not built (make $port)"
|
[ -e "$program" ] || fail "$program is not built (make $port)"
|
||||||
fi
|
fi
|
||||||
program="$(cd "$(dirname "$program")" && pwd)/$(basename "$program")"
|
program="$(cd "$(dirname "$program")" && pwd)/$(basename "$program")"
|
||||||
|
|
|
||||||
35
make/iigs.mk
35
make/iigs.mk
|
|
@ -62,6 +62,7 @@ KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
SCCPROBE_SRC := $(EXAMPLES)/sccprobe/sccprobe.c
|
SCCPROBE_SRC := $(EXAMPLES)/sccprobe/sccprobe.c
|
||||||
|
SERLOSS_SRC := $(EXAMPLES)/serloss/serloss.c
|
||||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
|
|
@ -69,17 +70,19 @@ UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
|
AGI_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
|
||||||
STAXI_SRCS := $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c $(EXAMPLES)/spacetaxi/stMarshalIigs.s
|
STAXI_SRCS := $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
|
|
||||||
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
# NinjaTrackerPlus replayer: Merlin32-assemble ninjatrackerplus.s to a 34KB
|
||||||
# binary that ships next to the program; jlpAudioInit loads "ntpplayer" from
|
# binary that ships next to the program; jlpAudioInit loads "ntpplayer" from
|
||||||
# disk at run time (see the $(BINDIR)/AUDIO rule).
|
# disk at run time (see the $(BINDIR)/AUDIO rule). The source is assembled
|
||||||
|
# with JoeyLib's interrupt window added to NTPstreamsound (NTP_PATCH).
|
||||||
NTP_SRC := $(IIGS_NTP_DIR)/ninjatrackerplus.s
|
NTP_SRC := $(IIGS_NTP_DIR)/ninjatrackerplus.s
|
||||||
|
NTP_PATCH := $(REPO_DIR)/make/support/iigs/ntpIrqWindow.py
|
||||||
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
||||||
IIGS_MERLIN := $(IIGS_AS)
|
IIGS_MERLIN := $(IIGS_AS)
|
||||||
|
|
||||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-shrtail clean-iigs clean
|
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-disk-all iigs-game-disk iigs-game-disks iigs-game-disk-agi iigs-verify-game-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-serial-loss iigs-verify-shrtail clean-iigs clean
|
||||||
|
|
||||||
# Default: build (compile-check) the library. The MAME smoke test is
|
# Default: build (compile-check) the library. The MAME smoke test is
|
||||||
# `iigs-clang-smoke`; disks and examples are `iigs-disk`.
|
# `iigs-clang-smoke`; disks and examples are `iigs-disk`.
|
||||||
|
|
@ -222,6 +225,19 @@ iigs-verify: iigs-disk
|
||||||
iigs-verify-serial: $(BINDIR)/SCCPROBE
|
iigs-verify-serial: $(BINDIR)/SCCPROBE
|
||||||
$(REPO_DIR)/scripts/verify-iigs-serial.sh
|
$(REPO_DIR)/scripts/verify-iigs-serial.sh
|
||||||
|
|
||||||
|
# Receive-loss gate: 4096 bytes streamed at 9600 baud while the program does
|
||||||
|
# nothing but full-screen presents must all arrive. Runs the modem port (SCC)
|
||||||
|
# and a Super Serial Card in slot 2 (6551) on the installed MAME, with the SCC
|
||||||
|
# receive FIFO cut to the real chip's 3 bytes (JOEY_Z80SCC_RX_FIFO=3) and the
|
||||||
|
# card's a2ssc firmware from iigsFirmware; see the script header. ~3 min per mode.
|
||||||
|
# The card runs at SERLOSS_SSC_BAUD: its 6551 holds ONE received byte, so it is
|
||||||
|
# lossless through full-screen presents only up to 2400 (docs/ApiSerial.adoc);
|
||||||
|
# at 9600 this half could never pass.
|
||||||
|
SERLOSS_SSC_BAUD := 2400
|
||||||
|
iigs-verify-serial-loss: $(BINDIR)/SERLOSS $(BINDIR)/SERLOSSS
|
||||||
|
$(REPO_DIR)/scripts/verify-iigs-serial-loss.sh scc
|
||||||
|
SERLOSS_BAUD=$(SERLOSS_SSC_BAUD) $(REPO_DIR)/scripts/verify-iigs-serial-loss.sh ssc
|
||||||
|
|
||||||
# Boot-verify every launchable example on joey.2mg (per-example thresholds).
|
# Boot-verify every launchable example on joey.2mg (per-example thresholds).
|
||||||
iigs-verify-all: iigs-disk
|
iigs-verify-all: iigs-disk
|
||||||
$(REPO_DIR)/scripts/verify-iigs-all.sh
|
$(REPO_DIR)/scripts/verify-iigs-all.sh
|
||||||
|
|
@ -259,9 +275,9 @@ iigs-verify-shrtail: $(LIB) $(NTP_BIN)
|
||||||
iigs-verify-save: $(BINDIR)/SAVE $(BINDIR)/DRAW
|
iigs-verify-save: $(BINDIR)/SAVE $(BINDIR)/DRAW
|
||||||
$(REPO_DIR)/scripts/verify-iigs-save.sh
|
$(REPO_DIR)/scripts/verify-iigs-save.sh
|
||||||
|
|
||||||
$(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
$(NTP_BIN): $(NTP_SRC) $(NTP_PATCH) $(IIGS_MERLIN)
|
||||||
@mkdir -p $(BUILD)/audio
|
@mkdir -p $(BUILD)/audio
|
||||||
@cp $(NTP_SRC) $(BUILD)/audio/ninjatrackerplus.s
|
@python3 $(NTP_PATCH) $(NTP_SRC) $(BUILD)/audio/ninjatrackerplus.s
|
||||||
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
||||||
mv $(BUILD)/audio/ntpplayer $@
|
mv $(BUILD)/audio/ntpplayer $@
|
||||||
|
|
||||||
|
|
@ -331,6 +347,15 @@ $(BINDIR)/SCCPROBE: $(SCCPROBE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SCCPROBE.d $(INCLUDES) -o $@ $(SCCPROBE_SRC) $(LIB)
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SCCPROBE.d $(INCLUDES) -o $@ $(SCCPROBE_SRC) $(LIB)
|
||||||
|
|
||||||
|
# Receive-loss probes for iigs-verify-serial-loss: modem port and slot-2 SSC.
|
||||||
|
$(BINDIR)/SERLOSS: $(SERLOSS_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SERLOSS.d $(INCLUDES) -o $@ $(SERLOSS_SRC) $(LIB)
|
||||||
|
|
||||||
|
$(BINDIR)/SERLOSSS: $(SERLOSS_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SERLOSSS.d -DSERLOSS_SLOT=2 -DSERLOSS_BAUD=$(SERLOSS_SSC_BAUD)u $(INCLUDES) -o $@ $(SERLOSS_SRC) $(LIB)
|
||||||
|
|
||||||
$(BINDIR)/SAVE: $(SAVE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
$(BINDIR)/SAVE: $(SAVE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SAVE.d $(INCLUDES) -o $@ $(SAVE_SRC) $(LIB)
|
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SAVE.d $(INCLUDES) -o $@ $(SAVE_SRC) $(LIB)
|
||||||
|
|
|
||||||
119
make/support/iigs/ntpIrqWindow.py
Executable file
119
make/support/iigs/ntpIrqWindow.py
Executable file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# ntpIrqWindow.py SRC DST - bound NinjaTrackerPlus's interrupts-off stretches.
|
||||||
|
#
|
||||||
|
# NinjaTrackerPlus moves sample data into the DOC with interrupts masked,
|
||||||
|
# ~4 us a byte (measured under MAME), and the Z8530 SCC's receive FIFO only
|
||||||
|
# lasts ~3.1 ms at 9600 baud (~2.3 ms under MAME). Two stretches outlast it:
|
||||||
|
# - NTPstreamsound fills a stream's 512-byte DOC page with two unrolled
|
||||||
|
# 256-byte copies under its own SEI: ~2.4 ms. stream_window goes between
|
||||||
|
# the halves: pending interrupts run there, then the DOC's mode and
|
||||||
|
# address are put back for the second half. The stream's oscillators are
|
||||||
|
# already halted, and its job is cleared first, so a refill interrupt left
|
||||||
|
# pending by the slot's previous sound cannot stream into the page.
|
||||||
|
# - The DOC interrupt handler refills every streaming oscillator that is
|
||||||
|
# due before it returns, 256 bytes each: ~1 ms apiece, ~2.5 ms for two.
|
||||||
|
# It returns after one instead; the DOC keeps its IRQ up for the rest, so
|
||||||
|
# the handler is re-entered at once, after anything the ROM's interrupt
|
||||||
|
# manager puts first (the SCC is on its fast path).
|
||||||
|
# Both only while the word at offset 27 of the replayer (irq_window, right
|
||||||
|
# after the documented jump table) is nonzero. audio_full.c sets it from
|
||||||
|
# hal.c's tight interrupt-latency bound before every NTPstreamsound, so with
|
||||||
|
# the bound loose the replayer does exactly what it always did.
|
||||||
|
#
|
||||||
|
# The source is the installer's pinned ntpsources.zip (checksummed), so
|
||||||
|
# every anchor below is exact; a miss means NTP changed and this script
|
||||||
|
# must be revisited, so it fails the build instead of guessing.
|
||||||
|
import sys
|
||||||
|
|
||||||
|
TABLE_END = """ jmp streamsound
|
||||||
|
"""
|
||||||
|
TABLE_END_NEW = """ jmp streamsound
|
||||||
|
|
||||||
|
; irq_window (JoeyLib, offset 27): nonzero bounds the interrupts-off
|
||||||
|
; stretches -- see stream_window and interrupt_handler_exit.
|
||||||
|
irq_window dw 0
|
||||||
|
"""
|
||||||
|
FILL = """ ldy streamsound_osc2
|
||||||
|
jsr stream_copy ;fill buffer with 512 bytes for first play
|
||||||
|
jsr stream_copy_no_init
|
||||||
|
"""
|
||||||
|
FILL_NEW = """ ldy streamsound_osc2
|
||||||
|
jsr stream_copy ;fill buffer with 512 bytes for first play
|
||||||
|
jsr stream_window ;JoeyLib: interrupts in between the halves, if asked
|
||||||
|
jsr stream_copy_no_init
|
||||||
|
"""
|
||||||
|
COPY_HELPERS = """ mx %10
|
||||||
|
streamsound_copy = * ;copy a byte from stream structure to multiple doc registers
|
||||||
|
"""
|
||||||
|
COPY_HELPERS_NEW = """; stream_window (JoeyLib): between the two halves of streamsound's first
|
||||||
|
; DOC fill, when irq_window is nonzero, let pending interrupts run, then
|
||||||
|
; point the DOC back at the second half (a handler may have used it).
|
||||||
|
; In and out: mx %00, Y = the stream's track * 2.
|
||||||
|
mx %00
|
||||||
|
stream_window = *
|
||||||
|
lda irq_window
|
||||||
|
beq stream_window_1
|
||||||
|
sep #$20
|
||||||
|
mx %10
|
||||||
|
ldx streamsound_osc ;no refill job left pending from the slot's last sound
|
||||||
|
stz oscillator_job_tbl,x
|
||||||
|
cli
|
||||||
|
nop ;a pending interrupt is taken here
|
||||||
|
sei
|
||||||
|
jsr access_doc_ram
|
||||||
|
rep #$20
|
||||||
|
mx %00
|
||||||
|
lda stream_doc_page,y ;the page's second half
|
||||||
|
inc
|
||||||
|
xba
|
||||||
|
sta sound_address
|
||||||
|
stream_window_1 = *
|
||||||
|
rts
|
||||||
|
|
||||||
|
mx %10
|
||||||
|
streamsound_copy = * ;copy a byte from stream structure to multiple doc registers
|
||||||
|
"""
|
||||||
|
HANDLER_JOB = """ ldx interrupt_job
|
||||||
|
jsr (interrupt_jump_tbl,x)
|
||||||
|
|
||||||
|
]loop lda sound_control
|
||||||
|
"""
|
||||||
|
HANDLER_JOB_NEW = """ ldx interrupt_job
|
||||||
|
jsr (interrupt_jump_tbl,x)
|
||||||
|
|
||||||
|
lda irq_window ;JoeyLib: one oscillator an interrupt when asked;
|
||||||
|
bne interrupt_handler_exit ;the DOC keeps its IRQ up for any others
|
||||||
|
]loop lda sound_control
|
||||||
|
"""
|
||||||
|
HANDLER_END = """ bpl interrupt_handler_loop
|
||||||
|
|
||||||
|
sep #$30
|
||||||
|
"""
|
||||||
|
HANDLER_END_NEW = """ bpl interrupt_handler_loop
|
||||||
|
|
||||||
|
interrupt_handler_exit = *
|
||||||
|
sep #$30
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def patchOnce(text, old, new):
|
||||||
|
if text.count(old) != 1:
|
||||||
|
sys.exit("ntpIrqWindow: anchor not found exactly once -- NinjaTrackerPlus changed:\n" + old)
|
||||||
|
return text.replace(old, new)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
sys.exit("usage: ntpIrqWindow.py SRC DST")
|
||||||
|
with open(sys.argv[1], "r", encoding="ascii") as f:
|
||||||
|
text = f.read()
|
||||||
|
text = patchOnce(text, TABLE_END, TABLE_END_NEW)
|
||||||
|
text = patchOnce(text, FILL, FILL_NEW)
|
||||||
|
text = patchOnce(text, COPY_HELPERS, COPY_HELPERS_NEW)
|
||||||
|
text = patchOnce(text, HANDLER_JOB, HANDLER_JOB_NEW)
|
||||||
|
text = patchOnce(text, HANDLER_END, HANDLER_END_NEW)
|
||||||
|
with open(sys.argv[2], "w", encoding="ascii") as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Sharp X68000 (elf2x68k / m68k-xelf-gcc) build rules.
|
# Sharp X68000 (elf2x68k / m68k-xelf-gcc) build rules.
|
||||||
#
|
#
|
||||||
# BRING-UP STATE. This builds libjoey.a and the serial example against the
|
# The port draws planar into the text plane (src/x68000/halPlanar.c), shares
|
||||||
# generic backend -- no native primitives, no audio, no sprite codegen yet.
|
# the Amiga's 68k planar fills, plots and sprite emitter (src/m68k), plays
|
||||||
# See claudeDocs/x68000_port.md for what is and is not real.
|
# chip music on the OPM and samples through the ADPCM channel.
|
||||||
#
|
#
|
||||||
# The toolchain is elf2x68k (BSD-3, gcc 13.4.0 / binutils / newlib), installed
|
# The toolchain is elf2x68k (BSD-3, gcc 13.4.0 / binutils / newlib), installed
|
||||||
# by ./install.sh (X68K_ROOT). m68k-xelf-gcc emits ELF; the driver's
|
# by ./install.sh (X68K_ROOT). m68k-xelf-gcc emits ELF; the driver's
|
||||||
|
|
@ -39,22 +39,11 @@ CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer \
|
||||||
LDFLAGS := -lm
|
LDFLAGS := -lm
|
||||||
|
|
||||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/x68000/*.c)
|
PORT_C_SRCS := $(wildcard $(SRC_DIR)/x68000/*.c)
|
||||||
# Port-local 68000 asm, if any lands. Empty today: SCB per-band palettes are
|
# Raw interrupt shims (timer tick, SCB raster, SCC receive): each saves every
|
||||||
# done in the pixel expansion (DOS-style), not with a raster ISR, so the port is
|
# register and returns with RTE, which a C function cannot.
|
||||||
# pure C again.
|
|
||||||
PORT_S_SRCS := $(wildcard $(SRC_DIR)/x68000/*.s)
|
PORT_S_SRCS := $(wildcard $(SRC_DIR)/x68000/*.s)
|
||||||
|
|
||||||
# No src/m68k sources yet: those are the planar sprite emitters and the planar
|
# Sprite codegen is the shared 68k PLANAR emitter, unchanged. Its
|
||||||
# surface helpers, and this port is still chunky. They come in with the storage
|
|
||||||
# model decision.
|
|
||||||
# common.mk filters spriteEmitStub.c out of GENERIC_C_SRCS because every
|
|
||||||
# shipping port has a real per-CPU emitter. This port does not yet, so add it
|
|
||||||
# back: jlSpriteCompile returns false and sprites stay INTERPRETER-ONLY, which
|
|
||||||
# is correct but slow (see the "sprites must be compiled or they interpret"
|
|
||||||
# note). Replacing this with a real 68k emitter is part of the storage-model
|
|
||||||
# work -- src/m68k/spriteEmitPlanar68k.c already exists and, on the text-plane
|
|
||||||
# path, needs only AMIGA_PLANE_STRIDE changed at three sites.
|
|
||||||
# Real sprite codegen: the shared 68k PLANAR emitter, unchanged. Its
|
|
||||||
# AMIGA_PLANE_STRIDE is SURFACE_WIDTH/8 = 40, which is this port's surface
|
# AMIGA_PLANE_STRIDE is SURFACE_WIDTH/8 = 40, which is this port's surface
|
||||||
# stride too -- the display's 128-byte stride never reaches the emitter, which
|
# stride too -- the display's 128-byte stride never reaches the emitter, which
|
||||||
# only ever addresses the surface. So sprites COMPILE here rather than
|
# only ever addresses the surface. So sprites COMPILE here rather than
|
||||||
|
|
@ -94,11 +83,41 @@ $(LIBXMP_AR): $(LIBXMP_OBJS)
|
||||||
|
|
||||||
LIB := $(LIBDIR)/libjoey.a
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
|
# Every example but Space Taxi is its sources plus the library, linked by the
|
||||||
|
# one rule below. Worth knowing which ones the gates use:
|
||||||
|
# PATTERN the SCB acceptance vehicle: 8 bands of 25 lines with 8 gradient
|
||||||
|
# palettes, so a misplaced band boundary is immediately visible.
|
||||||
|
# UBER the golden-hash vehicle: every public op, a hash per op, which
|
||||||
|
# tools/diff-uber-hashes compares against the IIgs reference.
|
||||||
|
# AUDIO both audio paths: JYM1 chip music on the OPM, MOD/SFX on ADPCM.
|
||||||
|
# KEYS the input and mouse vehicle: key predicates, the typed line
|
||||||
|
# (jlInputGetChar) and its verification row.
|
||||||
|
# SERIAL the RS-232C gate's echo program.
|
||||||
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||||
|
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||||
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
|
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||||
|
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||||
|
UBER_SRC := $(EXAMPLES)/uber/uber.c
|
||||||
|
ADV_SRC := $(EXAMPLES)/adventure/adventure.c
|
||||||
|
ADV2_SRC := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
|
AGI_SRC := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
|
||||||
|
EXAMPLES_X := HELLO PATTERN DRAW KEYS SERIAL SERTEST SAVE JOY SPRITE AUDIO UBER ADV ADV2 AGI
|
||||||
|
EXAMPLE_BINS := $(foreach e,$(EXAMPLES_X),$(BINDIR)/$(e).X)
|
||||||
|
|
||||||
|
# AUDIO loads DATA/test.mod and DATA/test.sfx from beside the program.
|
||||||
|
DATA_DIR := $(BINDIR)/DATA
|
||||||
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
|
# Space Taxi ships in its own folder with its DATA, as on the other ports,
|
||||||
|
# so its megabyte of data never rides onto every other program's floppy.
|
||||||
|
STAXI_DIR := $(BINDIR)/STAXI
|
||||||
|
STAXI_BIN := $(STAXI_DIR)/STAXI.X
|
||||||
STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c
|
STAXI_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
|
|
||||||
.PHONY: all x68000 x68000-lib x68000-examples x68000-verify-serial x68000-verify-mouse x68000-verify-golden clean-x68000 clean
|
.PHONY: all x68000 x68000-lib x68000-examples x68000-verify-serial x68000-verify-mouse x68000-verify-golden clean-x68000 clean
|
||||||
|
|
@ -107,7 +126,7 @@ all x68000: x68000-lib x68000-examples
|
||||||
|
|
||||||
x68000-lib: $(LIB) $(LIBXMP_AR)
|
x68000-lib: $(LIB) $(LIBXMP_AR)
|
||||||
|
|
||||||
x68000-examples: $(BINDIR)/SERIAL.X $(BINDIR)/UBER.X $(BINDIR)/AUDIO.X $(BINDIR)/PATTERN.X $(BINDIR)/KEYS.X $(BINDIR)/STAXI.X
|
x68000-examples: $(EXAMPLE_BINS) $(STAXI_BIN) $(DATA_FILES)
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -143,38 +162,19 @@ $(LIB): $(LIB_OBJS)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(X68K_AR) rcs $@ $(LIB_OBJS)
|
$(X68K_AR) rcs $@ $(LIB_OBJS)
|
||||||
|
|
||||||
# PATTERN is the SCB acceptance vehicle: 8 bands of 25 lines with 8 gradient
|
.SECONDEXPANSION:
|
||||||
# palettes, so a mis-placed band boundary is immediately visible.
|
$(EXAMPLE_BINS): $(BINDIR)/%.X: $$($$*_SRC) $(LIB) $(LIBXMP_AR)
|
||||||
$(BINDIR)/PATTERN.X: $(PATTERN_SRC) $(LIB) $(LIBXMP_AR)
|
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(X68K_CC) $(CFLAGS) $(PATTERN_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
$(X68K_CC) $(CFLAGS) $($*_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
$(BINDIR)/SERIAL.X: $(SERIAL_SRC) $(LIB) $(LIBXMP_AR)
|
$(DATA_DIR)/%: $(REPO_DIR)/assets/%
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(X68K_CC) $(CFLAGS) $(SERIAL_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
cp $< $@
|
||||||
|
|
||||||
# UBER is the golden-hash vehicle: it exercises every public op and prints a
|
|
||||||
# hash per op, which tools/diff-uber-hashes compares against the IIgs reference.
|
|
||||||
$(BINDIR)/UBER.X: $(UBER_SRC) $(LIB) $(LIBXMP_AR)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(X68K_CC) $(CFLAGS) $(UBER_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
|
||||||
|
|
||||||
# AUDIO exercises both audio paths: JYM1 chip music on the OPM and MOD/SFX
|
|
||||||
# through the ADPCM channel.
|
|
||||||
$(BINDIR)/AUDIO.X: $(AUDIO_SRC) $(LIB) $(LIBXMP_AR)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(X68K_CC) $(CFLAGS) $(AUDIO_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
|
||||||
|
|
||||||
# KEYS is the input acceptance vehicle: key predicates on the grid plus
|
|
||||||
# the typed-character line (jlInputGetChar) and its verification row.
|
|
||||||
$(BINDIR)/KEYS.X: $(KEYS_SRC) $(LIB) $(LIBXMP_AR)
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(X68K_CC) $(CFLAGS) $(KEYS_SRC) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
|
||||||
|
|
||||||
# Space Taxi asset variables (the rules follow the binary below); make
|
# Space Taxi asset variables (the rules follow the binary below); make
|
||||||
# expands prerequisites when it reads the rule, so these come first.
|
# expands prerequisites when it reads the rule, so these come first.
|
||||||
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
STAXI_GEN_DIR := $(EXAMPLES)/spacetaxi/generated/x68000
|
||||||
STAXI_RUN_DIR := $(BINDIR)/DATA
|
STAXI_RUN_DIR := $(STAXI_DIR)/DATA
|
||||||
STAXI_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
STAXI_LEVEL_GEN := $(foreach n,$(STAXI_LEVEL_NUMS),$(STAXI_GEN_DIR)/levels/level$(n).dat) \
|
||||||
$(STAXI_GEN_DIR)/levels/title.dat
|
$(STAXI_GEN_DIR)/levels/title.dat
|
||||||
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
STAXI_LEVEL_RUN := $(patsubst $(STAXI_GEN_DIR)/%,$(STAXI_RUN_DIR)/%,$(STAXI_LEVEL_GEN))
|
||||||
|
|
@ -227,7 +227,7 @@ $(STAXI_SPEECH_RUN): $(STAXI_SPEECH_GEN)
|
||||||
# Human68k floppy: the unstripped binary is most of the volume on its
|
# Human68k floppy: the unstripped binary is most of the volume on its
|
||||||
# own. The DATA tree next to it (levels + the precompiled cel bank) is
|
# own. The DATA tree next to it (levels + the precompiled cel bank) is
|
||||||
# what joey-run copies onto the data floppy.
|
# what joey-run copies onto the data floppy.
|
||||||
$(BINDIR)/STAXI.X: $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(call staxiData,$(STAXI_ASSET_DSTS))
|
$(STAXI_BIN): $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(X68K_CC) $(CFLAGS) -s -I$(EXAMPLES)/spacetaxi $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
$(X68K_CC) $(CFLAGS) -s -I$(EXAMPLES)/spacetaxi $(STAXI_SRCS) $(LIB) $(LIBXMP_AR) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
FRAMES="${X68K_BENCH_FRAMES:-120000}"
|
FRAMES="${X68K_BENCH_FRAMES:-120000}"
|
||||||
WALL="${X68K_BENCH_WALL:-7200}"
|
WALL="${X68K_BENCH_WALL:-7200}"
|
||||||
# Which plane the built port presents into: must match X68K_PRESENT_TEXT in
|
# Which plane the built port presents into: must match X68K_PRESENT_TEXT in
|
||||||
|
|
@ -59,24 +59,10 @@ m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
|
||||||
|
|
||||||
# TWO DISKS: the boot disk carries only Human68k and AUTOEXEC, while the binary
|
# TWO DISKS: the boot disk carries only Human68k and AUTOEXEC, while the binary
|
||||||
# AND joeylog.txt live on the second disk on B:. AUTOEXEC switches to B: before
|
# AND joeylog.txt live on the second disk on B:. AUTOEXEC switches to B: before
|
||||||
# launching so the log lands there too. Keeping the writable log off the boot
|
# launching so the log lands there too.
|
||||||
# disk also means a run can never corrupt the thing that boots it.
|
x68kBootDisk "$work/boot.xdf" 'B:' 'B:\UBER.X' || exit 1
|
||||||
printf 'B:\r\nB:\\UBER.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
x68kDataDisk "$work/data.xdf" || exit 1
|
||||||
cp "$TEMPLATE" "$work/boot.xdf"
|
x68kXdf add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
|
||||||
# Data disk = the template emptied (keeps the BPB and boot sector). `empty`
|
|
||||||
# rather than a list of filenames: the stock disk's utilities live in
|
|
||||||
# SUBDIRECTORIES, which `delete` cannot see or free.
|
|
||||||
cp "$TEMPLATE" "$work/data.xdf"
|
|
||||||
python3 "$repo/tools/xdftool.py" empty "$work/data.xdf" >/dev/null
|
|
||||||
# Trim the boot disk of anything not needed to boot and run one .X.
|
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
for d in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$repo/tools/xdftool.py" deltree "$work/boot.xdf" "$d" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
|
|
||||||
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
|
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
|
||||||
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
|
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
|
||||||
|
|
@ -171,7 +157,8 @@ emu.register_frame_done(function()
|
||||||
end)
|
end)
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
# In the work dir, so MAME cfg/ and nvram/ never persist between runs.
|
||||||
|
runLog=$(cd "$work" && timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||||
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" -flop2 "$work/data.xdf" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
-autoboot_script "$work/bench.lua" </dev/null 2>&1 | grep '^BENCH')
|
-autoboot_script "$work/bench.lua" </dev/null 2>&1 | grep '^BENCH')
|
||||||
|
|
@ -189,7 +176,7 @@ if ! echo "$runLog" | grep -q 'done-screen'; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 "$repo/tools/xdftool.py" extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
|
x68kXdf extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
|
||||||
echo "bench-x68000: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
|
echo "bench-x68000: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ cd "$repo"
|
||||||
"$cc" -DJOEYLIB_PLATFORM_BLANK \
|
"$cc" -DJOEYLIB_PLATFORM_BLANK \
|
||||||
-DJL_HAS_SERIAL_OPEN -DJL_HAS_SERIAL_CLOSE -DJL_HAS_SERIAL_POLL \
|
-DJL_HAS_SERIAL_OPEN -DJL_HAS_SERIAL_CLOSE -DJL_HAS_SERIAL_POLL \
|
||||||
-DJL_HAS_SERIAL_READ -DJL_HAS_SERIAL_WRITE -DJL_HAS_SERIAL_AVAILABLE \
|
-DJL_HAS_SERIAL_READ -DJL_HAS_SERIAL_WRITE -DJL_HAS_SERIAL_AVAILABLE \
|
||||||
-DJL_HAS_SERIAL_FLUSH \
|
-DJL_HAS_SERIAL_FLUSH -DJL_HAS_SERIAL_SET_RTS -DJL_HAS_SERIAL_SET_DTR \
|
||||||
-Iinclude -Iinclude/joey -Isrc/core -Wall -Wextra \
|
-Iinclude -Iinclude/joey -Isrc/core -Wall -Wextra \
|
||||||
src/core/serial.c tests/host/serialHost.c \
|
src/core/serial.c tests/host/serialHost.c \
|
||||||
-o "$work/serialHost"
|
-o "$work/serialHost"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,48 @@ joeyNeedFile() {
|
||||||
JOEY_DOSBOX_ISOLATE=(--noprimaryconf --nolocalconf)
|
JOEY_DOSBOX_ISOLATE=(--noprimaryconf --nolocalconf)
|
||||||
|
|
||||||
|
|
||||||
|
# joeyExampleProgram NAME PORT -- the built program for an example name. A
|
||||||
|
# program that ships in its own folder with its DATA (Space Taxi on every
|
||||||
|
# port but the IIgs) is found inside that folder. When nothing is built yet
|
||||||
|
# it prints the flat path, so the caller's "not built" message names it.
|
||||||
|
joeyExampleProgram() {
|
||||||
|
local name="$1"
|
||||||
|
local bin="$JOEY_REPO/build/$2/bin"
|
||||||
|
local up="${name^^}"
|
||||||
|
local ext=""
|
||||||
|
local candidate
|
||||||
|
[ "$up" = "SPACETAXI" ] && up=STAXI
|
||||||
|
case "$2" in
|
||||||
|
iigs)
|
||||||
|
printf '%s\n' "$bin/$up"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
amiga)
|
||||||
|
# Mixed-case names, and Space Taxi is called Taxi there.
|
||||||
|
up="${name,,}"
|
||||||
|
case "$up" in
|
||||||
|
staxi|spacetaxi) up=taxi ;;
|
||||||
|
esac
|
||||||
|
up="${up^}"
|
||||||
|
;;
|
||||||
|
atarist) ext=".PRG" ;;
|
||||||
|
dos) ext=".EXE" ;;
|
||||||
|
x68000) ext=".X" ;;
|
||||||
|
*)
|
||||||
|
echo "$(basename "$0"): unknown port '$2' (iigs, amiga, atarist, dos or x68000)" >&2
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
for candidate in "$bin/$up/$up$ext" "$bin/$up$ext"; do
|
||||||
|
if [ -f "$candidate" ]; then
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "$bin/$up$ext"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# joeyFindWindow DISPLAY PID -- print the X window owned by process PID.
|
# joeyFindWindow DISPLAY PID -- print the X window owned by process PID.
|
||||||
# More robust than matching titles, which differ between emulator builds.
|
# More robust than matching titles, which differ between emulator builds.
|
||||||
joeyFindWindow() {
|
joeyFindWindow() {
|
||||||
|
|
|
||||||
132
scripts/lib/serialLoopPeer.py
Executable file
132
scripts/lib/serialLoopPeer.py
Executable file
|
|
@ -0,0 +1,132 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""serialLoopPeer.py - the host end of the serial loopback gates.
|
||||||
|
|
||||||
|
The guest runs SERTEST (examples/sertest): it sends a banner, then echoes
|
||||||
|
every byte it receives. This peer waits for the banner (the guest's
|
||||||
|
transmit works), then sends a probe line and expects it back (its receive
|
||||||
|
works too).
|
||||||
|
|
||||||
|
serialLoopPeer.py tcp-server PORT WALL [BANNER] listen; the emulator connects
|
||||||
|
serialLoopPeer.py tcp-client PORT WALL [BANNER] connect to the emulator
|
||||||
|
serialLoopPeer.py pty PATHFILE WALL [BANNER] make a raw pty pair, write the
|
||||||
|
guest end's path to PATHFILE
|
||||||
|
|
||||||
|
WALL is the wall-clock budget in seconds. Prints one LOOP PASS / LOOP FAIL
|
||||||
|
line; exit 0 on pass, 1 on fail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import tty
|
||||||
|
|
||||||
|
DEFAULT_BANNER = b"JLSERTEST-READY"
|
||||||
|
PROBE = b"PING12345"
|
||||||
|
RESEND_SECONDS = 3.0 # the probe is re-sent until it comes back
|
||||||
|
POLL_SECONDS = 0.5
|
||||||
|
CONNECT_RETRY_SECONDS = 0.5
|
||||||
|
READ_SIZE = 256
|
||||||
|
SHOWN_BYTES = 80
|
||||||
|
|
||||||
|
|
||||||
|
def acceptOne(port, wall):
|
||||||
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
server.bind(("127.0.0.1", port))
|
||||||
|
server.listen(1)
|
||||||
|
server.settimeout(wall)
|
||||||
|
try:
|
||||||
|
conn, _ = server.accept()
|
||||||
|
except socket.timeout:
|
||||||
|
return None
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def connectOne(port, wall, start):
|
||||||
|
while time.time() - start < wall:
|
||||||
|
try:
|
||||||
|
return socket.create_connection(("127.0.0.1", port), timeout=2)
|
||||||
|
except OSError:
|
||||||
|
time.sleep(CONNECT_RETRY_SECONDS)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def openPty(pathFile):
|
||||||
|
master, slave = os.openpty()
|
||||||
|
tty.setraw(slave)
|
||||||
|
tty.setraw(master)
|
||||||
|
# Publish atomically: the gate polls for the file.
|
||||||
|
with open(pathFile + ".tmp", "w") as f:
|
||||||
|
f.write(os.ttyname(slave))
|
||||||
|
os.rename(pathFile + ".tmp", pathFile)
|
||||||
|
return master
|
||||||
|
|
||||||
|
|
||||||
|
def loop(send, receive, wall, start, banner):
|
||||||
|
# send(bytes); receive() -> bytes, b"" for "nothing yet", None at EOF.
|
||||||
|
got = b""
|
||||||
|
sentAt = None
|
||||||
|
lastSend = 0.0
|
||||||
|
while time.time() - start < wall:
|
||||||
|
if banner in got and time.time() - lastSend > RESEND_SECONDS:
|
||||||
|
send(PROBE + b"\r\n")
|
||||||
|
lastSend = time.time()
|
||||||
|
if sentAt is None:
|
||||||
|
sentAt = len(got)
|
||||||
|
data = receive()
|
||||||
|
if data is None:
|
||||||
|
break
|
||||||
|
got += data
|
||||||
|
if sentAt is not None and PROBE in got[sentAt:]:
|
||||||
|
print("LOOP PASS: banner %r seen, probe echoed; stream=%r" % (banner, got[:SHOWN_BYTES]))
|
||||||
|
return 0
|
||||||
|
print("LOOP FAIL: stream=%r" % got[:SHOWN_BYTES + 40])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 4 or argv[1] not in ("tcp-server", "tcp-client", "pty"):
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
mode = argv[1]
|
||||||
|
wall = float(argv[3])
|
||||||
|
banner = argv[4].encode() if len(argv) > 4 else DEFAULT_BANNER
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
if mode == "pty":
|
||||||
|
master = openPty(argv[2])
|
||||||
|
|
||||||
|
def receive():
|
||||||
|
ready, _, _ = select.select([master], [], [], POLL_SECONDS)
|
||||||
|
if not ready:
|
||||||
|
return b""
|
||||||
|
try:
|
||||||
|
return os.read(master, READ_SIZE)
|
||||||
|
except OSError:
|
||||||
|
return b""
|
||||||
|
|
||||||
|
return loop(lambda data: os.write(master, data), receive, wall, start, banner)
|
||||||
|
|
||||||
|
port = int(argv[2])
|
||||||
|
conn = acceptOne(port, wall) if mode == "tcp-server" else connectOne(port, wall, start)
|
||||||
|
if conn is None:
|
||||||
|
print("LOOP FAIL: no connection on port %d" % port)
|
||||||
|
return 1
|
||||||
|
conn.settimeout(POLL_SECONDS)
|
||||||
|
|
||||||
|
def receive():
|
||||||
|
try:
|
||||||
|
data = conn.recv(READ_SIZE)
|
||||||
|
except socket.timeout:
|
||||||
|
return b""
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
return data if data else None
|
||||||
|
|
||||||
|
return loop(conn.sendall, receive, wall, start, banner)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
70
scripts/lib/x68kDisk.sh
Normal file
70
scripts/lib/x68kDisk.sh
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# Human68k floppy staging shared by joey-run and the X68000 gates and tools.
|
||||||
|
# Every run boots a copy of the stock system disk (HUMAN302.XDF from the
|
||||||
|
# x68kFirmware component) trimmed to what it takes to boot and run one
|
||||||
|
# program; most also carry the program, its DATA tree and its log on an
|
||||||
|
# emptied second disk in drive B, so a run can never corrupt what boots it.
|
||||||
|
# Source after joeyEnv.sh and call `joeyNeed X68K_DIR x68kFirmware` first.
|
||||||
|
|
||||||
|
X68K_SYSTEM_DISK="${X68K_DIR:-}/HUMAN302.XDF"
|
||||||
|
|
||||||
|
|
||||||
|
# x68kAddTree DISK DIR PREFIX -- copy every file under DIR onto DISK as
|
||||||
|
# PREFIX/<path>, upper-cased the way Human68k shows names.
|
||||||
|
x68kAddTree() {
|
||||||
|
local disk="$1"
|
||||||
|
local dir="$2"
|
||||||
|
local prefix="$3"
|
||||||
|
local files
|
||||||
|
local f
|
||||||
|
local rel
|
||||||
|
files="$(find "$dir" -type f | sort)"
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
rel="${f#"$dir"/}"
|
||||||
|
x68kXdf add "$disk" "$f" "$prefix/${rel^^}" >/dev/null || return 1
|
||||||
|
done <<< "$files"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# x68kBootDisk OUT LINE... -- OUT becomes a boot disk whose AUTOEXEC.BAT runs
|
||||||
|
# the given command lines (pass them single-quoted: 'B:\UBER.X'). The stock
|
||||||
|
# disk's keyboard, beep and font drivers go, and so do its utility folders
|
||||||
|
# (~900 KB, in subdirectories only deltree can free): nothing a JoeyLib
|
||||||
|
# program needs -- except \SYS\RSDRV.SYS, Human68k's RS-232C driver, which
|
||||||
|
# comes back: the stock CONFIG.SYS loads it (DEVICE = \SYS\RSDRV.SYS), and
|
||||||
|
# with it the serial add-on runs above 9600 bps and does RTS/CTS.
|
||||||
|
x68kBootDisk() {
|
||||||
|
local out="$1"
|
||||||
|
local name
|
||||||
|
shift
|
||||||
|
cp "$X68K_SYSTEM_DISK" "$out" || return 1
|
||||||
|
for name in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
||||||
|
x68kXdf delete "$out" "$name" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
for name in SYS HIS BIN BASIC2 ASK ETC; do
|
||||||
|
x68kXdf deltree "$out" "$name" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
# Optional: without it the serial add-on keeps the ROM's limits.
|
||||||
|
if x68kXdf extract "$X68K_SYSTEM_DISK" SYS/RSDRV.SYS "$out.rsdrv" >/dev/null 2>&1; then
|
||||||
|
x68kXdf add "$out" "$out.rsdrv" SYS/RSDRV.SYS >/dev/null || return 1
|
||||||
|
fi
|
||||||
|
rm -f "$out.rsdrv"
|
||||||
|
{ printf '%s\r\n' "$@"; printf '\x1a'; } > "$out.autoexec"
|
||||||
|
x68kXdf add "$out" "$out.autoexec" AUTOEXEC.BAT >/dev/null || return 1
|
||||||
|
rm -f "$out.autoexec"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# x68kDataDisk OUT -- OUT becomes an empty disk: the stock disk's BPB and
|
||||||
|
# boot sector with every file and folder freed (all 1221 clusters).
|
||||||
|
x68kDataDisk() {
|
||||||
|
cp "$X68K_SYSTEM_DISK" "$1" || return 1
|
||||||
|
x68kXdf empty "$1" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# x68kXdf ARGS... -- tools/xdftool.py (list, add, delete, deltree, empty,
|
||||||
|
# extract, free).
|
||||||
|
x68kXdf() {
|
||||||
|
python3 "$JOEY_REPO/tools/xdftool.py" "$@"
|
||||||
|
}
|
||||||
|
|
@ -37,10 +37,9 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
bin=$repo/build/x68000/bin/$PROG.X
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
|
bin=$(joeyExampleProgram "$prog" x68000) || exit 2
|
||||||
elf=$bin.elf
|
elf=$bin.elf
|
||||||
x68=$X68K_DIR
|
|
||||||
template=$x68/HUMAN302.XDF
|
|
||||||
joeyNeed X68K_ROOT toolchain:x68000
|
joeyNeed X68K_ROOT toolchain:x68000
|
||||||
export PATH="$X68K_ROOT/bin:$PATH"
|
export PATH="$X68K_ROOT/bin:$PATH"
|
||||||
|
|
||||||
|
|
@ -69,25 +68,13 @@ mouseXOff=$(m68k-xelf-nm "$elf" | awk '$3 == "gMouseX" { print strtonum("0x" $1)
|
||||||
work=$(mktemp -d -t joey-x68prof.XXXXXX)
|
work=$(mktemp -d -t joey-x68prof.XXXXXX)
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
printf 'B:\r\nB:\\%s.X\r\n\x1a' "$PROG" > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/boot.xdf" 'B:' "B:\\$PROG.X" || exit 1
|
||||||
cp "$template" "$work/boot.xdf"
|
x68kDataDisk "$work/data.xdf" || exit 1
|
||||||
cp "$template" "$work/data.xdf"
|
x68kXdf add "$work/data.xdf" "$bin" "$PROG.X" >/dev/null || exit 1
|
||||||
python3 "$repo/tools/xdftool.py" empty "$work/data.xdf" >/dev/null
|
# The program's DATA tree sits next to it: its own folder's for Space Taxi,
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
# the shared bin/DATA for the rest.
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
if [[ -d $(dirname "$bin")/DATA ]]; then
|
||||||
done
|
x68kAddTree "$work/data.xdf" "$(dirname "$bin")/DATA" DATA || exit 1
|
||||||
for d in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$repo/tools/xdftool.py" deltree "$work/boot.xdf" "$d" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$bin" "$PROG.X" >/dev/null
|
|
||||||
data_dir=$repo/build/x68000/bin/DATA
|
|
||||||
if [[ -d $data_dir ]]; then
|
|
||||||
while IFS= read -r f; do
|
|
||||||
rel=${f#"$data_dir"/}
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$f" \
|
|
||||||
"DATA/$(echo "$rel" | tr '[:lower:]' '[:upper:]')" >/dev/null
|
|
||||||
done < <(find "$data_dir" -type f | sort)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > "$work/prof.lua" <<LUA
|
cat > "$work/prof.lua" <<LUA
|
||||||
|
|
@ -223,7 +210,7 @@ end)
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
( cd "$work" && SDL_AUDIODRIVER=dummy timeout 3600 "$MAME" x68000 \
|
( cd "$work" && SDL_AUDIODRIVER=dummy timeout 3600 "$MAME" x68000 \
|
||||||
-bios ipl10 -rompath "$x68/roms" \
|
-bios ipl10 -rompath "$X68K_DIR/roms" \
|
||||||
-flop1 boot.xdf -flop2 data.xdf \
|
-flop1 boot.xdf -flop2 data.xdf \
|
||||||
-video none -sound none -nothrottle -skip_gameinfo \
|
-video none -sound none -nothrottle -skip_gameinfo \
|
||||||
-autoboot_script prof.lua </dev/null >mame.log 2>&1 ) || true
|
-autoboot_script prof.lua </dev/null >mame.log 2>&1 ) || true
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# verify-audio-all.sh - Run every port's audio gate and summarise.
|
# verify-all.sh - Run one kind of gate on every port and summarise.
|
||||||
#
|
#
|
||||||
# Driven by `make verify-audio`. Five gates, two kinds:
|
# Driven by `make verify-audio` and `make verify-serial`.
|
||||||
#
|
#
|
||||||
|
# audio: five gates, two kinds:
|
||||||
# iigs DOC wavetable + sound IRQ vector (register tap)
|
# iigs DOC wavetable + sound IRQ vector (register tap)
|
||||||
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
||||||
# atarist YM2149 write trace, with the pc (register tap)
|
# atarist YM2149 write trace, with the pc (register tap)
|
||||||
# amiga recorded sound, four loudness phases (Paula has no tap)
|
# amiga recorded sound, four loudness phases (Paula has no tap)
|
||||||
# dos recorded sound, four loudness phases (SB has no tap)
|
# dos recorded sound, four loudness phases (SB has no tap)
|
||||||
#
|
#
|
||||||
# SEQUENTIAL, deliberately. The two recording gates drive the emulator on
|
# serial: the host check of the shared layer, then each port's loopback
|
||||||
|
# (banner out, probe echoed back):
|
||||||
|
# host scripts/check-serial.sh (no emulator)
|
||||||
|
# iigs SCC modem port -> MAME null_modem
|
||||||
|
# x68000 SCC channel A -> MAME null_modem
|
||||||
|
# atarist Modem port -> Hatari pty
|
||||||
|
# amiga serial.device -> FS-UAE TCP
|
||||||
|
# dos COM1 -> DOSBox nullmodem
|
||||||
|
#
|
||||||
|
# SEQUENTIAL, deliberately. The two recording audio gates drive the emulator on
|
||||||
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
||||||
# second emulator competing for CPU can slide a phase boundary and fail a
|
# second emulator competing for CPU can slide a phase boundary and fail a
|
||||||
# gate that is actually fine. Running five emulators at once also makes
|
# gate that is actually fine. Running five emulators at once also makes
|
||||||
|
|
@ -27,17 +37,23 @@
|
||||||
# roll-up. Gate stdout/stderr is echoed as it happens, so a failure shows
|
# roll-up. Gate stdout/stderr is echoed as it happens, so a failure shows
|
||||||
# its diagnosis here and not just a verdict.
|
# its diagnosis here and not just a verdict.
|
||||||
#
|
#
|
||||||
# Usage: scripts/verify-audio-all.sh [port ...]
|
# Usage: scripts/verify-all.sh audio|serial [port ...]
|
||||||
# with no arguments, all five. Otherwise any of:
|
# with no ports, all of that kind's. Otherwise any of:
|
||||||
# iigs x68000 atarist amiga dos
|
# iigs x68000 atarist amiga dos (and host, for serial)
|
||||||
# Env: AUDIO_ALL_LOG also append the full transcript to this file
|
# Env: VERIFY_ALL_LOG also append the full transcript to this file
|
||||||
# Requires: whatever each selected gate requires; missing ones SKIP.
|
# Requires: whatever each selected gate requires; missing ones SKIP.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
label=verify-audio-all
|
kind=${1:-}
|
||||||
|
shift || true
|
||||||
|
label="verify-all $kind"
|
||||||
|
|
||||||
ALL_PORTS="iigs x68000 atarist amiga dos"
|
case $kind in
|
||||||
|
audio) ALL_PORTS="iigs x68000 atarist amiga dos" ;;
|
||||||
|
serial) ALL_PORTS="host iigs x68000 atarist amiga dos" ;;
|
||||||
|
*) echo "usage: scripts/verify-all.sh audio|serial [port ...]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
ports=${*:-$ALL_PORTS}
|
ports=${*:-$ALL_PORTS}
|
||||||
|
|
||||||
for p in $ports; do
|
for p in $ports; do
|
||||||
|
|
@ -48,12 +64,11 @@ for p in $ports; do
|
||||||
done
|
done
|
||||||
|
|
||||||
gateFor() { # $1 = port -> script + args
|
gateFor() { # $1 = port -> script + args
|
||||||
case $1 in
|
case $kind:$1 in
|
||||||
iigs) echo "scripts/verify-iigs-audio.sh staxi" ;;
|
audio:iigs) echo "scripts/verify-iigs-audio.sh staxi" ;;
|
||||||
x68000) echo "scripts/verify-x68000-audio.sh" ;;
|
audio:*) echo "scripts/verify-$1-audio.sh" ;;
|
||||||
atarist) echo "scripts/verify-atarist-audio.sh" ;;
|
serial:host) echo "scripts/check-serial.sh" ;;
|
||||||
amiga) echo "scripts/verify-amiga-audio.sh" ;;
|
serial:*) echo "scripts/verify-$1-serial.sh" ;;
|
||||||
dos) echo "scripts/verify-dos-audio.sh" ;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,8 +82,8 @@ for port in $ports; do
|
||||||
echo
|
echo
|
||||||
echo "=== $label: $port ($gate) ==="
|
echo "=== $label: $port ($gate) ==="
|
||||||
start=$SECONDS
|
start=$SECONDS
|
||||||
if [ -n "${AUDIO_ALL_LOG:-}" ]; then
|
if [ -n "${VERIFY_ALL_LOG:-}" ]; then
|
||||||
( cd "$repo" && $gate ) 2>&1 | tee -a "$AUDIO_ALL_LOG"
|
( cd "$repo" && $gate ) 2>&1 | tee -a "$VERIFY_ALL_LOG"
|
||||||
rc=${PIPESTATUS[0]}
|
rc=${PIPESTATUS[0]}
|
||||||
else
|
else
|
||||||
( cd "$repo" && $gate )
|
( cd "$repo" && $gate )
|
||||||
|
|
@ -106,5 +121,5 @@ if [ -n "$skipped" ]; then
|
||||||
echo "$label: INCOMPLETE - skipped$skipped (build those ports, then re-run)" >&2
|
echo "$label: INCOMPLETE - skipped$skipped (build those ports, then re-run)" >&2
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
echo "$label: PASS (all $(count "$passed") audio gates)"
|
echo "$label: PASS (all $(count "$passed") $kind gates)"
|
||||||
exit 0
|
exit 0
|
||||||
67
scripts/verify-amiga-serial.sh
Executable file
67
scripts/verify-amiga-serial.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-amiga-serial.sh - both-directions RS-232 gate for the Amiga port.
|
||||||
|
#
|
||||||
|
# Boots SerTest (examples/sertest: banner, then echo) off a directory hard
|
||||||
|
# drive in FS-UAE with the serial port on a TCP socket, and has the host
|
||||||
|
# peer (scripts/lib/serialLoopPeer.py) wait for the banner, send a probe and
|
||||||
|
# expect it echoed. That exercises the chain: m68k-amigaos-gcc -> AmigaDOS ->
|
||||||
|
# serial.device -> JoeyLib serial HAL -> Paula's UART -> host.
|
||||||
|
#
|
||||||
|
# Kickstart 3.1 has no serial.device in ROM: AmigaOS loads it from DEVS:, so
|
||||||
|
# the boot drive gets Devs/serial.device from the installer (amigaSerial).
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-amiga-serial.sh
|
||||||
|
# Env: AMIGA_SERIAL_PORT TCP port FS-UAE listens on (default 6801)
|
||||||
|
# Requires: fs-uae, Xvfb; build/amiga/bin/SerTest (make amiga); the
|
||||||
|
# Kickstart and serial.device (install.sh install emulator:amiga).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed FSUAE fsuae
|
||||||
|
joeyNeed KICKSTART_ROM kickstart
|
||||||
|
joeyNeed AMIGA_SERIAL_DEVICE amigaSerial
|
||||||
|
|
||||||
|
bin="$repo/build/amiga/bin/SerTest"
|
||||||
|
port="${AMIGA_SERIAL_PORT:-6801}"
|
||||||
|
wall=60
|
||||||
|
[ -f "$bin" ] || { echo "verify-amiga-serial: build SerTest first (make amiga)" >&2; exit 2; }
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-amiga-serial.XXXXXX)
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "${fsuaePid:-}"
|
||||||
|
joeyStop "${xvfbPid:-}"
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/hd/s" "$work/hd/Devs" "$work/home"
|
||||||
|
cp "$bin" "$work/hd/SerTest"
|
||||||
|
cp "$AMIGA_SERIAL_DEVICE" "$work/hd/Devs/serial.device"
|
||||||
|
printf 'stack 32768\n:SerTest\n' > "$work/hd/s/startup-sequence"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 1700))"
|
||||||
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
|
xvfbPid=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# serial_port tcp://.../wait: FS-UAE listens and holds the emulation until
|
||||||
|
# the peer connects, so the banner is never sent to nobody.
|
||||||
|
HOME="$work/home" DISPLAY="$display" SDL_AUDIODRIVER=dummy ALSOFT_DRIVERS=null "$FSUAE" \
|
||||||
|
--amiga_model=A500 --kickstart_file="$KICKSTART_ROM" \
|
||||||
|
--hard_drive_0="$work/hd" --hard_drive_0_label=JOEYLIB \
|
||||||
|
--fade_out_duration=0 --joystick_port_0=none --joystick_port_1=none \
|
||||||
|
--amiga_enable_serial_port=1 --serial_port="tcp://127.0.0.1:$port/wait" \
|
||||||
|
> "$work/fsuae.log" 2>&1 &
|
||||||
|
fsuaePid=$!
|
||||||
|
|
||||||
|
result=$(python3 "$repo/scripts/lib/serialLoopPeer.py" tcp-client "$port" "$wall")
|
||||||
|
rc=$?
|
||||||
|
echo "$result"
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
echo "verify-amiga-serial: PASS (banner out, probe echoed through serial.device)"
|
||||||
|
else
|
||||||
|
echo "verify-amiga-serial: FAIL" >&2
|
||||||
|
tail -n 5 "$work/fsuae.log" >&2
|
||||||
|
fi
|
||||||
|
exit "$rc"
|
||||||
59
scripts/verify-atarist-serial.sh
Executable file
59
scripts/verify-atarist-serial.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-atarist-serial.sh - both-directions RS-232 gate for the Atari ST port.
|
||||||
|
#
|
||||||
|
# Runs SERTEST.PRG (examples/sertest: banner, then echo) under Hatari and
|
||||||
|
# EmuTOS with the Modem port on a pseudo-terminal whose other end is the host
|
||||||
|
# peer (scripts/lib/serialLoopPeer.py): it waits for the banner, sends a
|
||||||
|
# probe and expects it echoed. That exercises the chain: m68k-atari-mint-gcc
|
||||||
|
# -> the BIOS Aux channel -> JoeyLib serial HAL -> the MFP's USART -> host.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-atarist-serial.sh
|
||||||
|
# Requires: hatari, EmuTOS (install.sh install emulator:atarist);
|
||||||
|
# build/atarist/bin/SERTEST.PRG (make atarist).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed HATARI hatari
|
||||||
|
joeyNeed EMUTOS_IMG emutos
|
||||||
|
|
||||||
|
prg="$repo/build/atarist/bin/SERTEST.PRG"
|
||||||
|
wall=60
|
||||||
|
[ -f "$prg" ] || { echo "verify-atarist-serial: build SERTEST.PRG first (make atarist)" >&2; exit 2; }
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-atarist-serial.XXXXXX)
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "${hatariPid:-}"
|
||||||
|
joeyStop "${peerPid:-}"
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/c"
|
||||||
|
cp "$prg" "$work/c/SERTEST.PRG"
|
||||||
|
|
||||||
|
python3 "$repo/scripts/lib/serialLoopPeer.py" pty "$work/pty" "$wall" > "$work/peer.log" 2>&1 &
|
||||||
|
peerPid=$!
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
[ -f "$work/pty" ] && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
[ -f "$work/pty" ] || { echo "verify-atarist-serial: the peer made no pty" >&2; exit 1; }
|
||||||
|
pty=$(cat "$work/pty")
|
||||||
|
|
||||||
|
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy "$HATARI" --tos "$EMUTOS_IMG" \
|
||||||
|
--harddrive "$work/c" --gemdos-drive C --drive-a off --drive-b off \
|
||||||
|
--sound off --fast-forward on --rs232-in "$pty" --rs232-out "$pty" \
|
||||||
|
--auto 'C:\SERTEST.PRG' > "$work/hatari.log" 2>&1 &
|
||||||
|
hatariPid=$!
|
||||||
|
|
||||||
|
wait "$peerPid"
|
||||||
|
rc=$?
|
||||||
|
peerPid=""
|
||||||
|
cat "$work/peer.log"
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
echo "verify-atarist-serial: PASS (banner out, probe echoed through the Modem port)"
|
||||||
|
else
|
||||||
|
echo "verify-atarist-serial: FAIL" >&2
|
||||||
|
fi
|
||||||
|
exit "$rc"
|
||||||
60
scripts/verify-dos-serial.sh
Executable file
60
scripts/verify-dos-serial.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dos-serial.sh - both-directions RS-232 gate for the DOS port.
|
||||||
|
#
|
||||||
|
# Runs SERTEST.EXE (examples/sertest: banner, then echo) in DOSBox Staging
|
||||||
|
# with COM1 as a nullmodem connecting to the host peer
|
||||||
|
# (scripts/lib/serialLoopPeer.py), which waits for the banner, sends a probe
|
||||||
|
# and expects it echoed. That exercises the chain: DJGPP -> DPMI -> JoeyLib's
|
||||||
|
# interrupt-driven 8250/16550 HAL -> DOSBox's UART -> host.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dos-serial.sh
|
||||||
|
# Env: DOS_SERIAL_PORT TCP port the peer listens on (default 6802)
|
||||||
|
# Requires: DOSBox Staging, Xvfb; build/dos/bin/SERTEST.EXE (make dos).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
|
||||||
|
exe="$repo/build/dos/bin/SERTEST.EXE"
|
||||||
|
port="${DOS_SERIAL_PORT:-6802}"
|
||||||
|
wall=38
|
||||||
|
runSeconds=40 # SERTEST echoes forever: DOSBox ends by timeout
|
||||||
|
[ -f "$exe" ] || { echo "verify-dos-serial: build SERTEST.EXE first (make dos)" >&2; exit 2; }
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dos-serial.XXXXXX)
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "${peerPid:-}"
|
||||||
|
joeyStop "${xvfbPid:-}"
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/c" "$work/xdg"
|
||||||
|
cp "$exe" "$work/c/SERTEST.EXE"
|
||||||
|
|
||||||
|
python3 "$repo/scripts/lib/serialLoopPeer.py" tcp-server "$port" "$wall" > "$work/peer.log" 2>&1 &
|
||||||
|
peerPid=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 1300))"
|
||||||
|
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||||
|
xvfbPid=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Foreground under timeout -s KILL, so nothing can outlive the gate.
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$runSeconds" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--set "serial1=nullmodem server:127.0.0.1 port:$port transparent:1" \
|
||||||
|
-c "MOUNT C $work/c" -c "C:" -c "SERTEST.EXE" -c "exit" > "$work/dosbox.log" 2>&1
|
||||||
|
|
||||||
|
wait "$peerPid"
|
||||||
|
rc=$?
|
||||||
|
peerPid=""
|
||||||
|
cat "$work/peer.log"
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
echo "verify-dos-serial: PASS (banner out, probe echoed through COM1)"
|
||||||
|
else
|
||||||
|
echo "verify-dos-serial: FAIL" >&2
|
||||||
|
fi
|
||||||
|
exit "$rc"
|
||||||
360
scripts/verify-iigs-serial-loss.sh
Executable file
360
scripts/verify-iigs-serial-loss.sh
Executable file
|
|
@ -0,0 +1,360 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-iigs-serial-loss.sh - Receive-loss gate for the Apple IIgs serial add-on.
|
||||||
|
#
|
||||||
|
# SERLOSS (examples/serloss/serloss.c) fills and presents the whole screen in a
|
||||||
|
# loop while this script's peer streams 4096 bytes (byte k = k & 0xFF) into the
|
||||||
|
# port at 9600 baud. The probe counts, sequence-checks and sums what arrives and
|
||||||
|
# leaves the result in a mailbox at $E1:9DD0, which the MAME Lua reads.
|
||||||
|
#
|
||||||
|
# PASS all 4096 bytes arrived, in order, with the right sum
|
||||||
|
# FAIL anything missing or out of sequence
|
||||||
|
# SKIP (exit 2) a tool or ROM is missing, or MAME did not confirm the
|
||||||
|
# real SCC receive FIFO depth (below)
|
||||||
|
#
|
||||||
|
# scripts/verify-iigs-serial-loss.sh scc modem port (SCC channel B)
|
||||||
|
# scripts/verify-iigs-serial-loss.sh ssc Super Serial Card in slot 2
|
||||||
|
#
|
||||||
|
# The emulator must model the real receive FIFO depth, or a receiver that
|
||||||
|
# falls behind still looks fine. The installed MAME's
|
||||||
|
# installer/patches/mame/0002-z80scc-rx-fifo.patch gives the Z8530 a 512-byte
|
||||||
|
# receive FIFO by default, so polled receivers survive in the other gates;
|
||||||
|
# this script runs it with JOEY_Z80SCC_RX_FIFO=3, the real chip's depth. MAME
|
||||||
|
# then prints "RX FIFO depth 3 (JOEY_Z80SCC_RX_FIFO)" per SCC channel, and an
|
||||||
|
# scc PASS counts only with that line (installer/patches/mame/README.md). The
|
||||||
|
# 6551 on the Super Serial Card has no FIFO to widen.
|
||||||
|
#
|
||||||
|
# The card's interrupt switch (SW2-6) is OFF by default, as on a real Super
|
||||||
|
# Serial Card; the Lua turns it on so the 6551's IRQ reaches the slot. MAME
|
||||||
|
# needs the card's firmware, a2ssc.zip, which the iigsFirmware component
|
||||||
|
# installs into $IIGS_ROMPATH.
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# SERLOSS_MAME MAME binary (default $MAME); it runs with JOEY_Z80SCC_RX_FIFO=3.
|
||||||
|
# An scc PASS from one that does not report that depth (a
|
||||||
|
# build without the 0002 patch, whose FIFO is already the
|
||||||
|
# real 3) stands, with a warning; from $MAME it is a SKIP,
|
||||||
|
# since that means the installed MAME predates the switch
|
||||||
|
# SERLOSS_BIN probe binary (default build/iigs/bin/SERLOSS or SERLOSSS)
|
||||||
|
# SERLOSS_PORT TCP port for the bitbanger (default 45241)
|
||||||
|
# SERLOSS_MACHINE apple2gs (ROM 03, default) or apple2gsr1 (ROM 01)
|
||||||
|
# SERLOSS_LAUNCHER set to boot straight into the probe instead of driving
|
||||||
|
# the Finder (whose timeline only fits ROM 03)
|
||||||
|
# SERLOSS_BAUD peer line rate, must match the probe's -DSERLOSS_BAUD (default 9600)
|
||||||
|
# SERLOSS_ROMPATH MAME rompath (default $IIGS_ROMPATH, which holds a2ssc)
|
||||||
|
# SERLOSS_WALL wall-clock cap for MAME in seconds (default 600)
|
||||||
|
# SERLOSS_MAX_FRAME emulated-frame cap (default 12000)
|
||||||
|
# SERLOSS_TAP file: log every data-register read with its emulated time
|
||||||
|
# SERLOSS_PADDLE plug MAME's joystick into the game port and hold both paddles
|
||||||
|
# at this value (0..255) for a -DSERLOSS_JOY probe; 255, full
|
||||||
|
# deflection, is the longest paddle read there is
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
joeyNeed MAME mame
|
||||||
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
||||||
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
||||||
|
|
||||||
|
mode="${1:-scc}"
|
||||||
|
case "$mode" in
|
||||||
|
scc) name=SERLOSS ;;
|
||||||
|
ssc) name=SERLOSSS ;;
|
||||||
|
*) echo "verify-iigs-serial-loss: mode must be scc or ssc" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
mame="${SERLOSS_MAME:-$MAME}"
|
||||||
|
rompath="${SERLOSS_ROMPATH:-$IIGS_ROMPATH}"
|
||||||
|
bin="${SERLOSS_BIN:-$repo/build/iigs/bin/$name}"
|
||||||
|
PORT="${SERLOSS_PORT:-45241}"
|
||||||
|
WALL="${SERLOSS_WALL:-600}"
|
||||||
|
maxFrame="${SERLOSS_MAX_FRAME:-12000}"
|
||||||
|
total=4096
|
||||||
|
|
||||||
|
[ -x "$mame" ] || { echo "verify-iigs-serial-loss: no MAME at $mame" >&2; exit 2; }
|
||||||
|
[ -f "$bin" ] || { echo "verify-iigs-serial-loss: build $bin first" >&2; exit 2; }
|
||||||
|
if [ "$mode" = ssc ] && [ -z "${SERLOSS_ROMPATH:-}" ]; then
|
||||||
|
joeyNeedFile "$IIGS_ROMPATH/a2ssc.zip" iigsFirmware
|
||||||
|
fi
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-iigs-loss.XXXXXX)
|
||||||
|
peer_pid=""
|
||||||
|
cleanup() {
|
||||||
|
[ -n "$peer_pid" ] && kill -9 "$peer_pid" 2>/dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/bin"
|
||||||
|
cp "$bin" "$work/bin/$name"
|
||||||
|
BINDIR="$work/bin" JOEY_DISK_EXAMPLES="$name" bash "$repo/scripts/make-iigs-disk.sh" "$work/loss.2mg" >/dev/null 2>&1 || {
|
||||||
|
echo "verify-iigs-serial-loss: FAIL - could not build the probe disk" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
# SERLOSS_LAUNCHER=1 boots straight into the probe (make-iigs-boot-disk.sh)
|
||||||
|
# instead of driving the Finder, whose keystroke timeline is calibrated for
|
||||||
|
# ROM 03 only.
|
||||||
|
if [ -n "${SERLOSS_LAUNCHER:-}" ]; then
|
||||||
|
bash "$repo/scripts/make-iigs-boot-disk.sh" "$work/boot.po" "$name" >/dev/null 2>&1 || {
|
||||||
|
echo "verify-iigs-serial-loss: FAIL - could not build the launcher boot disk" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
finderAt=999999
|
||||||
|
else
|
||||||
|
cp "$GSOS_SYSTEM_DISK" "$work/boot.po"
|
||||||
|
finderAt=3000
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Peer: listen (MAME connects out), wait for the probe's READY, stream the
|
||||||
|
# pattern, then keep draining whatever the probe sends until MAME goes away.
|
||||||
|
cat > "$work/peer.py" <<'PY'
|
||||||
|
import socket, sys, time
|
||||||
|
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
total = int(sys.argv[2])
|
||||||
|
log = open(sys.argv[3], "w")
|
||||||
|
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
srv.bind(("127.0.0.1", port))
|
||||||
|
srv.listen(1)
|
||||||
|
srv.settimeout(600)
|
||||||
|
conn, _ = srv.accept()
|
||||||
|
conn.settimeout(1.0)
|
||||||
|
seen = b""
|
||||||
|
sent = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
chunk = conn.recv(256)
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
except OSError:
|
||||||
|
break
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
seen += chunk
|
||||||
|
if not sent and b"READY" in seen:
|
||||||
|
conn.sendall(bytes(k & 0xFF for k in range(total)))
|
||||||
|
sent = True
|
||||||
|
log.write("peer: READY seen, sent %d bytes\n" % total)
|
||||||
|
log.flush()
|
||||||
|
log.write("peer: received %r\n" % seen[:64])
|
||||||
|
log.close()
|
||||||
|
PY
|
||||||
|
python3 "$work/peer.py" "$PORT" "$total" "$work/peer.log" &
|
||||||
|
peer_pid=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if [ "$mode" = ssc ]; then
|
||||||
|
serialArgs=(-sl2 ssc -sl2:ssc:rs232 null_modem -bitb "socket.127.0.0.1:$PORT")
|
||||||
|
dipPort=":sl2:ssc:DSWX"
|
||||||
|
modemTag=":sl2:ssc:rs232:null_modem"
|
||||||
|
else
|
||||||
|
serialArgs=(-modem null_modem -bitb "socket.127.0.0.1:$PORT")
|
||||||
|
dipPort=""
|
||||||
|
modemTag=":modem:null_modem"
|
||||||
|
fi
|
||||||
|
# The apple2gs has nothing on its game port by default (every paddle reads 0).
|
||||||
|
paddle="${SERLOSS_PADDLE:-}"
|
||||||
|
gameArgs=()
|
||||||
|
if [ -n "$paddle" ]; then
|
||||||
|
gameArgs=(-gameio joy)
|
||||||
|
fi
|
||||||
|
paddleValue="${paddle:-0}"
|
||||||
|
case "${SERLOSS_BAUD:-9600}" in
|
||||||
|
2400) baudCode=5 ;;
|
||||||
|
4800) baudCode=6 ;;
|
||||||
|
9600) baudCode=7 ;;
|
||||||
|
19200) baudCode=9 ;;
|
||||||
|
*) echo "verify-iigs-serial-loss: unsupported SERLOSS_BAUD" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat > "$work/loss.lua" <<LUA
|
||||||
|
local cpu = manager.machine.devices[":maincpu"]
|
||||||
|
local mem = cpu.spaces["program"]
|
||||||
|
local nat = manager.machine.natkeyboard
|
||||||
|
local frame = 0
|
||||||
|
local idx = 1
|
||||||
|
local launched = false
|
||||||
|
|
||||||
|
local function field(port, name)
|
||||||
|
local p = manager.machine.ioport.ports[port]
|
||||||
|
if p == nil then return nil end
|
||||||
|
return p.fields[name]
|
||||||
|
end
|
||||||
|
local key_cmd = field(":macadb:KEY3", "Command / Open Apple")
|
||||||
|
local function press(f) if f then f:set_value(1) end end
|
||||||
|
local function release(f) if f then f:set_value(0) end end
|
||||||
|
|
||||||
|
local function u16(off)
|
||||||
|
return mem:read_u8(0xE19DD0 + off) | (mem:read_u8(0xE19DD1 + off) << 8)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The stick spread bytes are biased by 128 (serloss.c MB_JOY_BIAS).
|
||||||
|
local function s8(off)
|
||||||
|
return mem:read_u8(0xE19DD0 + off) - 128
|
||||||
|
end
|
||||||
|
|
||||||
|
local function report()
|
||||||
|
io.write(string.format("VERIFY-IIGS-LOSS frame=%d sig=%02X%02X phase=%02X received=%d inSequence=%d breaks=%d presents=%d ticks=%d overruns=%d dropped=%d sum=%04X now=%d fillTicks=%d fillPresentTicks=%d joyPolls=%d joyX=%d..%d joyY=%d..%d sfxShots=%d\n",
|
||||||
|
frame, mem:read_u8(0xE19DD0), mem:read_u8(0xE19DD1), mem:read_u8(0xE19DD2),
|
||||||
|
u16(4), u16(6), u16(8), u16(10), u16(12), u16(14), u16(16), u16(18), u16(20), u16(22), u16(24),
|
||||||
|
u16(26), s8(28), s8(29), s8(30), s8(31), u16(32)))
|
||||||
|
io.flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- With the paddles in play, serve VERTCNT/HORIZCNT (C02E/C02F) with the
|
||||||
|
-- real machine's 262-line mapping (VERTCNT*2 + HORIZCNT bit 7 = 250..511).
|
||||||
|
-- MAME's apple2gs get_vpos() wraps with -261 instead of -262, so its counter
|
||||||
|
-- repeats line 501 and never reads 250; the tight joystick read times its
|
||||||
|
-- passes with these counters, and a span across the repeat or the wrap is 65
|
||||||
|
-- ticks (~6 counts) off. The beam position comes from time_until_pos: MAME's
|
||||||
|
-- Lua has no screen vpos/hpos.
|
||||||
|
local vcTaps = {}
|
||||||
|
local function beamCountersFix()
|
||||||
|
if "$paddle" == "" then return end
|
||||||
|
local scr = manager.machine.screens[":screen"]
|
||||||
|
local function beam()
|
||||||
|
local e = scr.frame_period - scr:time_until_pos(0, 0)
|
||||||
|
if e >= scr.frame_period then e = e - scr.frame_period end
|
||||||
|
if e < 0 then e = e + scr.frame_period end
|
||||||
|
e = e + scr.pixel_period / 2
|
||||||
|
local v = math.floor(e / scr.scan_period)
|
||||||
|
return v % 262, math.floor((e - v * scr.scan_period) / scr.pixel_period)
|
||||||
|
end
|
||||||
|
local function counters(offset, data, mask)
|
||||||
|
local v, hp = beam()
|
||||||
|
local line = v + 501
|
||||||
|
if line > 511 then line = line - 262 end
|
||||||
|
if (offset & 0xFF) == 0x2E then return line >> 1 end
|
||||||
|
local h = hp // 11
|
||||||
|
if h > 0 then h = h + 0x40 end
|
||||||
|
return h | ((line & 1) << 7)
|
||||||
|
end
|
||||||
|
for _, bank in ipairs({0x000000, 0xE00000, 0xE10000}) do
|
||||||
|
vcTaps[#vcTaps + 1] = mem:install_read_tap(bank + 0xC02E, bank + 0xC02F, "beam" .. bank, counters)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hold both paddles of MAME's joystick (-gameio joy) still.
|
||||||
|
local function paddlesHold()
|
||||||
|
if "$paddle" == "" then return end
|
||||||
|
for _, name in ipairs({"joystick_1_x", "joystick_1_y"}) do
|
||||||
|
local p = manager.machine.ioport.ports[":gameio:joy:" .. name]
|
||||||
|
if p ~= nil then
|
||||||
|
for _, f in pairs(p.fields) do f:set_value($paddleValue) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sscInterruptsOn()
|
||||||
|
local p = manager.machine.ioport.ports["$dipPort"]
|
||||||
|
if p == nil then return end
|
||||||
|
local f = p.fields["Interrupts"]
|
||||||
|
if f ~= nil then f.user_value = 0 end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Optional (SERLOSS_TAP=file): log every CPU read of the data register with
|
||||||
|
-- the emulated time, for a receive-latency histogram.
|
||||||
|
local tapFile = "${SERLOSS_TAP:-}"
|
||||||
|
local taps = {}
|
||||||
|
local function installTaps()
|
||||||
|
if tapFile == "" then return end
|
||||||
|
local out = io.open(tapFile, "w")
|
||||||
|
local dataReg = ("$mode" == "ssc") and 0xC0A8 or 0xC03A
|
||||||
|
for _, bank in ipairs({0x000000, 0xE00000, 0xE10000}) do
|
||||||
|
taps[#taps + 1] = mem:install_read_tap(bank + dataReg, bank + dataReg, "rx" .. bank,
|
||||||
|
function(offset, data, mask)
|
||||||
|
out:write(string.format("%.7f %d\n", manager.machine.time:as_double(), data & 0xFF))
|
||||||
|
out:flush()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The null_modem's line rate is a configuration setting (default 9600).
|
||||||
|
local function peerBaud()
|
||||||
|
for _, name in ipairs({"RS232_TXBAUD", "RS232_RXBAUD"}) do
|
||||||
|
local p = manager.machine.ioport.ports["$modemTag:" .. name]
|
||||||
|
if p ~= nil then
|
||||||
|
for _, f in pairs(p.fields) do f.user_value = $baudCode end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local steps = {
|
||||||
|
{10, function() if "$dipPort" ~= "" then sscInterruptsOn() end; peerBaud(); paddlesHold(); beamCountersFix(); installTaps() end},
|
||||||
|
{11, function() if $finderAt > 3000 then launched = true end end},
|
||||||
|
{$finderAt, function() nat:post("J") end},
|
||||||
|
{$finderAt + 120, function() press(key_cmd) end},
|
||||||
|
{$finderAt + 126, function() nat:post("o") end},
|
||||||
|
{$finderAt + 180, function() release(key_cmd) end},
|
||||||
|
{$finderAt + 540, function() nat:post("$name") end},
|
||||||
|
{$finderAt + 660, function() press(key_cmd) end},
|
||||||
|
{$finderAt + 666, function() nat:post("o") end},
|
||||||
|
{$finderAt + 720, function() release(key_cmd); launched = true end},
|
||||||
|
}
|
||||||
|
|
||||||
|
emu.register_frame_done(function()
|
||||||
|
frame = frame + 1
|
||||||
|
while idx <= #steps and frame >= steps[idx][1] do
|
||||||
|
steps[idx][2]()
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
if launched and frame % 60 == 0 then
|
||||||
|
local sig = mem:read_u8(0xE19DD0) == 0xA5 and mem:read_u8(0xE19DD1) == 0x5A
|
||||||
|
local phase = mem:read_u8(0xE19DD2)
|
||||||
|
if sig and (phase == 0x05 or phase == 0xEE) then
|
||||||
|
report(); manager.machine:exit()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if frame >= $maxFrame then
|
||||||
|
report(); manager.machine:exit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
LUA
|
||||||
|
|
||||||
|
cd "$work"
|
||||||
|
out=$(JOEY_Z80SCC_RX_FIFO=3 QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$WALL" "$mame" "${SERLOSS_MACHINE:-apple2gs}" \
|
||||||
|
-rompath "$rompath" \
|
||||||
|
-flop3 "$work/boot.po" -flop4 "$work/loss.2mg" \
|
||||||
|
"${serialArgs[@]}" "${gameArgs[@]}" \
|
||||||
|
-video none -sound none -nothrottle \
|
||||||
|
-autoboot_script "$work/loss.lua" </dev/null 2>&1) || true
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
kill -9 "$peer_pid" 2>/dev/null
|
||||||
|
peer_pid=""
|
||||||
|
cat "$work/peer.log" 2>/dev/null
|
||||||
|
|
||||||
|
line=$(echo "$out" | grep -E '^VERIFY-IIGS-LOSS ' | tail -1)
|
||||||
|
echo "$line"
|
||||||
|
if [ -z "$line" ]; then
|
||||||
|
echo "verify-iigs-serial-loss: FAIL - MAME produced no report" >&2
|
||||||
|
echo "$out" | tail -10 >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
get() { echo "$line" | grep -oE "$1=[0-9A-F]+" | cut -d= -f2; }
|
||||||
|
phase=$(get phase); received=$(get received); inSeq=$(get inSequence); breaks=$(get breaks); sum=$(get sum)
|
||||||
|
presents=$(get presents); fillTicks=$(get fillTicks); bothTicks=$(get fillPresentTicks)
|
||||||
|
if [ -n "$bothTicks" ] && [ -n "$fillTicks" ]; then
|
||||||
|
# 60 fills vs 60 fill+present pairs, in 60 Hz ticks: the difference is 60 presents.
|
||||||
|
echo " full-screen present: $(( (bothTicks - fillTicks) * 1000000 / 3600 )) us each (fills $fillTicks ticks, fills+presents $bothTicks ticks)"
|
||||||
|
fi
|
||||||
|
joyPolls=$(get joyPolls)
|
||||||
|
if [ -n "$joyPolls" ] && [ "$joyPolls" != 0 ]; then
|
||||||
|
# A -DSERLOSS_JOY probe calibrates the held stick first, so every reading
|
||||||
|
# should be 0: the spread is how far interrupts pushed the paddle reads.
|
||||||
|
echo " joystick: $joyPolls polls, paddles held at ${paddle:-0}, X read $(echo "$line" | grep -oE 'joyX=[-0-9.]+' | cut -d= -f2), Y read $(echo "$line" | grep -oE 'joyY=[-0-9.]+' | cut -d= -f2)"
|
||||||
|
fi
|
||||||
|
if [ "$phase" = 05 ] && [ "$received" = "$total" ] && [ "$inSeq" = "$total" ] && [ "$breaks" = 0 ] && [ "$sum" = F800 ]; then
|
||||||
|
# An scc PASS proves something only at the real FIFO depth (see the header).
|
||||||
|
if [ "$mode" = scc ] && ! grep -qF 'RX FIFO depth 3 (JOEY_Z80SCC_RX_FIFO)' <<< "$out"; then
|
||||||
|
if [ -z "${SERLOSS_MAME:-}" ]; then
|
||||||
|
echo "verify-iigs-serial-loss: SKIP - $mame did not confirm RX FIFO depth 3 (installed before JOEY_Z80SCC_RX_FIFO existed?), so a 512-byte SCC FIFO may be hiding loss; run: ./install.sh install mame" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "verify-iigs-serial-loss: WARNING - $mame did not report JOEY_Z80SCC_RX_FIFO=3; trusting it to model the real SCC FIFO" >&2
|
||||||
|
fi
|
||||||
|
echo "verify-iigs-serial-loss: PASS ($mode: $total/$total bytes, in order, through $presents full-screen presents)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "verify-iigs-serial-loss: FAIL ($mode: received $received/$total, $breaks sequence breaks, phase $phase)" >&2
|
||||||
|
exit 1
|
||||||
|
|
@ -41,13 +41,12 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
bin=$repo/build/x68000/bin/$PROG.X
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
x68=$X68K_DIR
|
bin=$(joeyExampleProgram "$prog" x68000) || exit 2
|
||||||
template=$x68/HUMAN302.XDF
|
|
||||||
source=$repo/src/x68000/audio.c
|
source=$repo/src/x68000/audio.c
|
||||||
|
|
||||||
[[ -f $bin ]] || { echo "verify-x68000-audio: $bin not built" >&2; exit 2; }
|
[[ -f $bin ]] || { echo "verify-x68000-audio: $bin not built" >&2; exit 2; }
|
||||||
[[ -f $template ]] || { echo "verify-x68000-audio: missing $template" >&2; exit 2; }
|
[[ -f $X68K_SYSTEM_DISK ]] || { echo "verify-x68000-audio: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
||||||
|
|
||||||
# The noise table, straight out of the port so the two cannot drift apart.
|
# The noise table, straight out of the port so the two cannot drift apart.
|
||||||
table=$(python3 - "$source" <<'PY'
|
table=$(python3 - "$source" <<'PY'
|
||||||
|
|
@ -63,25 +62,13 @@ PY
|
||||||
work=$(mktemp -d -t joey-x68audio.XXXXXX)
|
work=$(mktemp -d -t joey-x68audio.XXXXXX)
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
printf 'B:\r\nB:\\%s.X\r\n\x1a' "$PROG" > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/boot.xdf" 'B:' "B:\\$PROG.X" || exit 1
|
||||||
cp "$template" "$work/boot.xdf"
|
x68kDataDisk "$work/data.xdf" || exit 1
|
||||||
cp "$template" "$work/data.xdf"
|
x68kXdf add "$work/data.xdf" "$bin" "$PROG.X" >/dev/null || exit 1
|
||||||
python3 "$repo/tools/xdftool.py" empty "$work/data.xdf" >/dev/null
|
# The program's DATA tree sits next to it: its own folder's for Space Taxi,
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
# the shared bin/DATA for the rest.
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1 || true
|
if [[ -d $(dirname "$bin")/DATA ]]; then
|
||||||
done
|
x68kAddTree "$work/data.xdf" "$(dirname "$bin")/DATA" DATA || exit 1
|
||||||
for d in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$repo/tools/xdftool.py" deltree "$work/boot.xdf" "$d" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$bin" "$PROG.X" >/dev/null
|
|
||||||
data_dir=$repo/build/x68000/bin/DATA
|
|
||||||
if [[ -d $data_dir ]]; then
|
|
||||||
while IFS= read -r f; do
|
|
||||||
rel=${f#"$data_dir"/}
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$f" \
|
|
||||||
"DATA/$(echo "$rel" | tr '[:lower:]' '[:upper:]')" >/dev/null
|
|
||||||
done < <(find "$data_dir" -type f | sort)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The taps keep to arithmetic and file writes: a tap that reads memory
|
# The taps keep to arithmetic and file writes: a tap that reads memory
|
||||||
|
|
@ -150,7 +137,7 @@ end)
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
( cd "$work" && SDL_AUDIODRIVER=dummy timeout 1800 "$MAME" x68000 \
|
( cd "$work" && SDL_AUDIODRIVER=dummy timeout 1800 "$MAME" x68000 \
|
||||||
-bios ipl10 -rompath "$x68/roms" \
|
-bios ipl10 -rompath "$X68K_DIR/roms" \
|
||||||
-flop1 boot.xdf -flop2 data.xdf \
|
-flop1 boot.xdf -flop2 data.xdf \
|
||||||
-video none -sound none -nothrottle -skip_gameinfo \
|
-video none -sound none -nothrottle -skip_gameinfo \
|
||||||
-autoboot_script tap.lua </dev/null >mame.log 2>&1 ) || true
|
-autoboot_script tap.lua </dev/null >mame.log 2>&1 ) || true
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
GOLDEN="${X68K_GOLDEN:-$repo/tests/goldens/uber/iigs.txt}"
|
GOLDEN="${X68K_GOLDEN:-$repo/tests/goldens/uber/iigs.txt}"
|
||||||
FRAMES="${X68K_GOLDEN_FRAMES:-60000}"
|
FRAMES="${X68K_GOLDEN_FRAMES:-60000}"
|
||||||
WALL="${X68K_GOLDEN_WALL:-7200}"
|
WALL="${X68K_GOLDEN_WALL:-7200}"
|
||||||
|
|
@ -53,24 +53,10 @@ m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
|
||||||
|
|
||||||
# TWO DISKS: the boot disk carries only Human68k and AUTOEXEC, while the binary
|
# TWO DISKS: the boot disk carries only Human68k and AUTOEXEC, while the binary
|
||||||
# AND joeylog.txt live on the second disk on B:. AUTOEXEC switches to B: before
|
# AND joeylog.txt live on the second disk on B:. AUTOEXEC switches to B: before
|
||||||
# launching so the log lands there too. Keeping the writable log off the boot
|
# launching so the log lands there too.
|
||||||
# disk also means a run can never corrupt the thing that boots it.
|
x68kBootDisk "$work/gold.xdf" 'B:' 'B:\UBER.X' || exit 1
|
||||||
printf 'B:\r\nB:\\UBER.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
x68kDataDisk "$work/data.xdf" || exit 1
|
||||||
cp "$TEMPLATE" "$work/gold.xdf"
|
x68kXdf add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
|
||||||
# Data disk = the template emptied (keeps the BPB and boot sector). `empty`
|
|
||||||
# rather than a list of filenames: the stock disk's utilities live in
|
|
||||||
# SUBDIRECTORIES, which `delete` cannot see or free.
|
|
||||||
cp "$TEMPLATE" "$work/data.xdf"
|
|
||||||
python3 "$repo/tools/xdftool.py" empty "$work/data.xdf" >/dev/null
|
|
||||||
# Trim the boot disk of anything not needed to boot and run one .X.
|
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/gold.xdf" "$f" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
for d in SYS HIS BIN BASIC2 ASK ETC; do
|
|
||||||
python3 "$repo/tools/xdftool.py" deltree "$work/gold.xdf" "$d" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/data.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/gold.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
|
|
||||||
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
|
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
|
||||||
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
|
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
|
||||||
|
|
@ -165,7 +151,8 @@ emu.register_frame_done(function()
|
||||||
end)
|
end)
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
# In the work dir, so MAME cfg/ and nvram/ never persist between runs.
|
||||||
|
runLog=$(cd "$work" && timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||||
-rompath "$X68K_DIR/roms" -flop1 "$work/gold.xdf" -flop2 "$work/data.xdf" \
|
-rompath "$X68K_DIR/roms" -flop1 "$work/gold.xdf" -flop2 "$work/data.xdf" \
|
||||||
-video none -sound none -nothrottle \
|
-video none -sound none -nothrottle \
|
||||||
-autoboot_script "$work/gold.lua" </dev/null 2>&1 | grep '^GOLD')
|
-autoboot_script "$work/gold.lua" </dev/null 2>&1 | grep '^GOLD')
|
||||||
|
|
@ -183,7 +170,7 @@ if ! echo "$runLog" | grep -q 'done-screen'; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 "$repo/tools/xdftool.py" extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
|
x68kXdf extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
|
||||||
echo "verify-x68000-golden: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
|
echo "verify-x68000-golden: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# verify-x68000-input.sh - Headless typed-character acceptance gate for
|
# verify-x68000-input.sh - Headless typed-character acceptance gate for
|
||||||
# the X68000 port. Boots Human68k under the patched MAME with KEYS.X in
|
# the X68000 port. Boots Human68k under the patched MAME with KEYS.X in
|
||||||
# AUTOEXEC.BAT, waits for the example's A5A5 sentinel to appear in
|
# AUTOEXEC.BAT, waits for the example's A5A5 sentinel to appear on the
|
||||||
# GVRAM, types the RetroNet definition-of-done string on the emulated
|
# screen, types the RetroNet definition-of-done string on the emulated
|
||||||
# keyboard, and reads the verification scanline back out of GVRAM.
|
# keyboard, and reads the verification scanline back out of text VRAM.
|
||||||
# KEYS encodes every character it received from jlInputGetChar as nibble
|
# KEYS encodes every character it received from jlInputGetChar as nibble
|
||||||
# pixel pairs, so the row must equal the typed ASCII exactly.
|
# pixel pairs, so the row must equal the typed ASCII exactly.
|
||||||
#
|
#
|
||||||
# GVRAM geometry (crtmod 13, 256-colour graphics plane): 16-bit word
|
# Text-plane geometry: four 1bpp planes 0x20000 apart at 0xE00000, 128
|
||||||
# per pixel at $C00000, 512 words per row; the 320x200 stage is centred
|
# bytes per row, leftmost pixel in bit 7; the 320x200 stage is centred at
|
||||||
# at origin (96, 156). Verification row = stage y 198 -> GVRAM y 354.
|
# origin (96, 156). Verification row = stage y 198 -> screen y 354.
|
||||||
#
|
#
|
||||||
# bash scripts/verify-x68000-input.sh
|
# bash scripts/verify-x68000-input.sh
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
@ -19,24 +19,19 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
WALL="${X68K_INPUT_WALL:-900}"
|
WALL="${X68K_INPUT_WALL:-900}"
|
||||||
|
|
||||||
target="192.168.1.10:6510"
|
target="192.168.1.10:6510"
|
||||||
|
|
||||||
[ -f "$TEMPLATE" ] || { echo "verify-x68000-input: missing $TEMPLATE" >&2; exit 2; }
|
[ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-input: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
||||||
[ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-input: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
[ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-input: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
||||||
|
|
||||||
work=$(mktemp -d -t joey-x68input.XXXXXX)
|
work=$(mktemp -d -t joey-x68input.XXXXXX)
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
printf 'A:\\KEYS.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/boot.xdf" 'A:\KEYS.X' || exit 1
|
||||||
cp "$TEMPLATE" "$work/boot.xdf"
|
x68kXdf add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
|
|
||||||
cat > "$work/input.lua" <<LUA
|
cat > "$work/input.lua" <<LUA
|
||||||
local cpu = manager.machine.devices[":maincpu"]
|
local cpu = manager.machine.devices[":maincpu"]
|
||||||
|
|
|
||||||
|
|
@ -35,22 +35,17 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
WALL="${X68K_MOUSE_WALL:-900}"
|
WALL="${X68K_MOUSE_WALL:-900}"
|
||||||
|
|
||||||
[ -f "$TEMPLATE" ] || { echo "verify-x68000-mouse: missing $TEMPLATE" >&2; exit 2; }
|
[ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-mouse: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
||||||
[ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-mouse: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
[ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-mouse: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
||||||
|
|
||||||
work=$(mktemp -d -t joey-x68mouse.XXXXXX)
|
work=$(mktemp -d -t joey-x68mouse.XXXXXX)
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
printf 'A:\\KEYS.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/boot.xdf" 'A:\KEYS.X' || exit 1
|
||||||
cp "$TEMPLATE" "$work/boot.xdf"
|
x68kXdf add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1
|
||||||
for f in USKCG.SYS BEEP.SYS KEY.SYS STARTUP.ENV; do
|
|
||||||
python3 "$repo/tools/xdftool.py" delete "$work/boot.xdf" "$f" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/boot.xdf" "$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
|
|
||||||
cat > "$work/mouse.lua" <<'LUA'
|
cat > "$work/mouse.lua" <<'LUA'
|
||||||
local cpu = manager.machine.devices[":maincpu"]
|
local cpu = manager.machine.devices[":maincpu"]
|
||||||
|
|
|
||||||
|
|
@ -16,26 +16,39 @@ source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
joeyNeed MAME mame
|
joeyNeed MAME mame
|
||||||
joeyNeed X68K_DIR x68kFirmware
|
joeyNeed X68K_DIR x68kFirmware
|
||||||
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
||||||
TEMPLATE="$X68K_DIR/HUMAN302.XDF"
|
source "$repo/scripts/lib/x68kDisk.sh"
|
||||||
ROMS="$X68K_DIR/roms"
|
ROMS="$X68K_DIR/roms"
|
||||||
PORT="${X68K_SERIAL_PORT:-6800}"
|
PORT="${X68K_SERIAL_PORT:-6800}"
|
||||||
|
# SERTEST (no jlInit, banner then echo) passes the same probe/echo test.
|
||||||
|
PROG="${X68K_SERIAL_PROG:-SERIAL}"
|
||||||
|
# The host end's line, "BAUD DATABITS PARITY STOPBITS" (e.g. "4800 7 E 1").
|
||||||
|
# Empty keeps MAME's null_modem default, 9600 8 N 1. A guest that did not
|
||||||
|
# apply its jlSerialConfigT would still be at the default, so running this
|
||||||
|
# with a program built for another line proves the settings reach the SCC.
|
||||||
|
# Build that program with the matching -DSERIAL_BAUD=... (and SERIAL_DATABITS,
|
||||||
|
# SERIAL_PARITY, SERIAL_STOPBITS; see examples/serial/serial.c) and pass its
|
||||||
|
# name in X68K_SERIAL_PROG. 19200 and 38400 need RSDRV.SYS, which the boot
|
||||||
|
# disk loads (scripts/lib/x68kDisk.sh).
|
||||||
|
LINE="${X68K_SERIAL_LINE:-}"
|
||||||
PROBE="${X68K_SERIAL_PROBE:-JOEYLIB-X68K}"
|
PROBE="${X68K_SERIAL_PROBE:-JOEYLIB-X68K}"
|
||||||
MAXWAIT="${X68K_SERIAL_TIMEOUT:-420}"
|
MAXWAIT="${X68K_SERIAL_TIMEOUT:-420}"
|
||||||
|
|
||||||
for f in "$MAME" "$TEMPLATE"; do
|
for f in "$MAME" "$X68K_SYSTEM_DISK"; do
|
||||||
[ -e "$f" ] || { echo "verify-x68000-serial: missing $f" >&2; exit 2; }
|
[ -e "$f" ] || { echo "verify-x68000-serial: missing $f" >&2; exit 2; }
|
||||||
done
|
done
|
||||||
|
|
||||||
work=$(mktemp -d -t joey-x68ser.XXXXXX)
|
work=$(mktemp -d -t joey-x68ser.XXXXXX)
|
||||||
trap 'rm -rf "$work"; kill %1 2>/dev/null' EXIT
|
peer=""
|
||||||
|
mame=""
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$mame" "$peer"
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Autorun SERIAL.X. No ECHO OFF so a load failure is visible in a screenshot.
|
# Autorun the program. No ECHO OFF so a load failure is visible in a screenshot.
|
||||||
printf 'A:\\SERIAL.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
x68kBootDisk "$work/serial.xdf" "A:\\$PROG.X" || exit 1
|
||||||
cp "$TEMPLATE" "$work/serial.xdf"
|
x68kXdf add "$work/serial.xdf" "$repo/build/x68000/bin/$PROG.X" "$PROG.X" >/dev/null || exit 1
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/serial.xdf" \
|
|
||||||
"$repo/build/x68000/bin/SERIAL.X" SERIAL.X >/dev/null
|
|
||||||
python3 "$repo/tools/xdftool.py" add "$work/serial.xdf" \
|
|
||||||
"$work/AUTOEXEC.BAT" AUTOEXEC.BAT >/dev/null
|
|
||||||
|
|
||||||
# Host end of the link. MAME's null_modem socket form CONNECTS, so we listen.
|
# Host end of the link. MAME's null_modem socket form CONNECTS, so we listen.
|
||||||
python3 "$repo/scripts/x68kSerialPeer.py" \
|
python3 "$repo/scripts/x68kSerialPeer.py" \
|
||||||
|
|
@ -44,13 +57,45 @@ python3 "$repo/scripts/x68kSerialPeer.py" \
|
||||||
peer=$!
|
peer=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
timeout -s KILL "$MAXWAIT" "$MAME" x68000 \
|
# From the work dir, so MAME's cfg/ and nvram/ land there, not in the caller's
|
||||||
|
# cwd where the next run would read them back.
|
||||||
|
cd "$work" || exit 1
|
||||||
|
if [ -n "$LINE" ]; then
|
||||||
|
python3 - "$LINE" > "$work/x68000.cfg" <<'PY' || exit 2
|
||||||
|
import sys
|
||||||
|
# MAME's RS232_* enum values (src/devices/bus/rs232/rs232.h).
|
||||||
|
BAUD = {"300": 2, "600": 3, "1200": 4, "2400": 5, "4800": 6, "9600": 7, "19200": 9, "38400": 11}
|
||||||
|
DATA = {"5": 0, "6": 1, "7": 2, "8": 3}
|
||||||
|
PARITY = {"N": 0, "O": 1, "E": 2}
|
||||||
|
STOP = {"1": 1, "2": 3}
|
||||||
|
baud, data, parity, stop = sys.argv[1].split()
|
||||||
|
tag = ":rs232c:null_modem:"
|
||||||
|
ports = [("RS232_TXBAUD", 7, BAUD[baud]), ("RS232_RXBAUD", 7, BAUD[baud]),
|
||||||
|
("RS232_DATABITS", 3, DATA[data]), ("RS232_PARITY", 0, PARITY[parity.upper()]),
|
||||||
|
("RS232_STOPBITS", 1, STOP[stop])]
|
||||||
|
print('<?xml version="1.0"?>\n<mameconfig version="10">\n <system name="x68000">\n <input>')
|
||||||
|
for name, default, value in ports:
|
||||||
|
print(f' <port tag="{tag}{name}" type="CONFIG" mask="255" defvalue="{default}" value="{value}" />')
|
||||||
|
print(' </input>\n </system>\n</mameconfig>')
|
||||||
|
PY
|
||||||
|
mkdir -p "$work/cfg"
|
||||||
|
mv "$work/x68000.cfg" "$work/cfg/x68000.cfg"
|
||||||
|
fi
|
||||||
|
# MAME runs until the peer has its verdict and is then stopped by PID: waiting
|
||||||
|
# out a whole run once the probe has echoed cost minutes per gate. The peer's
|
||||||
|
# own deadline (MAXWAIT) bounds the run, and MAME is started directly rather
|
||||||
|
# than under timeout, so $! is MAME itself and stopping it orphans nothing.
|
||||||
|
"$MAME" x68000 \
|
||||||
-bios ipl10 -rompath "$ROMS" \
|
-bios ipl10 -rompath "$ROMS" \
|
||||||
-flop1 "$work/serial.xdf" \
|
-flop1 "$work/serial.xdf" \
|
||||||
-rs232c null_modem -bitb "socket.127.0.0.1:$PORT" \
|
-rs232c null_modem -bitb "socket.127.0.0.1:$PORT" \
|
||||||
-video none -sound none -nothrottle </dev/null >/dev/null 2>&1
|
-video none -sound none -nothrottle </dev/null >/dev/null 2>&1 &
|
||||||
|
mame=$!
|
||||||
|
|
||||||
wait "$peer"
|
wait "$peer"
|
||||||
rc=$?
|
rc=$?
|
||||||
|
peer=""
|
||||||
|
joeyStop "$mame"
|
||||||
|
mame=""
|
||||||
cat "$work/peer.out"
|
cat "$work/peer.out"
|
||||||
exit $rc
|
exit $rc
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#include <exec/io.h>
|
#include <exec/io.h>
|
||||||
#include <exec/memory.h>
|
#include <exec/memory.h>
|
||||||
#include <devices/serial.h>
|
#include <devices/serial.h>
|
||||||
|
#include <hardware/cia.h>
|
||||||
|
|
||||||
#include <proto/exec.h>
|
#include <proto/exec.h>
|
||||||
|
|
||||||
|
|
@ -29,6 +30,11 @@
|
||||||
// Largest single async transmit; a longer jlSerialWrite is split across frames.
|
// Largest single async transmit; a longer jlSerialWrite is split across frames.
|
||||||
#define TX_STAGE_SIZE 1024
|
#define TX_STAGE_SIZE 1024
|
||||||
|
|
||||||
|
// CIA-B, whose port A carries the serial control lines. DTR is bit 7 (CIAF_COMDTR), active low, and
|
||||||
|
// is only driven while its DDRA bit makes it an output: an input floats high, which the line driver
|
||||||
|
// turns into DTR off.
|
||||||
|
#define AMIGA_CIAB ((volatile struct CIA *)0xBFD000)
|
||||||
|
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
|
|
@ -38,16 +44,51 @@ static struct IOExtSer *gWriteReq = NULL;
|
||||||
static bool gOpen = false;
|
static bool gOpen = false;
|
||||||
static bool gWritePending = false;
|
static bool gWritePending = false;
|
||||||
static UBYTE gTxStage[TX_STAGE_SIZE];
|
static UBYTE gTxStage[TX_STAGE_SIZE];
|
||||||
|
static bool gDtrRaised = false; // jlpSerialOpen raised DTR and saved the bits below
|
||||||
|
static UBYTE gPreOpenPra = 0u; // CIA-B PRA / DDRA DTR bits before that open
|
||||||
|
static UBYTE gPreOpenDdra = 0u;
|
||||||
|
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static bool serialDtrIsOn(void);
|
||||||
|
static void serialDtrRestore(void);
|
||||||
|
static void serialDtrSet(bool on);
|
||||||
static void serialReapWrite(void);
|
static void serialReapWrite(void);
|
||||||
static void serialTeardown(void);
|
static void serialTeardown(bool keepDtr);
|
||||||
|
|
||||||
|
|
||||||
// ----- Helpers -----
|
// ----- Helpers -----
|
||||||
|
|
||||||
|
static bool serialDtrIsOn(void) {
|
||||||
|
return (AMIGA_CIAB->ciaddra & CIAF_COMDTR) != 0u && (AMIGA_CIAB->ciapra & CIAF_COMDTR) == 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Put DTR back the way jlpSerialOpen found it (a failed open never happened).
|
||||||
|
static void serialDtrRestore(void) {
|
||||||
|
Disable();
|
||||||
|
AMIGA_CIAB->ciapra = (UBYTE)((AMIGA_CIAB->ciapra & (UBYTE)~CIAF_COMDTR) | gPreOpenPra);
|
||||||
|
AMIGA_CIAB->ciaddra = (UBYTE)((AMIGA_CIAB->ciaddra & (UBYTE)~CIAF_COMDTR) | gPreOpenDdra);
|
||||||
|
Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Drive DTR on or off, leaving every other port A line alone. PRA first, so the pin only becomes an
|
||||||
|
// output once its latch already holds the new level. Disabled, because both are read-modify-writes
|
||||||
|
// of registers whose other bits belong to interrupt-level code (the parallel port's control lines).
|
||||||
|
static void serialDtrSet(bool on) {
|
||||||
|
Disable();
|
||||||
|
if (on) {
|
||||||
|
AMIGA_CIAB->ciapra = (UBYTE)(AMIGA_CIAB->ciapra & (UBYTE)~CIAF_COMDTR);
|
||||||
|
} else {
|
||||||
|
AMIGA_CIAB->ciapra = (UBYTE)(AMIGA_CIAB->ciapra | CIAF_COMDTR);
|
||||||
|
}
|
||||||
|
AMIGA_CIAB->ciaddra = (UBYTE)(AMIGA_CIAB->ciaddra | CIAF_COMDTR);
|
||||||
|
Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Collect a completed async write so the request + staging buffer free up.
|
// Collect a completed async write so the request + staging buffer free up.
|
||||||
static void serialReapWrite(void) {
|
static void serialReapWrite(void) {
|
||||||
if (gWritePending && CheckIO((struct IORequest *)gWriteReq) != NULL) {
|
if (gWritePending && CheckIO((struct IORequest *)gWriteReq) != NULL) {
|
||||||
|
|
@ -57,15 +98,44 @@ static void serialReapWrite(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void serialTeardown(void) {
|
// Release everything jlpSerialOpen acquired. keepDtr is a close (not a failed open): DTR must come out
|
||||||
|
// of it the way it went in.
|
||||||
|
//
|
||||||
|
// Close must not drop DTR: that hangs up a modem and tells a BBS the caller left, which a door or a
|
||||||
|
// terminal handing the line back must never do. serial.device drops it whenever it was off before
|
||||||
|
// the unit's first open: every open saves CIA-B ciapra/ciaddra bits 3-7 and asserts DTR and RTS, and
|
||||||
|
// the unit's last close writes the saved bits back (serial 37.4, OS 3.1's DEVS:serial.device, read
|
||||||
|
// from its code and MEASURED under fs-uae). jlpSerialOpen therefore raises DTR BEFORE OpenDevice, so
|
||||||
|
// the snapshot says on and the last close leaves it on, with no gap at all. Two cases remain, and
|
||||||
|
// in both DTR comes out of CloseDevice different from how it went in, so it is put straight back
|
||||||
|
// (Forbid keeps any other task from opening the unit in between): a unit another program opened
|
||||||
|
// first, whose snapshot had DTR off, and a program that dropped DTR with jlSerialSetDtr -- a close
|
||||||
|
// must leave a hung-up line down, but the snapshot says on. Either way the line holds the wrong
|
||||||
|
// level for the rest of serial.device's close, at most ~0.7 ms on a 7 MHz A500: far below the 50 ms
|
||||||
|
// a Hayes modem's S25 needs before it hangs up, and a line already hung up only rises briefly.
|
||||||
|
// While another opener still holds the unit this close is not the last and the lines are
|
||||||
|
// untouched. A failed open puts DTR back as it found it -- that open never happened.
|
||||||
|
static void serialTeardown(bool keepDtr) {
|
||||||
|
bool dtrWasOn;
|
||||||
|
|
||||||
if (gWritePending) {
|
if (gWritePending) {
|
||||||
AbortIO((struct IORequest *)gWriteReq);
|
AbortIO((struct IORequest *)gWriteReq);
|
||||||
WaitIO((struct IORequest *)gWriteReq);
|
WaitIO((struct IORequest *)gWriteReq);
|
||||||
gWritePending = false;
|
gWritePending = false;
|
||||||
}
|
}
|
||||||
if (gReadReq != NULL && gReadReq->IOSer.io_Device != NULL) {
|
if (gReadReq != NULL && gReadReq->IOSer.io_Device != NULL) {
|
||||||
|
Forbid();
|
||||||
|
dtrWasOn = serialDtrIsOn();
|
||||||
CloseDevice((struct IORequest *)gReadReq); // shared open; closes for both
|
CloseDevice((struct IORequest *)gReadReq); // shared open; closes for both
|
||||||
|
if (keepDtr && serialDtrIsOn() != dtrWasOn) {
|
||||||
|
serialDtrSet(dtrWasOn);
|
||||||
|
}
|
||||||
|
Permit();
|
||||||
}
|
}
|
||||||
|
if (!keepDtr && gDtrRaised) {
|
||||||
|
serialDtrRestore();
|
||||||
|
}
|
||||||
|
gDtrRaised = false;
|
||||||
if (gWriteReq != NULL) {
|
if (gWriteReq != NULL) {
|
||||||
DeleteIORequest((struct IORequest *)gWriteReq);
|
DeleteIORequest((struct IORequest *)gWriteReq);
|
||||||
gWriteReq = NULL;
|
gWriteReq = NULL;
|
||||||
|
|
@ -93,8 +163,9 @@ uint16_t jlpSerialAvailable(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Leaves DTR as it was: see serialTeardown.
|
||||||
void jlpSerialClose(void) {
|
void jlpSerialClose(void) {
|
||||||
serialTeardown();
|
serialTeardown(true);
|
||||||
gOpen = false;
|
gOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +204,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
gReadReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
gReadReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
||||||
if (gReadReq == NULL) {
|
if (gReadReq == NULL) {
|
||||||
gAmigaSerialFailStage = 2u;
|
gAmigaSerialFailStage = 2u;
|
||||||
serialTeardown();
|
serialTeardown(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +229,11 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
|
|
||||||
// Any device but SLOT maps to the built-in port (unit 0); SLOT picks a unit.
|
// Any device but SLOT maps to the built-in port (unit 0); SLOT picks a unit.
|
||||||
unit = (device == JL_SERIAL_SLOT) ? (ULONG)config->unit : 0ul;
|
unit = (device == JL_SERIAL_SLOT) ? (ULONG)config->unit : 0ul;
|
||||||
|
// DTR goes up before the open, so serial.device's snapshot records it on (see serialTeardown).
|
||||||
|
gPreOpenPra = (UBYTE)(AMIGA_CIAB->ciapra & CIAF_COMDTR);
|
||||||
|
gPreOpenDdra = (UBYTE)(AMIGA_CIAB->ciaddra & CIAF_COMDTR);
|
||||||
|
gDtrRaised = true;
|
||||||
|
serialDtrSet(true);
|
||||||
err = OpenDevice((CONST_STRPTR)"serial.device", unit, (struct IORequest *)gReadReq, 0ul);
|
err = OpenDevice((CONST_STRPTR)"serial.device", unit, (struct IORequest *)gReadReq, 0ul);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Exclusive access is refused if ANYONE holds the unit; fall back to a shared open, which is
|
// Exclusive access is refused if ANYONE holds the unit; fall back to a shared open, which is
|
||||||
|
|
@ -172,7 +248,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
// verdict and io_Device is non-NULL if the device actually attached.
|
// verdict and io_Device is non-NULL if the device actually attached.
|
||||||
gAmigaSerialFailIoErr = gReadReq->IOSer.io_Error;
|
gAmigaSerialFailIoErr = gReadReq->IOSer.io_Error;
|
||||||
gAmigaSerialFailDevice = (gReadReq->IOSer.io_Device != NULL) ? 1u : 0u;
|
gAmigaSerialFailDevice = (gReadReq->IOSer.io_Device != NULL) ? 1u : 0u;
|
||||||
serialTeardown();
|
serialTeardown(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
gReadReq->io_Baud = config->baud;
|
gReadReq->io_Baud = config->baud;
|
||||||
|
|
@ -190,7 +266,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
if (DoIO((struct IORequest *)gReadReq) != 0) {
|
if (DoIO((struct IORequest *)gReadReq) != 0) {
|
||||||
gAmigaSerialFailStage = 4u;
|
gAmigaSerialFailStage = 4u;
|
||||||
gAmigaSerialFailErr = gReadReq->IOSer.io_Error;
|
gAmigaSerialFailErr = gReadReq->IOSer.io_Error;
|
||||||
serialTeardown();
|
serialTeardown(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,7 +274,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
gWriteReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
gWriteReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
||||||
if (gWriteReq == NULL) {
|
if (gWriteReq == NULL) {
|
||||||
gAmigaSerialFailStage = 5u;
|
gAmigaSerialFailStage = 5u;
|
||||||
serialTeardown();
|
serialTeardown(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
*gWriteReq = *gReadReq;
|
*gWriteReq = *gReadReq;
|
||||||
|
|
@ -236,6 +312,16 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DTR on demand (jlSerialSetDtr, how a program hangs up): CIA-B PRA bit 7. serial.device only touches
|
||||||
|
// DTR when the unit is opened and closed, so the level set here holds until close, which keeps it.
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
if (!gOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serialDtrSet(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
|
||||||
|
|
@ -230,15 +230,15 @@ static long uninstallTimerA(void) {
|
||||||
gOldTimerAVec = NULL;
|
gOldTimerAVec = NULL;
|
||||||
}
|
}
|
||||||
// Channel A belongs to the PSG again. Replay voice 0's latched state,
|
// Channel A belongs to the PSG again. Replay voice 0's latched state,
|
||||||
// which both clears the DAC's residual level (so the handoff back to TOS
|
// which both clears the DAC's residual level (so the handoff back to TOS
|
||||||
// leaves no DC on the speaker) and restores a note the music player set
|
// leaves no DC on the speaker) and restores a note the music player set
|
||||||
// while the pump owned the channel. Without the replay voice 0 would stay
|
// while the pump owned the channel. Without the replay voice 0 would stay
|
||||||
// dead until its next note EVENT, because src/core/music.c only reprograms
|
// dead until its next note EVENT, because src/core/music.c only reprograms
|
||||||
// a voice when one arrives -- a held note would never come back.
|
// a voice when one arrives -- a held note would never come back.
|
||||||
gYmVoice = 0u;
|
gYmVoice = 0u;
|
||||||
gYmFreqHz = gVoiceAFreqHz;
|
gYmFreqHz = gVoiceAFreqHz;
|
||||||
gYmAtten = gVoiceAAtten;
|
gYmAtten = gVoiceAAtten;
|
||||||
return applyYmVoice();
|
return applyYmVoice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -528,9 +528,9 @@ void jlpAudioCriticalExit(void) {
|
||||||
|
|
||||||
|
|
||||||
void jlpAudioNoise(uint8_t pitch, uint8_t atten) {
|
void jlpAudioNoise(uint8_t pitch, uint8_t atten) {
|
||||||
// Same Supexec-with-stashed-args shape as jlpAudioVoice; noise
|
// Same Supexec-with-stashed-args shape as jlpAudioVoice. Noise plays
|
||||||
// inherits the MOD-collision caveat documented there (the PWM
|
// through channel C, so it keeps playing while the PWM pump owns
|
||||||
// pump's installTimerA force-silences the whole PSG on start).
|
// channel A (installTimerA takes channel A only).
|
||||||
gYmNoisePitch = pitch;
|
gYmNoisePitch = pitch;
|
||||||
gYmNoiseAtten = atten;
|
gYmNoiseAtten = atten;
|
||||||
Supexec(applyYmNoise);
|
Supexec(applyYmNoise);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@
|
||||||
#define ST_RSCONF_INQUIRE ((int16_t)-2)
|
#define ST_RSCONF_INQUIRE ((int16_t)-2)
|
||||||
#define ST_BAUD_CODE_MAX 15
|
#define ST_BAUD_CODE_MAX 15
|
||||||
|
|
||||||
|
// DTR is YM2149 port A bit 4, set and cleared through XBIOS Ongibit/Offgibit. The line driver inverts
|
||||||
|
// it: the bit SET is DTR OFF, so Ongibit(ST_GI_DTR_OFF) hangs up and Offgibit(ST_GI_DTR_ON_MASK) --
|
||||||
|
// every bit but 4 kept -- raises it.
|
||||||
|
#define ST_GI_DTR_OFF 0x10
|
||||||
|
#define ST_GI_DTR_ON_MASK 0xEF
|
||||||
|
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
|
|
@ -197,6 +203,10 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
baudCode = serialBaudCode(config->baud);
|
baudCode = serialBaudCode(config->baud);
|
||||||
ucr = serialUcr(config);
|
ucr = serialUcr(config);
|
||||||
|
|
||||||
|
// Raise DTR, as the open does on every port: TOS only sets it at boot, so a line an earlier
|
||||||
|
// jlSerialSetDtr(false) hung up would otherwise stay down.
|
||||||
|
Offgibit(ST_GI_DTR_ON_MASK);
|
||||||
|
|
||||||
// Snapshot the baud code BEFORE installing ours -- the inquire reports the
|
// Snapshot the baud code BEFORE installing ours -- the inquire reports the
|
||||||
// rate currently in force. Range-gate the answer: a TOS that did not
|
// rate currently in force. Range-gate the answer: a TOS that did not
|
||||||
// implement the -2 inquire would fall through to the normal path, write
|
// implement the -2 inquire would fall through to the normal path, write
|
||||||
|
|
@ -233,6 +243,18 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DTR on demand (jlSerialSetDtr, how a program hangs up). Neither Rsconf nor anything else in EmuTOS
|
||||||
|
// touches port A bit 4 after boot (Hatari psg_write + xbios trace), and close does not either, so a
|
||||||
|
// dropped DTR stays dropped.
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
if (on) {
|
||||||
|
Offgibit(ST_GI_DTR_ON_MASK);
|
||||||
|
} else {
|
||||||
|
Ongibit(ST_GI_DTR_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,19 +100,17 @@ typedef char AssetTileSizeCheckT[(sizeof(jlTileT) == TILE_BYTES) ? 1 : -1];
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
#define EXPECTED_TILE_TARGET 3u // chunky 4bpp, same as DOS (uses the generic tile ops)
|
#define EXPECTED_TILE_TARGET 3u // chunky 4bpp, same as DOS (uses the generic tile ops)
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
// Chunky 4bpp while the port is on the generic backend, so it shares DOS's
|
// Plane-major like the Amiga (src/x68000/x68kTile.h reads jlTileT.pixels as
|
||||||
// target byte -- the payload is bytes, so the endian difference does not
|
// 4 planes x 8 rows), baked by assetbake --target x68000.
|
||||||
// matter. This gets its own target the moment the storage model is decided
|
#define EXPECTED_TILE_TARGET 5u
|
||||||
// (a text-plane port would bake Amiga-shaped tiles and want target 1).
|
|
||||||
#define EXPECTED_TILE_TARGET 3u
|
|
||||||
#else
|
#else
|
||||||
#error "Unknown platform for asset loader"
|
#error "Unknown platform for asset loader"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Per-platform target byte for .spc files (pre-compiled sprite code, so
|
// Per-platform target byte for .spc files (pre-compiled sprite code, so
|
||||||
// it is per MACHINE CODE, not per pixel format: the X68000 shares DOS's
|
// it is per MACHINE CODE, not per pixel format: every port's banks carry
|
||||||
// chunky tile byte above but runs 68k planar sprite routines, and must
|
// their own byte, so a bank baked for any other build is refused). Mirrors
|
||||||
// never accept an x86 bank). Mirrors SPC_TARGET in tools/spritebake.
|
// SPC_TARGET in tools/spritebake.
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA)
|
#if defined(JOEYLIB_PLATFORM_AMIGA)
|
||||||
#define EXPECTED_SPC_TARGET 1u
|
#define EXPECTED_SPC_TARGET 1u
|
||||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ extern void iigsTilePasteMonoInner(uint8_t *dstRow0, const uint8_t *monoTile, ui
|
||||||
extern void iigsTilePasteGlyphMark(const uint8_t *bits, uint16_t bx, uint16_t by, uint16_t fgColor, uint16_t bgColor);
|
extern void iigsTilePasteGlyphMark(const uint8_t *bits, uint16_t bx, uint16_t by, uint16_t fgColor, uint16_t bgColor);
|
||||||
extern void iigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
extern void iigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||||
extern void iigsCopyRectRows (uint8_t *dst, uint16_t srcLo, uint16_t srcBank, uint16_t rowBytes, uint16_t rows);
|
extern void iigsCopyRectRows (uint8_t *dst, uint16_t srcLo, uint16_t srcBank, uint16_t rowBytes, uint16_t rows);
|
||||||
extern void iigsQuitGS (void);
|
|
||||||
extern void iigsFillRectMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
extern void iigsFillRectMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||||
extern void iigsFillRectNoMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
extern void iigsFillRectNoMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||||
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||||
|
|
@ -294,6 +293,13 @@ void iigsMemCopy(void *dst, const void *src, uint32_t bytes);
|
||||||
#define jlpMemCopy(_d, _s, _n) iigsMemCopy((_d), (_s), (_n))
|
#define jlpMemCopy(_d, _s, _n) iigsMemCopy((_d), (_s), (_n))
|
||||||
// The one Memory Manager owner ID every IIgs allocation uses (hal.c).
|
// The one Memory Manager owner ID every IIgs allocation uses (hal.c).
|
||||||
uint16_t iigsMemUserId(void);
|
uint16_t iigsMemUserId(void);
|
||||||
|
// Interrupt-latency bound of the long interrupts-off paths (hal.c): a
|
||||||
|
// client that cannot wait ~17 ms for its interrupt holds the tight bound
|
||||||
|
// while it needs it. Counted, so clients nest. gIigsIrqTight is nonzero
|
||||||
|
// while any client holds it, for the C paths that bound themselves.
|
||||||
|
extern uint16_t gIigsIrqTight;
|
||||||
|
void iigsIrqLatencyRelax(void);
|
||||||
|
void iigsIrqLatencyTighten(void);
|
||||||
|
|
||||||
// SFX streams go to NTPstreamsound as unsigned bytes with 0x00 forbidden
|
// SFX streams go to NTPstreamsound as unsigned bytes with 0x00 forbidden
|
||||||
// (joey/audio.h JL_SFX_RAW_UNSIGNED8_NOZERO): a raw producer hands
|
// (joey/audio.h JL_SFX_RAW_UNSIGNED8_NOZERO): a raw producer hands
|
||||||
|
|
@ -1070,6 +1076,16 @@ void jlpGenericSerialFlush(void);
|
||||||
#define jlpSerialFlush() jlpGenericSerialFlush()
|
#define jlpSerialFlush() jlpGenericSerialFlush()
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
// Application-driven DTR (jlSerialSetDtr), how a program hangs up. The generic stub does nothing, like
|
||||||
|
// the RTS one below: a port with no controllable DTR line has nothing to drop.
|
||||||
|
void jlpGenericSerialSetDtr(bool on);
|
||||||
|
#if !defined(jlpSerialSetDtr)
|
||||||
|
#if defined(JL_HAS_SERIAL_SET_DTR)
|
||||||
|
void jlpSerialSetDtr(bool on);
|
||||||
|
#else
|
||||||
|
#define jlpSerialSetDtr(_o) jlpGenericSerialSetDtr((_o))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
// Application-driven RTS (jlSerialSetRts). The generic stub does nothing, which is the honest answer
|
// Application-driven RTS (jlSerialSetRts). The generic stub does nothing, which is the honest answer
|
||||||
// for a port whose RTS line is not controllable - a caller drives this unconditionally.
|
// for a port whose RTS line is not controllable - a caller drives this unconditionally.
|
||||||
void jlpGenericSerialSetRts(bool on);
|
void jlpGenericSerialSetRts(bool on);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,15 @@
|
||||||
// Single open port at a time (module-static gate), matching the add-on's
|
// Single open port at a time (module-static gate), matching the add-on's
|
||||||
// stated contract. Config is normalized + range-checked here so every per-port
|
// stated contract. Config is normalized + range-checked here so every per-port
|
||||||
// HAL can assume a sane jlSerialConfigT.
|
// HAL can assume a sane jlSerialConfigT.
|
||||||
|
//
|
||||||
|
// A port the program leaves open is closed at exit by an atexit handler the
|
||||||
|
// first successful open registers: DOS, the X68000 and the IIgs point a
|
||||||
|
// hardware interrupt vector into the program while a port is open, and the
|
||||||
|
// program's memory is about to be freed. Closing leaves DTR as it was, so a
|
||||||
|
// connection stays up after the program ends.
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "joey/serial.h"
|
#include "joey/serial.h"
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
@ -25,7 +32,13 @@
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
static bool gSerialOpen = false;
|
static bool gSerialOpen = false;
|
||||||
|
static bool gSerialAtExitOn = false;
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void serialAtExit(void);
|
||||||
|
|
||||||
|
|
||||||
// ----- Public API (alphabetical) -----
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
@ -79,6 +92,9 @@ bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gSerialOpen = jlpSerialOpen(device, &cfg);
|
gSerialOpen = jlpSerialOpen(device, &cfg);
|
||||||
|
if (gSerialOpen && !gSerialAtExitOn) {
|
||||||
|
gSerialAtExitOn = atexit(serialAtExit) == 0;
|
||||||
|
}
|
||||||
return gSerialOpen;
|
return gSerialOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +125,14 @@ int16_t jlSerialReadByte(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlSerialSetDtr(bool on) {
|
||||||
|
if (!gSerialOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jlpSerialSetDtr(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void jlSerialSetRts(bool on) {
|
void jlSerialSetRts(bool on) {
|
||||||
if (!gSerialOpen) {
|
if (!gSerialOpen) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -128,3 +152,11 @@ uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
bool jlSerialWriteByte(uint8_t b) {
|
bool jlSerialWriteByte(uint8_t b) {
|
||||||
return jlSerialWrite(&b, 1u) == 1u;
|
return jlSerialWrite(&b, 1u) == 1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Internal -----
|
||||||
|
|
||||||
|
// The atexit handler (see the header comment): closes a port left open.
|
||||||
|
static void serialAtExit(void) {
|
||||||
|
jlSerialClose();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ static _go32_dpmi_seginfo gNewIrqVec;
|
||||||
static void picMaskIrq(uint8_t irq);
|
static void picMaskIrq(uint8_t irq);
|
||||||
static void picUnmaskIrq(uint8_t irq);
|
static void picUnmaskIrq(uint8_t irq);
|
||||||
static int picVectorForIrq(uint8_t irq);
|
static int picVectorForIrq(uint8_t irq);
|
||||||
|
static void serialMcrBit(uint8_t bit, bool on);
|
||||||
static bool serialResolvePort(jlSerialDeviceE device, const jlSerialConfigT *cfg, uint16_t *outBase, uint8_t *outIrq);
|
static bool serialResolvePort(jlSerialDeviceE device, const jlSerialConfigT *cfg, uint16_t *outBase, uint8_t *outIrq);
|
||||||
static void serialRxIsr(void);
|
static void serialRxIsr(void);
|
||||||
|
|
||||||
|
|
@ -149,6 +150,28 @@ static void serialRxIsr(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Modem control -----
|
||||||
|
|
||||||
|
// Set or clear one MCR bit -- DTR or RTS for jlSerialSetDtr/jlSerialSetRts, OUT2 at close --
|
||||||
|
// read-modify-write so every other bit keeps whatever the chip holds: above all OUT2, which physically
|
||||||
|
// gates this UART's interrupt to the PIC (clearing it along with a line would silence the receiver
|
||||||
|
// entirely), and DTR, which close must leave as it is.
|
||||||
|
static void serialMcrBit(uint8_t bit, bool on) {
|
||||||
|
uint8_t mcr;
|
||||||
|
|
||||||
|
if (gComBase == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mcr = (uint8_t)inportb((int)(gComBase + UART_MCR));
|
||||||
|
if (on) {
|
||||||
|
mcr = (uint8_t)(mcr | bit);
|
||||||
|
} else {
|
||||||
|
mcr = (uint8_t)(mcr & (uint8_t)~bit);
|
||||||
|
}
|
||||||
|
outportb((int)(gComBase + UART_MCR), mcr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- Port resolution -----
|
// ----- Port resolution -----
|
||||||
|
|
||||||
// DEFAULT/MODEM/PRINTER -> COM1; SLOT -> COM<unit> (1..4). The canonical COM
|
// DEFAULT/MODEM/PRINTER -> COM1; SLOT -> COM<unit> (1..4). The canonical COM
|
||||||
|
|
@ -179,6 +202,10 @@ uint16_t jlpSerialAvailable(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Close hands the IRQ back but NOT the line: only OUT2 (the UART's IRQ gate to the PIC) is cleared,
|
||||||
|
// and DTR and RTS stay exactly as they are. Dropping DTR hangs up a modem and tells a BBS the caller
|
||||||
|
// left, which a door or a terminal handing the line back must never do -- the same reason a FOSSIL
|
||||||
|
// driver's deinit (INT 14h function 05h) leaves DTR alone.
|
||||||
void jlpSerialClose(void) {
|
void jlpSerialClose(void) {
|
||||||
if (!gOpen) {
|
if (!gOpen) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -188,7 +215,7 @@ void jlpSerialClose(void) {
|
||||||
|
|
||||||
picMaskIrq(gComIrq);
|
picMaskIrq(gComIrq);
|
||||||
outportb((int)(gComBase + UART_IER), 0); // disable UART interrupts
|
outportb((int)(gComBase + UART_IER), 0); // disable UART interrupts
|
||||||
outportb((int)(gComBase + UART_MCR), 0); // drop DTR/RTS/OUT2
|
serialMcrBit(MCR_OUT2, false); // IRQ gate off; DTR and RTS stay as they are
|
||||||
_go32_dpmi_set_protected_mode_interrupt_vector(vector, &gOldIrqVec);
|
_go32_dpmi_set_protected_mode_interrupt_vector(vector, &gOldIrqVec);
|
||||||
_go32_dpmi_free_iret_wrapper(&gNewIrqVec);
|
_go32_dpmi_free_iret_wrapper(&gNewIrqVec);
|
||||||
gIrqHooked = false;
|
gIrqHooked = false;
|
||||||
|
|
@ -208,11 +235,14 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
uint8_t irq;
|
uint8_t irq;
|
||||||
uint16_t divisor;
|
uint16_t divisor;
|
||||||
uint8_t lcr;
|
uint8_t lcr;
|
||||||
|
uint8_t mcrBefore;
|
||||||
int vector;
|
int vector;
|
||||||
|
|
||||||
if (gOpen || !serialResolvePort(device, config, &base, &irq)) {
|
if (gOpen || !serialResolvePort(device, config, &base, &irq)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// A failed open puts the modem lines back the way it found them.
|
||||||
|
mcrBefore = (uint8_t)inportb((int)(base + UART_MCR));
|
||||||
|
|
||||||
divisor = (uint16_t)(UART_BAUD_BASE / config->baud);
|
divisor = (uint16_t)(UART_BAUD_BASE / config->baud);
|
||||||
if (divisor == 0u) {
|
if (divisor == 0u) {
|
||||||
|
|
@ -251,7 +281,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
gNewIrqVec.pm_offset = (unsigned long)serialRxIsr;
|
gNewIrqVec.pm_offset = (unsigned long)serialRxIsr;
|
||||||
gNewIrqVec.pm_selector = _go32_my_cs();
|
gNewIrqVec.pm_selector = _go32_my_cs();
|
||||||
if (_go32_dpmi_allocate_iret_wrapper(&gNewIrqVec) != 0) {
|
if (_go32_dpmi_allocate_iret_wrapper(&gNewIrqVec) != 0) {
|
||||||
outportb((int)(base + UART_MCR), 0);
|
outportb((int)(base + UART_MCR), mcrBefore);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Page-lock the ISR + every object it touches (fault at IRQ time hangs).
|
// Page-lock the ISR + every object it touches (fault at IRQ time hangs).
|
||||||
|
|
@ -278,23 +308,6 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// RTS on demand (jlSerialSetRts): MCR bit 1. DTR and OUT2 are preserved - OUT2 physically gates this
|
|
||||||
// UART's interrupt to the PIC, so clearing it while dropping RTS would silence the receiver entirely,
|
|
||||||
// which is the opposite of what a flow-control pause is for.
|
|
||||||
void jlpSerialSetRts(bool on) {
|
|
||||||
uint8_t mcr;
|
|
||||||
|
|
||||||
if (gComBase == 0u) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mcr = (uint8_t)(MCR_DTR | MCR_OUT2);
|
|
||||||
if (on) {
|
|
||||||
mcr = (uint8_t)(mcr | MCR_RTS);
|
|
||||||
}
|
|
||||||
outportb((int)(gComBase + UART_MCR), mcr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void jlpSerialPoll(void) {
|
void jlpSerialPoll(void) {
|
||||||
// The RX IRQ fills the ring in the background; nothing to pump.
|
// The RX IRQ fills the ring in the background; nothing to pump.
|
||||||
}
|
}
|
||||||
|
|
@ -313,6 +326,18 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DTR on demand (jlSerialSetDtr, how a program hangs up): MCR bit 0.
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
serialMcrBit(MCR_DTR, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RTS on demand (jlSerialSetRts): MCR bit 1.
|
||||||
|
void jlpSerialSetRts(bool on) {
|
||||||
|
serialMcrBit(MCR_RTS, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ void jlpGenericSerialPoll(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A port with no controllable DTR line: nothing to raise or drop, same reasoning as the RTS stub below.
|
||||||
|
void jlpGenericSerialSetDtr(bool on) {
|
||||||
|
(void)on;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A port with no controllable RTS line: doing nothing is the honest answer, and it lets a caller drive
|
// A port with no controllable RTS line: doing nothing is the honest answer, and it lets a caller drive
|
||||||
// jlSerialSetRts unconditionally instead of guarding every call site with a platform test.
|
// jlSerialSetRts unconditionally instead of guarding every call site with a platform test.
|
||||||
void jlpGenericSerialSetRts(bool on) {
|
void jlpGenericSerialSetRts(bool on) {
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,13 @@ typedef void *Pointer;
|
||||||
#define NTP_ENTRY_PLAY 3
|
#define NTP_ENTRY_PLAY 3
|
||||||
#define NTP_ENTRY_STOP 6
|
#define NTP_ENTRY_STOP 6
|
||||||
#define NTP_ENTRY_STREAMSOUND 24
|
#define NTP_ENTRY_STREAMSOUND 24
|
||||||
|
// The word JoeyLib's build adds right after the jump table (make/support/
|
||||||
|
// iigs/ntpIrqWindow.py). Nonzero bounds NTP's two long interrupts-off
|
||||||
|
// stretches: NTPstreamsound takes interrupts between the two 256-byte
|
||||||
|
// halves of its first DOC fill (~2.4 ms masked otherwise), and the DOC
|
||||||
|
// interrupt refills one streaming oscillator per interrupt instead of
|
||||||
|
// every one that is due (~1 ms apiece).
|
||||||
|
#define NTP_IRQ_WINDOW 27
|
||||||
|
|
||||||
// The silent module handed to NTPprepare when no song is playing (see
|
// The silent module handed to NTPprepare when no song is playing (see
|
||||||
// ntpArm). One track, one never-triggered streamed instrument, one
|
// ntpArm). One track, one never-triggered streamed instrument, one
|
||||||
|
|
@ -778,10 +785,17 @@ static void sfxFillStruct(uint8_t slot, uint32_t slotBase, uint32_t length, uint
|
||||||
// the machine down; guarding on gNTPPlaying instead would be safe but
|
// the machine down; guarding on gNTPPlaying instead would be safe but
|
||||||
// would silence every SFX in an application that plays no music, which
|
// would silence every SFX in an application that plays no music, which
|
||||||
// is all of them today.
|
// is all of them today.
|
||||||
|
//
|
||||||
|
// While hal.c's interrupt-latency bound is tight (gIigsIrqTight, e.g. a
|
||||||
|
// serial port is open), NTP is asked to bound itself (NTP_IRQ_WINDOW), so
|
||||||
|
// no stretch of the call, and no refill interrupt, masks for more than
|
||||||
|
// ~1.4 ms. The word is set before every trigger, so it follows the bound
|
||||||
|
// within one sound.
|
||||||
static void sfxTrigger(uint32_t structAddr) {
|
static void sfxTrigger(uint32_t structAddr) {
|
||||||
if (!gNTPArmed) {
|
if (!gNTPArmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*(volatile uint16_t *)(gNTPBase + NTP_IRQ_WINDOW) = gIigsIrqTight;
|
||||||
ntpCall(gNTPBase + NTP_ENTRY_STREAMSOUND,
|
ntpCall(gNTPBase + NTP_ENTRY_STREAMSOUND,
|
||||||
(uint16_t)(structAddr & 0xFFFFu),
|
(uint16_t)(structAddr & 0xFFFFu),
|
||||||
(uint16_t)((structAddr >> 16) & 0xFFu),
|
(uint16_t)((structAddr >> 16) & 0xFFu),
|
||||||
|
|
|
||||||
108
src/iigs/hal.c
108
src/iigs/hal.c
|
|
@ -134,8 +134,6 @@ extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y,
|
||||||
// bytes), iigsTilePasteMonoInner's mono-byte -> opacity-pair index
|
// bytes), iigsTilePasteMonoInner's mono-byte -> opacity-pair index
|
||||||
// table (PERF-AUDIT #3).
|
// table (PERF-AUDIT #3).
|
||||||
extern void iigsInitRowLut(void);
|
extern void iigsInitRowLut(void);
|
||||||
// Serial soft-ring pump (serial.c) -- called from every long-running HAL path.
|
|
||||||
extern void iigsSerialPump(void);
|
|
||||||
// Filled circle, scanline-style. Takes the raw fill nibble; the asm
|
// Filled circle, scanline-style. Takes the raw fill nibble; the asm
|
||||||
// derives the doubled fill byte and RMW nibble bytes (PERF-AUDIT #41).
|
// derives the doubled fill byte and RMW nibble bytes (PERF-AUDIT #41).
|
||||||
extern void iigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
|
extern void iigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
|
||||||
|
|
@ -183,6 +181,21 @@ extern void iigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint1
|
||||||
// in jlpInit so the visible bezel matches the cleared SHR background.
|
// in jlpInit so the visible bezel matches the cleared SHR background.
|
||||||
#define BORDER_COLOR_MASK 0xF0
|
#define BORDER_COLOR_MASK 0xF0
|
||||||
|
|
||||||
|
// Interrupt windows of the two long interrupts-off paths in joeyDraw.s
|
||||||
|
// (see gIigsPresentChunkRows). Loose is what they always did and costs
|
||||||
|
// nothing; tight fits the Z8530 SCC's 3-byte receive FIFO at 9600 baud
|
||||||
|
// (~3.1 ms): 4 present rows ~1.8 ms, 12 clear blocks ~1.4 ms.
|
||||||
|
#define IRQ_PRESENT_ROWS_LOOSE 40u
|
||||||
|
#define IRQ_PRESENT_ROWS_TIGHT 4u
|
||||||
|
#define IRQ_CLEAR_BLOCKS_LOOSE 200u
|
||||||
|
#define IRQ_CLEAR_BLOCKS_TIGHT 12u
|
||||||
|
|
||||||
|
// Video beam ticks a frame (VERTCNT/HORIZCNT: 65 positions a line), for
|
||||||
|
// the tight joystick read's beam stamps (iigsJoyPass).
|
||||||
|
#define BEAM_TICKS_PER_LINE 65u
|
||||||
|
#define BEAM_LINES_NTSC 262u
|
||||||
|
#define BEAM_LINES_PAL 312u
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
static uint8_t gPreviousNewVideo = 0;
|
static uint8_t gPreviousNewVideo = 0;
|
||||||
|
|
@ -190,6 +203,25 @@ static uint8_t gPreviousBorder = 0;
|
||||||
static uint8_t gPreviousShadow = 0;
|
static uint8_t gPreviousShadow = 0;
|
||||||
static bool gModeSet = false;
|
static bool gModeSet = false;
|
||||||
|
|
||||||
|
// Interrupt-latency bound, read by joeyDraw.s (so not static, and BSS
|
||||||
|
// like every other C global the asm reads DBR-relative; jlpInit sets
|
||||||
|
// the loose values). The present's chunked PEI slam re-enables
|
||||||
|
// interrupts every gIigsPresentChunkRows rows (~0.42 ms a row), the
|
||||||
|
// stage fast clear every gIigsClearChunkBlocks 160-byte blocks
|
||||||
|
// (~0.12 ms a block). A client whose interrupt cannot wait ~17 ms holds
|
||||||
|
// the tight bound while it needs it; the holds are counted, so clients
|
||||||
|
// nest. Tight costs a full-screen present ~5% (83.1 -> 87.2 ms).
|
||||||
|
// gIigsIrqTight tells the C paths that bound themselves (input.c's
|
||||||
|
// joystick read, audio_full.c's NinjaTrackerPlus entries) which way to
|
||||||
|
// go. All three follow gIrqTightHolds, through irqLatencyApply alone.
|
||||||
|
uint16_t gIigsPresentChunkRows;
|
||||||
|
uint16_t gIigsClearChunkBlocks;
|
||||||
|
uint16_t gIigsIrqTight;
|
||||||
|
static uint16_t gIrqTightHolds = 0u;
|
||||||
|
|
||||||
|
// Beam ticks a frame for the joystick asm's frame wrap (jlpInit).
|
||||||
|
uint16_t gIigsBeamFrameTicks;
|
||||||
|
|
||||||
// SCB / palette upload skipping is now driven by gStageScbDirty /
|
// SCB / palette upload skipping is now driven by gStageScbDirty /
|
||||||
// gStagePaletteDirty (core/surface.c). The old per-frame memcmp-
|
// gStagePaletteDirty (core/surface.c). The old per-frame memcmp-
|
||||||
// against-cached-copy approach was costing ~7 ms / frame.
|
// against-cached-copy approach was costing ~7 ms / frame.
|
||||||
|
|
@ -315,6 +347,7 @@ void jlpBigFree(void *p) {
|
||||||
|
|
||||||
|
|
||||||
static void claimStagePixels(void);
|
static void claimStagePixels(void);
|
||||||
|
static void irqLatencyApply(bool tight);
|
||||||
|
|
||||||
bool jlpInit(const jlConfigT *config) {
|
bool jlpInit(const jlConfigT *config) {
|
||||||
uint16_t tick;
|
uint16_t tick;
|
||||||
|
|
@ -353,8 +386,10 @@ bool jlpInit(const jlConfigT *config) {
|
||||||
// bank $E1 is unreliable from jlpInit's calling context, so we
|
// bank $E1 is unreliable from jlpInit's calling context, so we
|
||||||
// don't try it here.
|
// don't try it here.
|
||||||
iigsInitRowLut();
|
iigsInitRowLut();
|
||||||
gFrameHz = (iigsReadHzParam() == 1u) ? 50u : 60u;
|
irqLatencyApply(gIrqTightHolds != 0u);
|
||||||
gModeSet = true;
|
gFrameHz = (iigsReadHzParam() == 1u) ? 50u : 60u;
|
||||||
|
gIigsBeamFrameTicks = (uint16_t)(((gFrameHz == 50u) ? BEAM_LINES_PAL : BEAM_LINES_NTSC) * BEAM_TICKS_PER_LINE);
|
||||||
|
gModeSet = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,25 +431,28 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
// rows, which seed all 16 palettes, present, then do pixel-only presents
|
// rows, which seed all 16 palettes, present, then do pixel-only presents
|
||||||
// over rows 190-199 (where a descending-push overrun would land first)
|
// over rows 190-199 (where a descending-push overrun would land first)
|
||||||
// and re-compare $E1 against the stage.
|
// and re-compare $E1 against the stage.
|
||||||
iigsSerialPump();
|
|
||||||
uploadScbAndPaletteIfNeeded(src);
|
uploadScbAndPaletteIfNeeded(src);
|
||||||
iigsBlitStageToShr();
|
iigsBlitStageToShr();
|
||||||
iigsSerialPump();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// GS/OS QuitGS, issued from the pure-constant asm routine in
|
// Release one hold on the tight interrupt-latency bound; the last one out
|
||||||
// joeyDraw.s (iigsQuitGS). The first cut built the call stub in a C
|
// puts the loose bound back.
|
||||||
// BSS array and hit the cross-bank DBR trap on the 10-segment STAXI
|
void iigsIrqLatencyRelax(void) {
|
||||||
// link: the byte stores went DB-relative into bank $08 while the
|
if (gIrqTightHolds == 0u) {
|
||||||
// `jsl gQuitStub` resolved to the array's true bank $10 -- the JSL
|
return;
|
||||||
// executed zeros and BRK'd. The asm routine has no relocations; its
|
}
|
||||||
// parameter block is the fixed scratch word at $E1:9DCC (inside the
|
gIrqTightHolds--;
|
||||||
// survives-present $E1:9DC8-$9DFD window), written here before the
|
irqLatencyApply(gIrqTightHolds != 0u);
|
||||||
// call. QuitGS pCount = 0 = plain quit to the parent (Finder).
|
}
|
||||||
#define IIGS_QUIT_PARM_LO ((volatile uint8_t *)0xE19DCCL)
|
|
||||||
#define IIGS_QUIT_PARM_HI ((volatile uint8_t *)0xE19DCDL)
|
|
||||||
#define IIGS_QUIT_MAILBOX ((volatile uint8_t *)0xE19DC8L)
|
// Take a hold on the tight interrupt-latency bound (see
|
||||||
|
// gIigsPresentChunkRows).
|
||||||
|
void iigsIrqLatencyTighten(void) {
|
||||||
|
gIrqTightHolds++;
|
||||||
|
irqLatencyApply(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// jlMemCopy: MVN moves of up to a bank at a time. MVN's X and Y wrap
|
// jlMemCopy: MVN moves of up to a bank at a time. MVN's X and Y wrap
|
||||||
|
|
@ -476,21 +514,11 @@ void jlpShutdown(void) {
|
||||||
*IIGS_SHADOW_REG = gPreviousShadow;
|
*IIGS_SHADOW_REG = gPreviousShadow;
|
||||||
gModeSet = false;
|
gModeSet = false;
|
||||||
}
|
}
|
||||||
// Quit to GS/OS HERE instead of returning: the llvm-mos runtime's
|
// Returns like every port's: main's return then goes through the
|
||||||
// post-main exit path is broken (a php/rtl thunk at segment offset
|
// runtime's exit(), which runs the atexit handlers (an open serial
|
||||||
// $0072 mis-balances the stack and the machine COPs to the monitor
|
// port's close among them) before GS/OS QUIT. This used to QuitGS
|
||||||
// at $00:0002 -- reproduced identically for KEYS and STAXI, see
|
// itself because that runtime exit path crashed into the monitor
|
||||||
// claudeDocs/LLVM816-ASKS.md #6). jlShutdown is the last thing every example
|
// (LLVM816-ASKS #6, fixed 2026-09-28).
|
||||||
// calls, so ending the process here is equivalent and safe; on
|
|
||||||
// IIgs jlShutdown therefore does not return. If QuitGS itself
|
|
||||||
// errors, fall through to the old (crashing) path rather than
|
|
||||||
// hang. The mailbox marker lets the MAME harness verify the call
|
|
||||||
// was reached ($AA) or returned ($EE).
|
|
||||||
*IIGS_QUIT_PARM_LO = 0u; // QuitGS pCount = 0
|
|
||||||
*IIGS_QUIT_PARM_HI = 0u;
|
|
||||||
*IIGS_QUIT_MAILBOX = 0xAAu;
|
|
||||||
iigsQuitGS();
|
|
||||||
*IIGS_QUIT_MAILBOX = 0xEEu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -559,6 +587,15 @@ static void claimStagePixels(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Point the chunked-SEI asm, and the C paths that bound themselves, at
|
||||||
|
// the tight or the loose interrupt window.
|
||||||
|
static void irqLatencyApply(bool tight) {
|
||||||
|
gIigsPresentChunkRows = tight ? IRQ_PRESENT_ROWS_TIGHT : IRQ_PRESENT_ROWS_LOOSE;
|
||||||
|
gIigsClearChunkBlocks = tight ? IRQ_CLEAR_BLOCKS_TIGHT : IRQ_CLEAR_BLOCKS_LOOSE;
|
||||||
|
gIigsIrqTight = tight ? 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t *jlpStageAllocPixels(void) {
|
uint8_t *jlpStageAllocPixels(void) {
|
||||||
return IIGS_STAGE_PIXELS;
|
return IIGS_STAGE_PIXELS;
|
||||||
}
|
}
|
||||||
|
|
@ -642,10 +679,7 @@ void jlpWaitVBL(void) {
|
||||||
|
|
||||||
tick = iigsGetTickWord();
|
tick = iigsGetTickWord();
|
||||||
while (iigsGetTickWord() == tick) {
|
while (iigsGetTickWord() == tick) {
|
||||||
// The VBL spin is the client loop's longest deaf window (~16ms); drain the 3-byte SCC FIFO
|
// spin until the heartbeat advances
|
||||||
// into the serial soft ring while waiting or a 9600-baud downlink loses mid-frame bytes
|
|
||||||
// (the X68000 lesson, same Z8530).
|
|
||||||
iigsSerialPump();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
171
src/iigs/input.c
171
src/iigs/input.c
|
|
@ -55,15 +55,10 @@
|
||||||
#define IIGS_MODIFIERS ((volatile uint8_t *)0x00C025L)
|
#define IIGS_MODIFIERS ((volatile uint8_t *)0x00C025L)
|
||||||
#define IIGS_KMSTATUS ((volatile uint8_t *)0x00C027L)
|
#define IIGS_KMSTATUS ((volatile uint8_t *)0x00C027L)
|
||||||
|
|
||||||
// Joystick / paddle softswitches.
|
// Joystick buttons (the paddles themselves are read by the asm in joeyDraw.s).
|
||||||
#define IIGS_BTN0 ((volatile uint8_t *)0x00C061L)
|
#define IIGS_BTN0 ((volatile uint8_t *)0x00C061L)
|
||||||
#define IIGS_BTN1 ((volatile uint8_t *)0x00C062L)
|
#define IIGS_BTN1 ((volatile uint8_t *)0x00C062L)
|
||||||
#define IIGS_PADDLE0 ((volatile uint8_t *)0x00C064L)
|
|
||||||
#define IIGS_PADDLE1 ((volatile uint8_t *)0x00C065L)
|
|
||||||
#define IIGS_PTRIG ((volatile uint8_t *)0x00C070L)
|
|
||||||
#define IIGS_BUTTON_BIT 0x80
|
#define IIGS_BUTTON_BIT 0x80
|
||||||
#define IIGS_PADDLE_BUSY 0x80
|
|
||||||
#define PADDLE_TIMEOUT 256
|
|
||||||
#define PADDLE_LO_THRESHOLD 64
|
#define PADDLE_LO_THRESHOLD 64
|
||||||
#define PADDLE_HI_THRESHOLD 192
|
#define PADDLE_HI_THRESHOLD 192
|
||||||
|
|
||||||
|
|
@ -113,12 +108,13 @@
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static void buildAsciiTable(void);
|
static void buildAsciiTable(void);
|
||||||
static void pollJoystick(void);
|
static void pollJoystick(void);
|
||||||
static void pollMouse(void);
|
static uint32_t pollJoystickTight(void);
|
||||||
static void readModifierKeys(void);
|
static void pollMouse(void);
|
||||||
static int8_t signExtend7(uint8_t raw);
|
static void readModifierKeys(void);
|
||||||
static int8_t thresholdPaddle(uint8_t v);
|
static int8_t signExtend7(uint8_t raw);
|
||||||
|
static int8_t thresholdPaddle(uint8_t v);
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
|
|
@ -235,8 +231,8 @@ static int8_t thresholdPaddle(uint8_t v) {
|
||||||
// approximates the paddle's 0..255 position (the Apple firmware
|
// approximates the paddle's 0..255 position (the Apple firmware
|
||||||
// PREAD routine works the same way). The paddle one-shot timer takes ~3 ms to
|
// PREAD routine works the same way). The paddle one-shot timer takes ~3 ms to
|
||||||
// charge at full deflection; if NO joystick is wired up, the BUSY bit
|
// charge at full deflection; if NO joystick is wired up, the BUSY bit
|
||||||
// stays set forever and the busy-wait runs the full PADDLE_TIMEOUT
|
// stays set forever and the asm's settle wait times out every frame --
|
||||||
// every frame -- ~3 ms wasted per frame on a stick that isn't there.
|
// ~1 ms wasted per frame on a stick that isn't there.
|
||||||
//
|
//
|
||||||
// After JOY_DISCONNECT_THRESHOLD consecutive timeouts we latch the
|
// After JOY_DISCONNECT_THRESHOLD consecutive timeouts we latch the
|
||||||
// stick as absent and stop polling entirely. The app calls
|
// stick as absent and stop polling entirely. The app calls
|
||||||
|
|
@ -282,6 +278,53 @@ void jlpJoystickReset(jlJoystickE js) {
|
||||||
// py = (ret >> 16) & 0xFF (JoyY 0..255)
|
// py = (ret >> 16) & 0xFF (JoyY 0..255)
|
||||||
extern uint32_t iigsPollJoystickInner(void);
|
extern uint32_t iigsPollJoystickInner(void);
|
||||||
|
|
||||||
|
// Tight-bound paddle read (pollJoystickTight), used while a client holds
|
||||||
|
// hal.c's tight interrupt-latency bound (gIigsIrqTight; the serial
|
||||||
|
// add-on does while a port is open). The classic read masks interrupts
|
||||||
|
// for its whole count, ~2.8 ms at full deflection; the tight read is
|
||||||
|
// built from passes of iigsJoyPass (joeyDraw.s), none of which masks for
|
||||||
|
// more than JOY_PASS_COUNTS counts (~1.6 ms with its beam stamp):
|
||||||
|
// LOW pass trigger and count at once, on the classic read's beat, so
|
||||||
|
// an axis below the limit reads exactly what the classic
|
||||||
|
// read gives; an axis still timing at the limit is OPEN.
|
||||||
|
// JOY_PASS_FIRST stops it at the first axis to time out.
|
||||||
|
// HIGH pass trigger, let interrupts run for JOY_PASS_HIGH_START
|
||||||
|
// counts' worth of video-beam time, then count to the end,
|
||||||
|
// adding the classic samples the beam says came before
|
||||||
|
// (gIigsJoyStart), so the value can differ from the classic
|
||||||
|
// read's by a count. An axis that counts 0 timed out before
|
||||||
|
// the count began: only a low pass can read it.
|
||||||
|
// The last reading picks the first pass: a stick with both axes low or
|
||||||
|
// centred costs one low pass, one past the low pass's reach a high pass
|
||||||
|
// first, and then, for an axis that was already out when the high pass
|
||||||
|
// began counting (a corner), a low pass that stops at that axis. Each
|
||||||
|
// case costs about what the classic read does. An axis the passes cannot
|
||||||
|
// pin down within JOY_PASS_ATTEMPTS (an interrupt ran long right where a
|
||||||
|
// high pass had to start) keeps its last value.
|
||||||
|
#define JOY_COUNT_RANGE 256u // an 8-bit paddle count
|
||||||
|
#define JOY_PASS_COUNTS 144u // one pass's masked count: a multiple of 3, ~1.55 ms
|
||||||
|
#define JOY_PASS_HIGH_START (JOY_COUNT_RANGE - JOY_PASS_COUNTS) // counts a high pass leaves unmasked
|
||||||
|
#define JOY_PASS_ATTEMPTS 4u
|
||||||
|
// iigsJoyPass flags and mode (joeyDraw.s JOY_PASS_*).
|
||||||
|
#define JOY_PASS_NO_STICK 0x01u
|
||||||
|
#define JOY_PASS_X_OPEN 0x02u
|
||||||
|
#define JOY_PASS_Y_OPEN 0x04u
|
||||||
|
#define JOY_PASS_FIRST 0x0100u
|
||||||
|
#define JOY_AXIS_X 0u
|
||||||
|
#define JOY_AXIS_Y 1u
|
||||||
|
#define JOY_AXES 2u
|
||||||
|
#define JOY_PADDLE_MAX 255u
|
||||||
|
#define JOY_PADDLE_CENTER 128u
|
||||||
|
#define JOY_RESOLVED 0x03u // the classic read's "both axes read"
|
||||||
|
// The classic read's "no stick" result: resolved 0, both axes 128.
|
||||||
|
#define JOY_RESULT_NO_STICK 0x00808000UL
|
||||||
|
|
||||||
|
extern uint32_t iigsJoyPass(uint16_t limitAndMode, uint16_t highStart);
|
||||||
|
extern uint16_t gIigsJoyStart;
|
||||||
|
|
||||||
|
// The last tight reading, for the pass order and the hold.
|
||||||
|
static uint8_t gJoyTightLast[JOY_AXES] = { JOY_PADDLE_CENTER, JOY_PADDLE_CENTER };
|
||||||
|
|
||||||
static void pollJoystick(void) {
|
static void pollJoystick(void) {
|
||||||
uint32_t result;
|
uint32_t result;
|
||||||
uint8_t px;
|
uint8_t px;
|
||||||
|
|
@ -310,7 +353,13 @@ static void pollJoystick(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asm read at 1 MHz -- result returned packed in registers (A:X).
|
// Asm read at 1 MHz -- result returned packed in registers (A:X).
|
||||||
result = iigsPollJoystickInner();
|
// While the interrupt-latency bound is tight, in passes that each
|
||||||
|
// mask interrupts for ~1.6 ms at most (pollJoystickTight).
|
||||||
|
if (gIigsIrqTight != 0u) {
|
||||||
|
result = pollJoystickTight();
|
||||||
|
} else {
|
||||||
|
result = iigsPollJoystickInner();
|
||||||
|
}
|
||||||
resolvedFlags = (uint8_t)(result & 0xFFu);
|
resolvedFlags = (uint8_t)(result & 0xFFu);
|
||||||
px = (uint8_t)((result >> 8) & 0xFFu);
|
px = (uint8_t)((result >> 8) & 0xFFu);
|
||||||
py = (uint8_t)((result >> 16) & 0xFFu);
|
py = (uint8_t)((result >> 16) & 0xFFu);
|
||||||
|
|
@ -371,6 +420,84 @@ static void pollJoystick(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The paddles as iigsPollJoystickInner reports them, read in passes that
|
||||||
|
// never mask interrupts for more than one pass (see JOY_PASS_COUNTS).
|
||||||
|
static uint32_t pollJoystickTight(void) {
|
||||||
|
uint32_t pass;
|
||||||
|
uint32_t result;
|
||||||
|
uint8_t *passBytes;
|
||||||
|
uint8_t *resultBytes;
|
||||||
|
uint8_t value[JOY_AXES];
|
||||||
|
bool known[JOY_AXES];
|
||||||
|
bool needHigh[JOY_AXES];
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t attempt;
|
||||||
|
uint16_t axis;
|
||||||
|
uint16_t count;
|
||||||
|
bool high;
|
||||||
|
bool first;
|
||||||
|
bool needed;
|
||||||
|
|
||||||
|
// Byte aliases, not shifts: a variable or 32-bit shift is a libcall here.
|
||||||
|
passBytes = (uint8_t *)&pass;
|
||||||
|
resultBytes = (uint8_t *)&result;
|
||||||
|
known[JOY_AXIS_X] = false;
|
||||||
|
known[JOY_AXIS_Y] = false;
|
||||||
|
needHigh[JOY_AXIS_X] = false;
|
||||||
|
needHigh[JOY_AXIS_Y] = false;
|
||||||
|
high = gJoyTightLast[JOY_AXIS_X] > JOY_PASS_COUNTS || gJoyTightLast[JOY_AXIS_Y] > JOY_PASS_COUNTS;
|
||||||
|
first = false;
|
||||||
|
for (attempt = 0u; attempt < JOY_PASS_ATTEMPTS; attempt++) {
|
||||||
|
pass = iigsJoyPass(first ? (JOY_PASS_COUNTS | JOY_PASS_FIRST) : JOY_PASS_COUNTS, high ? JOY_PASS_HIGH_START : 0u);
|
||||||
|
flags = (uint16_t)(passBytes[2] | ((uint16_t)passBytes[3] << 8));
|
||||||
|
if ((flags & JOY_PASS_NO_STICK) != 0u) {
|
||||||
|
return JOY_RESULT_NO_STICK;
|
||||||
|
}
|
||||||
|
for (axis = 0u; axis < JOY_AXES; axis++) {
|
||||||
|
if (known[axis]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count = passBytes[axis];
|
||||||
|
if (!high) {
|
||||||
|
if ((flags & (JOY_PASS_X_OPEN << axis)) == 0u) {
|
||||||
|
value[axis] = (uint8_t)count;
|
||||||
|
known[axis] = true;
|
||||||
|
} else if (!first) {
|
||||||
|
needHigh[axis] = true;
|
||||||
|
}
|
||||||
|
} else if (count != 0u) {
|
||||||
|
count = (uint16_t)(count + gIigsJoyStart);
|
||||||
|
if ((flags & (JOY_PASS_X_OPEN << axis)) != 0u || count > JOY_PADDLE_MAX) {
|
||||||
|
count = JOY_PADDLE_MAX;
|
||||||
|
}
|
||||||
|
value[axis] = (uint8_t)count;
|
||||||
|
known[axis] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (known[JOY_AXIS_X] && known[JOY_AXIS_Y]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Next: a high pass again only for an axis a low pass left timing
|
||||||
|
// (an interrupt ran long where the last high pass had to start). A
|
||||||
|
// single axis a high pass missed is the lower one, so the low pass
|
||||||
|
// that follows can stop at it.
|
||||||
|
needed = (!known[JOY_AXIS_X] && needHigh[JOY_AXIS_X]) || (!known[JOY_AXIS_Y] && needHigh[JOY_AXIS_Y]);
|
||||||
|
first = high && !needed && known[JOY_AXIS_X] != known[JOY_AXIS_Y];
|
||||||
|
high = needed;
|
||||||
|
}
|
||||||
|
for (axis = 0u; axis < JOY_AXES; axis++) {
|
||||||
|
if (known[axis]) {
|
||||||
|
gJoyTightLast[axis] = value[axis];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultBytes[0] = JOY_RESOLVED;
|
||||||
|
resultBytes[1] = gJoyTightLast[JOY_AXIS_X];
|
||||||
|
resultBytes[2] = gJoyTightLast[JOY_AXIS_Y];
|
||||||
|
resultBytes[3] = 0u;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Drain one X+Y delta pair from the ADB mouse FIFO. $C027 bit 1 tells
|
// Drain one X+Y delta pair from the ADB mouse FIFO. $C027 bit 1 tells
|
||||||
// us which coordinate the next $C024 read will return; we honor that
|
// us which coordinate the next $C024 read will return; we honor that
|
||||||
// rather than assuming an order, so we stay in sync even if a stray
|
// rather than assuming an order, so we stay in sync even if a stray
|
||||||
|
|
@ -433,12 +560,6 @@ void jlpInputInit(void) {
|
||||||
|
|
||||||
|
|
||||||
void jlpInputPoll(void) {
|
void jlpInputPoll(void) {
|
||||||
// Keep the serial soft ring fed across the input scan (multiple I/O-page reads).
|
|
||||||
{
|
|
||||||
extern void iigsSerialPump(void);
|
|
||||||
|
|
||||||
iigsSerialPump();
|
|
||||||
}
|
|
||||||
uint8_t kbd;
|
uint8_t kbd;
|
||||||
uint8_t ascii;
|
uint8_t ascii;
|
||||||
uint8_t key;
|
uint8_t key;
|
||||||
|
|
@ -457,14 +578,6 @@ void jlpInputPoll(void) {
|
||||||
// ever fails to clear.
|
// ever fails to clear.
|
||||||
strobeObserved = false;
|
strobeObserved = false;
|
||||||
for (drainGuard = 0; drainGuard < KBD_DRAIN_GUARD; drainGuard++) {
|
for (drainGuard = 0; drainGuard < KBD_DRAIN_GUARD; drainGuard++) {
|
||||||
{
|
|
||||||
extern void iigsSerialPump(void);
|
|
||||||
|
|
||||||
// Per-iteration pump (the X68000 per-group lesson): the FIFO drain + decode below is a
|
|
||||||
// multi-ms deaf window at 9600 and the Z8530 FIFO is 3 bytes - a pump only at poll ENTRY
|
|
||||||
// measurably lost a 4-byte clump mid-frame.
|
|
||||||
iigsSerialPump();
|
|
||||||
}
|
|
||||||
kbd = *IIGS_KBD;
|
kbd = *IIGS_KBD;
|
||||||
if ((kbd & KBD_STROBE_BIT) == 0) {
|
if ((kbd & KBD_STROBE_BIT) == 0) {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,12 @@ clrExit:
|
||||||
; inhibited by jlpInit. ~1.5x faster.
|
; inhibited by jlpInit. ~1.5x faster.
|
||||||
;
|
;
|
||||||
; SEI guards the AUXWRITE/SP-hijack window; SP is restored before CLI.
|
; SEI guards the AUXWRITE/SP-hijack window; SP is restored before CLI.
|
||||||
|
; The hold is chunked like the present's: every gIigsClearChunkBlocks
|
||||||
|
; blocks (hal.c; ~0.12 ms a block) the real stack and bank-0 writes come
|
||||||
|
; back for a CLI/SEI pair, so a pending interrupt waits at most one chunk.
|
||||||
|
; The loose bound (200) is the old single ~23 ms hold; the tight one
|
||||||
|
; (12, ~1.4 ms) fits the Z8530's 3-byte receive FIFO at 9600 baud and
|
||||||
|
; costs ~0.2 ms a clear.
|
||||||
; SAFE ONLY POST-jlpInit. Soft switches use LONG addressing to bank $E0
|
; SAFE ONLY POST-jlpInit. Soft switches use LONG addressing to bank $E0
|
||||||
; ($E0C005), NOT `sta $C005`: under the GS/OS Loader DBR=$08, a DBR-rel
|
; ($E0C005), NOT `sta $C005`: under the GS/OS Loader DBR=$08, a DBR-rel
|
||||||
; `sta $C0xx` hits bank-$08 RAM and the switch silently never toggles, so
|
; `sta $C0xx` hits bank-$08 RAM and the switch silently never toggles, so
|
||||||
|
|
@ -202,6 +208,7 @@ iigsSurfaceClearFastInner:
|
||||||
tcs ; SP = top of $01 stage region
|
tcs ; SP = top of $01 stage region
|
||||||
lda sclrFillCap ; A = fillWord = the pushed value
|
lda sclrFillCap ; A = fillWord = the pushed value
|
||||||
ldx #200 ; 200 blocks x 80 PHA = 16000 pushes = 32000 bytes
|
ldx #200 ; 200 blocks x 80 PHA = 16000 pushes = 32000 bytes
|
||||||
|
ldy gIigsClearChunkBlocks ; blocks until the next interrupt window
|
||||||
clrFastBlock:
|
clrFastBlock:
|
||||||
pha
|
pha
|
||||||
pha
|
pha
|
||||||
|
|
@ -284,7 +291,34 @@ clrFastBlock:
|
||||||
pha
|
pha
|
||||||
pha
|
pha
|
||||||
dex
|
dex
|
||||||
|
beq clrFastDone
|
||||||
|
dey
|
||||||
bne clrFastBlock
|
bne clrFastBlock
|
||||||
|
; Interrupt window. AUXWRITE goes off BEFORE anything is stored: a
|
||||||
|
; DBR-relative store to bank 0 would otherwise land in bank $01.
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
sta 0xE0C004 ; AUXWRITE off
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
tsc
|
||||||
|
sta sclrSlamSp ; where the slam resumes
|
||||||
|
lda sclrSavedSp
|
||||||
|
tcs ; the caller's stack for any interrupt
|
||||||
|
cli
|
||||||
|
nop ; a pending IRQ is taken here
|
||||||
|
sei
|
||||||
|
lda sclrSlamSp
|
||||||
|
tcs
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
sta 0xE0C005 ; AUXWRITE on
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda sclrFillCap
|
||||||
|
ldy gIigsClearChunkBlocks
|
||||||
|
brl clrFastBlock
|
||||||
|
clrFastDone:
|
||||||
lda sclrSavedSp
|
lda sclrSavedSp
|
||||||
tcs ; restore original SP
|
tcs ; restore original SP
|
||||||
sep #0x20
|
sep #0x20
|
||||||
|
|
@ -314,6 +348,11 @@ sclrFillCap:
|
||||||
sclrSavedSp:
|
sclrSavedSp:
|
||||||
.zero 2
|
.zero 2
|
||||||
|
|
||||||
|
.section .bss.sclrSlamSp,"aw"
|
||||||
|
.globl sclrSlamSp
|
||||||
|
sclrSlamSp:
|
||||||
|
.zero 2
|
||||||
|
|
||||||
; Flood-fill seed/match scratch (asm-only data; no C definition exists).
|
; Flood-fill seed/match scratch (asm-only data; no C definition exists).
|
||||||
; iigsFloodWalkAndScansInner writes the seed match result and the left/
|
; iigsFloodWalkAndScansInner writes the seed match result and the left/
|
||||||
; right span extents here for the C wrapper to read back.
|
; right span extents here for the C wrapper to read back.
|
||||||
|
|
@ -4661,6 +4700,13 @@ iigsBlitStageToShr:
|
||||||
; PLAN.md W2). The old rule both UNDER-promoted left-anchored
|
; PLAN.md W2). The old rule both UNDER-promoted left-anchored
|
||||||
; mid-width bands and OVER-promoted right-anchored ones.
|
; mid-width bands and OVER-promoted right-anchored ones.
|
||||||
PEI_RUN_MIN = 8
|
PEI_RUN_MIN = 8
|
||||||
|
; Interrupt-latency bound: gIigsPresentChunkRows (hal.c) rows are consumed
|
||||||
|
; per SEI chunk, ~0.42 ms a full-width row. Loose (40, ~17 ms) is free;
|
||||||
|
; tight (4, ~1.8 ms) fits the Z8530 SCC's 3-byte receive FIFO at 9600 baud
|
||||||
|
; (~3.1 ms; MEASURED zero loss under MAME, whose FIFO keeps only 2, where
|
||||||
|
; 6 rows already lost bytes; 40 rows lost ~42% of a 9600 stream). MEASURED
|
||||||
|
; tight cost: a full-screen present 83.1 -> 87.2 ms (each extra chunk
|
||||||
|
; re-pays the run setup, ~90 us); clean rows are unaffected.
|
||||||
|
|
||||||
php
|
php
|
||||||
phb
|
phb
|
||||||
|
|
@ -4706,7 +4752,7 @@ PEI_RUN_MIN = 8
|
||||||
ldx #0 ; X = absolute row counter
|
ldx #0 ; X = absolute row counter
|
||||||
|
|
||||||
; ---------------------------------------------------------------
|
; ---------------------------------------------------------------
|
||||||
; Chunked-SEI scan. Contract UNCHANGED: sei per chunk, <= 40 rows
|
; Chunked-SEI scan. Contract: sei per chunk, <= gIigsPresentChunkRows rows
|
||||||
; consumed per chunk, cli at chunk end so the latched VBL IRQ catches
|
; consumed per chunk, cli at chunk end so the latched VBL IRQ catches
|
||||||
; up (this is what keeps the GetTick bench clock honest). Runs are
|
; up (this is what keeps the GetTick bench clock honest). Runs are
|
||||||
; clipped at the chunk budget, so no SEI hold got longer -- every row
|
; clipped at the chunk budget, so no SEI hold got longer -- every row
|
||||||
|
|
@ -4805,7 +4851,7 @@ peiRowSkipGroup:
|
||||||
peiRowBounds:
|
peiRowBounds:
|
||||||
cpx #200
|
cpx #200
|
||||||
bcs peiChunkDone
|
bcs peiChunkDone
|
||||||
cpy #40
|
cpy gIigsPresentChunkRows
|
||||||
bcc peiRowLoop
|
bcc peiRowLoop
|
||||||
peiChunkDone:
|
peiChunkDone:
|
||||||
; Per-chunk teardown is now JUST the CLI: runs restore SP and switches
|
; Per-chunk teardown is now JUST the CLI: runs restore SP and switches
|
||||||
|
|
@ -4841,7 +4887,7 @@ peiRunGather:
|
||||||
iny
|
iny
|
||||||
cpx #200
|
cpx #200
|
||||||
bcs peiRunGathered
|
bcs peiRunGathered
|
||||||
cpy #40
|
cpy gIigsPresentChunkRows
|
||||||
bcs peiRunGathered ; chunk budget clips the run
|
bcs peiRunGathered ; chunk budget clips the run
|
||||||
lda gStageMinWord,x
|
lda gStageMinWord,x
|
||||||
inc a
|
inc a
|
||||||
|
|
@ -4875,7 +4921,7 @@ peiRunGathered: ; X/Y = scan resume values
|
||||||
txa
|
txa
|
||||||
sec
|
sec
|
||||||
sbc gPeiRunFirst
|
sbc gPeiRunFirst
|
||||||
sta gPeiRunLen ; rows in this run (1..40)
|
sta gPeiRunLen ; rows in this run (1..gIigsPresentChunkRows)
|
||||||
; STAGE B: computed entry. spOffset = 2*runMax + 1, parked
|
; STAGE B: computed entry. spOffset = 2*runMax + 1, parked
|
||||||
; at $E1:9DFE (the ONLY datum the switches-on loop loads);
|
; at $E1:9DFE (the ONLY datum the switches-on loop loads);
|
||||||
; the burst enters the PEI table at word runMax, pushing
|
; the burst enters the PEI table at word runMax, pushing
|
||||||
|
|
@ -5048,7 +5094,7 @@ peiRunDone:
|
||||||
lda gPeiOrigSp
|
lda gPeiOrigSp
|
||||||
tcs ; caller stack back
|
tcs ; caller stack back
|
||||||
; telemetry (W2 Stage 0): gPresentCopiedBytes +=
|
; telemetry (W2 Stage 0): gPresentCopiedBytes +=
|
||||||
; runLen * bytesPerRow. Repeated add (runLen <= 40),
|
; runLen * bytesPerRow. Repeated add (runLen <= gIigsPresentChunkRows),
|
||||||
; ~13 cyc per slammed row -- accepted for verifiability.
|
; ~13 cyc per slammed row -- accepted for verifiability.
|
||||||
; DBR is the caller's globals bank (slam never touches
|
; DBR is the caller's globals bank (slam never touches
|
||||||
; DBR), so the absolute counter access is safe.
|
; DBR), so the absolute counter access is safe.
|
||||||
|
|
@ -5112,7 +5158,7 @@ peiMvnGather:
|
||||||
iny
|
iny
|
||||||
cpx #200
|
cpx #200
|
||||||
bcs peiMvnRunGo
|
bcs peiMvnRunGo
|
||||||
cpy #40
|
cpy gIigsPresentChunkRows
|
||||||
bcs peiMvnRunGo
|
bcs peiMvnRunGo
|
||||||
lda gStageMinWord,x
|
lda gStageMinWord,x
|
||||||
cmp gPeiBandMin
|
cmp gPeiBandMin
|
||||||
|
|
@ -5791,6 +5837,407 @@ gjNoJoy:
|
||||||
rtl ; uint32_t in A:X
|
rtl ; uint32_t in A:X
|
||||||
|
|
||||||
|
|
||||||
|
; ====================================================================
|
||||||
|
; iigsJoyPass(uint16_t limitAndMode, uint16_t highStart)
|
||||||
|
;
|
||||||
|
; One pass of the tight-bound paddle read (input.c pollJoystickTight),
|
||||||
|
; used while a client holds hal.c's tight interrupt-latency bound. The
|
||||||
|
; classic read above masks interrupts for its whole count, ~2.8 ms at
|
||||||
|
; full deflection, and the Z8530 SCC's receive FIFO lasts ~3.1 ms at
|
||||||
|
; 9600 baud (~2.3 ms under MAME). A pass masks for at most its limit in
|
||||||
|
; counts plus a beam stamp, and it counts with the classic dual/solo loop
|
||||||
|
; on the classic 11-cycle beat, so a count means the same thing in both.
|
||||||
|
;
|
||||||
|
; highStart == 0 LOW pass: trigger and count at once. The trigger-
|
||||||
|
; to-first-sample sequence matches the classic read's
|
||||||
|
; cycle for cycle, so an axis that times out inside
|
||||||
|
; the limit reads exactly what the classic read gives.
|
||||||
|
; With JOY_PASS_FIRST it stops at the first axis to
|
||||||
|
; time out, leaving the other OPEN.
|
||||||
|
; highStart != 0 HIGH pass: trigger, let interrupts run for
|
||||||
|
; highStart counts' worth of beam ticks, then count.
|
||||||
|
; gIigsJoyStart says how many of the classic read's
|
||||||
|
; samples came before the pass's first, rounded; an
|
||||||
|
; axis that timed out before that sample counts 0, as
|
||||||
|
; in the classic loop.
|
||||||
|
;
|
||||||
|
; Beam ticks: VERTCNT/HORIZCNT as line * 65 + position (0..64), i.e.
|
||||||
|
; 1 MHz cycles on any IIgs (65 to a line), wrapped at gIigsBeamFrameTicks
|
||||||
|
; (hal.c). A classic count is 11 of them, and so is one here.
|
||||||
|
;
|
||||||
|
; ABI: limitAndMode in A: the limit in counts in the low byte (a multiple
|
||||||
|
; of 3, 3..255: the dual loop only tests for the wrap every third count),
|
||||||
|
; JOY_PASS_FIRST in the high byte; highStart @4,s on JSL entry. Returns
|
||||||
|
; uint32_t in A:X: A = px | (py << 8), the axes' counts (1-based like the
|
||||||
|
; classic read's), X = JOY_PASS_* flags (input.c mirrors them).
|
||||||
|
; Interrupts are re-enabled for the settle and the high pass's delay
|
||||||
|
; whatever the caller had.
|
||||||
|
; ====================================================================
|
||||||
|
JOY_PASS_NO_STICK = 0x01 ; a paddle never timed out: no stick
|
||||||
|
JOY_PASS_X_OPEN = 0x02 ; X still timing at the limit
|
||||||
|
JOY_PASS_Y_OPEN = 0x04 ; Y still timing at the limit
|
||||||
|
JOY_PASS_FIRST = 0x0100 ; low pass: stop at the first axis out
|
||||||
|
JOY_TICKS_PER_COUNT = 11 ; the classic loop's 11 cycles at 1 MHz
|
||||||
|
; Beam ticks a high pass's first sample comes after its count stamp,
|
||||||
|
; measured the way the classic read counts, from cycle counts (a 2.8 MHz
|
||||||
|
; cycle is 5/14 of a tick, an I/O access ~1.5): 52 from the count stamp
|
||||||
|
; to the first sample (all at 1 MHz), less 18.8 from the trigger stamp
|
||||||
|
; to the trigger and 15.1 from the trigger to the classic first sample.
|
||||||
|
JOY_START_TICKS = 18
|
||||||
|
; Settle tries, interrupts running: the previous pass can leave an axis
|
||||||
|
; timing for up to ~1.3 ms, and a try is ~3.5 us at 2.8 MHz.
|
||||||
|
JOY_SETTLE_TRIES = 0x0800
|
||||||
|
|
||||||
|
; JOY_DUAL_LOOP: the classic read's dual counter, instruction for
|
||||||
|
; instruction: both paddles sampled every ~11 cycles (10, 10, 13) while
|
||||||
|
; both still time, to `toSolo` when one stops. Falls through when X wraps
|
||||||
|
; to 0, i.e. at the pass's limit. In: M=8, X=8, DP = $C000, 1 MHz.
|
||||||
|
.macro JOY_DUAL_LOOP toSolo
|
||||||
|
1:
|
||||||
|
lda 0x64
|
||||||
|
and 0x65
|
||||||
|
bpl \toSolo
|
||||||
|
inx
|
||||||
|
lda 0x64
|
||||||
|
and 0x65
|
||||||
|
bpl \toSolo
|
||||||
|
inx
|
||||||
|
lda 0x64
|
||||||
|
and 0x65
|
||||||
|
bpl \toSolo
|
||||||
|
inx
|
||||||
|
bne 1b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.section .text.iigsJoyPass,"ax"
|
||||||
|
.globl iigsJoyPass
|
||||||
|
iigsJoyPass:
|
||||||
|
php
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
phd
|
||||||
|
pha
|
||||||
|
and #JOY_PASS_FIRST
|
||||||
|
sta joyMode
|
||||||
|
pla
|
||||||
|
and #0x00FF
|
||||||
|
eor #0x00FF
|
||||||
|
inc a
|
||||||
|
sta joyX0 ; 256 - limit: X wraps to 0 at the limit
|
||||||
|
lda 7,s ; highStart (+3: php, phd)
|
||||||
|
sta joyDelay
|
||||||
|
asl a
|
||||||
|
sta joyDelayTicks
|
||||||
|
asl a
|
||||||
|
asl a ; * 8
|
||||||
|
clc
|
||||||
|
adc joyDelayTicks ; * 10
|
||||||
|
adc 7,s ; * 11: the delay in beam ticks
|
||||||
|
sta joyDelayTicks
|
||||||
|
stz joyFlags
|
||||||
|
stz joyPx
|
||||||
|
stz joyPy
|
||||||
|
pea 0xC000
|
||||||
|
pld ; DP = $C000: paddle, beam and speed I/O
|
||||||
|
; Settle: both paddles timed out (a previous pass can leave one timing).
|
||||||
|
ldy #JOY_SETTLE_TRIES
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
jpSettle:
|
||||||
|
lda 0x64
|
||||||
|
ora 0x65
|
||||||
|
bpl jpSettled
|
||||||
|
dey
|
||||||
|
bne jpSettle
|
||||||
|
lda #JOY_PASS_NO_STICK
|
||||||
|
sta joyFlags
|
||||||
|
brl jpDone
|
||||||
|
jpSettled:
|
||||||
|
sei
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda joyDelay
|
||||||
|
beq jpLow
|
||||||
|
brl jpHigh
|
||||||
|
jpLow:
|
||||||
|
lda joyMode
|
||||||
|
beq jpLowBoth
|
||||||
|
brl jpLowFirst
|
||||||
|
; LOW pass: from the trigger on, the classic read's exact sequence.
|
||||||
|
jpLowBoth:
|
||||||
|
sep #0x30
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
bit 0x70 ; PTRIG: start X,Y timers
|
||||||
|
lsr 0x36 ; force 1 MHz (C = CYAREG bit 0, kept for the rol)
|
||||||
|
ldx joyX0 ; 4 + 2 + 2 cycles: the classic ldx #1 / xba / xba
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
jpDualXY0:
|
||||||
|
JOY_DUAL_LOOP jpToSolo
|
||||||
|
lda #JOY_PASS_X_OPEN | JOY_PASS_Y_OPEN
|
||||||
|
bra jpOpen
|
||||||
|
jpSameXY:
|
||||||
|
dex
|
||||||
|
txy
|
||||||
|
bra jpGotXY
|
||||||
|
jpSoloX:
|
||||||
|
bit 0x64
|
||||||
|
bmi jpSoloXOk
|
||||||
|
dey
|
||||||
|
bra jpGotXY
|
||||||
|
jpSoloXOk:
|
||||||
|
inx
|
||||||
|
bne jpSoloX
|
||||||
|
dey ; Y timed out first: the classic finish's dey
|
||||||
|
lda #JOY_PASS_X_OPEN
|
||||||
|
bra jpOpen
|
||||||
|
jpSoloY:
|
||||||
|
bit 0x65
|
||||||
|
bmi jpSoloYOk
|
||||||
|
dex
|
||||||
|
bra jpGotXY
|
||||||
|
jpToSolo:
|
||||||
|
txy
|
||||||
|
bit 0x64
|
||||||
|
bmi jpSoloXOk
|
||||||
|
bit 0x65
|
||||||
|
bpl jpSameXY
|
||||||
|
jpSoloYOk:
|
||||||
|
iny
|
||||||
|
bne jpSoloY
|
||||||
|
dex ; X timed out first: the classic finish's dex
|
||||||
|
lda #JOY_PASS_Y_OPEN
|
||||||
|
jpOpen:
|
||||||
|
tsb joyFlags
|
||||||
|
jpGotXY:
|
||||||
|
rol 0x36 ; restore CPU speed (C untouched by the loop)
|
||||||
|
txa
|
||||||
|
sec
|
||||||
|
sbc joyX0
|
||||||
|
inc a
|
||||||
|
sta joyPx ; count - X0 + 1: 1-based like the classic read
|
||||||
|
tya
|
||||||
|
sec
|
||||||
|
sbc joyX0
|
||||||
|
inc a
|
||||||
|
sta joyPy
|
||||||
|
bra jpDone
|
||||||
|
|
||||||
|
; LOW pass that stops at the first axis to time out: the same trigger
|
||||||
|
; sequence and dual counter, then the classic's own decisions about which
|
||||||
|
; axis stopped (and its finishing dex/dey) without counting on.
|
||||||
|
jpLowFirst:
|
||||||
|
sep #0x30
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
bit 0x70 ; PTRIG
|
||||||
|
lsr 0x36 ; force 1 MHz (C = CYAREG bit 0)
|
||||||
|
ldx joyX0
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
JOY_DUAL_LOOP jpFirstOut
|
||||||
|
lda #JOY_PASS_X_OPEN | JOY_PASS_Y_OPEN
|
||||||
|
bra jpOpen
|
||||||
|
jpFirstOut:
|
||||||
|
txy
|
||||||
|
bit 0x64
|
||||||
|
bmi jpFirstY ; X still timing: Y stopped first
|
||||||
|
bit 0x65
|
||||||
|
bpl jpSameXY ; both at once
|
||||||
|
dex ; X stopped first
|
||||||
|
lda #JOY_PASS_Y_OPEN
|
||||||
|
bra jpOpen
|
||||||
|
jpFirstY:
|
||||||
|
dey
|
||||||
|
lda #JOY_PASS_X_OPEN
|
||||||
|
bra jpOpen
|
||||||
|
|
||||||
|
; HIGH pass: trigger, interrupts on for highStart counts of beam time, then count.
|
||||||
|
jpHigh:
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
jsr joyStamp ; the trigger's stamp
|
||||||
|
sta joyStampTrig
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
bit 0x70 ; PTRIG
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda joyStampTrig
|
||||||
|
jsr joyLinear
|
||||||
|
sta joyLinTrig
|
||||||
|
cli
|
||||||
|
jpDelay:
|
||||||
|
jsr joyStamp
|
||||||
|
jsr joyElapsed
|
||||||
|
cmp joyDelayTicks
|
||||||
|
bcc jpDelay
|
||||||
|
sei
|
||||||
|
sep #0x30
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
lsr 0x36 ; force 1 MHz (C = CYAREG bit 0)
|
||||||
|
; The count's start stamp, taken at 1 MHz right before the loop: MAME
|
||||||
|
; re-times a whole timeslice when the CPU clock changes, so a stamp taken
|
||||||
|
; before the switch sits up to ~0.1 ms off the samples after it.
|
||||||
|
php ; keep C for the rol
|
||||||
|
jsr joyStamp
|
||||||
|
.a16
|
||||||
|
sta joyStampEnd
|
||||||
|
plp
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
ldx joyX0
|
||||||
|
brl jpDualXY0 ; an axis already timed out counts 0
|
||||||
|
|
||||||
|
jpDone:
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
pld
|
||||||
|
plp ; the caller's I, M=16, X=16
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
; A high pass's start in classic counts, (span + JOY_START_TICKS) / 11
|
||||||
|
; rounded, worked out with interrupts back on: 16 shift-subtract steps.
|
||||||
|
lda joyDelay
|
||||||
|
beq jpPack
|
||||||
|
lda joyStampEnd
|
||||||
|
jsr joyElapsed
|
||||||
|
clc
|
||||||
|
adc #JOY_START_TICKS + JOY_TICKS_PER_COUNT / 2
|
||||||
|
sta gIigsJoyStart ; dividend, becomes the quotient
|
||||||
|
lda #0 ; remainder
|
||||||
|
ldx #16
|
||||||
|
jpDivide:
|
||||||
|
asl gIigsJoyStart
|
||||||
|
rol a
|
||||||
|
cmp #JOY_TICKS_PER_COUNT
|
||||||
|
bcc jpDivideNext
|
||||||
|
sbc #JOY_TICKS_PER_COUNT
|
||||||
|
inc gIigsJoyStart
|
||||||
|
jpDivideNext:
|
||||||
|
dex
|
||||||
|
bne jpDivide
|
||||||
|
jpPack:
|
||||||
|
lda joyPy
|
||||||
|
xba
|
||||||
|
ora joyPx
|
||||||
|
ldx joyFlags
|
||||||
|
rtl
|
||||||
|
|
||||||
|
; joyStamp: the raw beam position, HORIZCNT:VERTCNT (VERTCNT in the low
|
||||||
|
; byte), read twice so a VERTCNT step between the two bytes cannot tear
|
||||||
|
; it. In: DP = $C000, any M. Out: A16. X, Y untouched.
|
||||||
|
joyStamp:
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda 0x2E ; VERTCNT, then HORIZCNT a cycle later
|
||||||
|
pha
|
||||||
|
lda 0x2E
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
cmp 1,s ; VERTCNT the same both times?
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
beq 1f
|
||||||
|
sta 1,s ; it stepped under the first read: take the second
|
||||||
|
1:
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; joyLinear: a raw beam position as ticks, line * 65 + position, where
|
||||||
|
; the line is VERTCNT * 2 + HORIZCNT bit 7 and the position is 0 for
|
||||||
|
; HORIZCNT $00 and 1..64 for $40..$7F. In/out A16; X, Y untouched.
|
||||||
|
joyLinear:
|
||||||
|
.a16
|
||||||
|
sta joyLinRaw
|
||||||
|
xba ; low byte = HORIZCNT
|
||||||
|
and #0x007F
|
||||||
|
bit #0x0040
|
||||||
|
beq 1f
|
||||||
|
and #0x003F
|
||||||
|
inc a
|
||||||
|
1:
|
||||||
|
sta joyLinPos
|
||||||
|
lda joyLinRaw
|
||||||
|
and #0x00FF
|
||||||
|
asl a ; VERTCNT * 2
|
||||||
|
bit joyLinRaw ; N = HORIZCNT bit 7: the line's low bit
|
||||||
|
bpl 2f
|
||||||
|
inc a
|
||||||
|
2:
|
||||||
|
sta joyLinRaw ; the line
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a ; line * 64
|
||||||
|
clc
|
||||||
|
adc joyLinRaw ; line * 65
|
||||||
|
adc joyLinPos
|
||||||
|
rts
|
||||||
|
|
||||||
|
; joyElapsed: ticks from joyLinTrig to a raw beam position, across a
|
||||||
|
; frame wrap. In/out A16; X, Y untouched.
|
||||||
|
joyElapsed:
|
||||||
|
.a16
|
||||||
|
jsr joyLinear
|
||||||
|
sec
|
||||||
|
sbc joyLinTrig
|
||||||
|
bcs 1f
|
||||||
|
clc
|
||||||
|
adc gIigsBeamFrameTicks ; the beam wrapped into the next frame
|
||||||
|
1:
|
||||||
|
rts
|
||||||
|
|
||||||
|
; iigsJoyPass scratch (asm-only data) and the high pass's start output,
|
||||||
|
; read by input.c.
|
||||||
|
.section .bss.joyX0,"aw"
|
||||||
|
joyX0:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyDelay,"aw"
|
||||||
|
joyDelay:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyMode,"aw"
|
||||||
|
joyMode:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyDelayTicks,"aw"
|
||||||
|
joyDelayTicks:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyFlags,"aw"
|
||||||
|
joyFlags:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyPx,"aw"
|
||||||
|
joyPx:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyPy,"aw"
|
||||||
|
joyPy:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyStampTrig,"aw"
|
||||||
|
joyStampTrig:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyStampEnd,"aw"
|
||||||
|
joyStampEnd:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyLinTrig,"aw"
|
||||||
|
joyLinTrig:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyLinRaw,"aw"
|
||||||
|
joyLinRaw:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.joyLinPos,"aw"
|
||||||
|
joyLinPos:
|
||||||
|
.zero 2
|
||||||
|
.section .bss.gIigsJoyStart,"aw"
|
||||||
|
.globl gIigsJoyStart
|
||||||
|
gIigsJoyStart:
|
||||||
|
.zero 2
|
||||||
|
|
||||||
|
|
||||||
; ====================================================================
|
; ====================================================================
|
||||||
; iigsInputSnapshot(void)
|
; iigsInputSnapshot(void)
|
||||||
;
|
;
|
||||||
|
|
@ -6912,23 +7359,3 @@ wsDoubledByte:
|
||||||
.globl wsHasTrail
|
.globl wsHasTrail
|
||||||
wsHasTrail:
|
wsHasTrail:
|
||||||
.zero 2
|
.zero 2
|
||||||
|
|
||||||
|
|
||||||
; ---------------------------------------------------------------------
|
|
||||||
; iigsQuitGS -- issue GS/OS QuitGS ($2029) via the inline convention
|
|
||||||
; and never return on success. Pure constants on purpose: the first
|
|
||||||
; workaround built this stub in a C BSS array and hit the cross-bank
|
|
||||||
; DBR trap (byte stores went DB-relative to bank $08 while the jsl
|
|
||||||
; resolved to the array's true bank $10 -- executed zeros, BRK). The
|
|
||||||
; parameter block lives at the FIXED scratch address $E1:9DCC (inside
|
|
||||||
; the $E1:9DC8-$9DFD survives-present window; present parks $9DFE) and
|
|
||||||
; the C caller writes its pCount=0 word before calling, so this
|
|
||||||
; routine contains no relocations at all. If QuitGS errors, we rtl
|
|
||||||
; and the caller falls back to the (broken) runtime exit path.
|
|
||||||
.section .text.iigsQuitGS,"ax"
|
|
||||||
.globl iigsQuitGS
|
|
||||||
iigsQuitGS:
|
|
||||||
jsl 0xE100A8
|
|
||||||
.word 0x2029
|
|
||||||
.long 0x00E19DCC
|
|
||||||
rtl
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Apple IIgs serial HAL -- direct, polled register access.
|
// Apple IIgs serial HAL -- direct register access, interrupt-driven receive.
|
||||||
//
|
//
|
||||||
// Three endpoints behind one API, selected at open:
|
// Three endpoints behind one API, selected at open:
|
||||||
// JL_SERIAL_MODEM -- modem port = Zilog 8530 SCC channel B
|
// JL_SERIAL_MODEM -- modem port = Zilog 8530 SCC channel B
|
||||||
|
|
@ -14,22 +14,46 @@
|
||||||
// table.
|
// table.
|
||||||
//
|
//
|
||||||
// Direct register pokes through 24-bit long pointers to $00C0xx, exactly like
|
// Direct register pokes through 24-bit long pointers to $00C0xx, exactly like
|
||||||
// input.c / the video + DOC pokes -- no GS/OS driver, no ROM firmware, lowest
|
// input.c / the video + DOC pokes -- no GS/OS driver and no ROM serial
|
||||||
// latency, and it does not fight the GS/OS heartbeat (JoeyLib's stated policy
|
// firmware. RECEIVE IS INTERRUPT-DRIVEN (serialIrq.s): the open port's
|
||||||
// is to avoid ISRs that do). RX is drained on jlSerialPoll() into a bank-0
|
// receive interrupt reaches a handler through the ROM interrupt manager --
|
||||||
// ring; TX is a bounded poll on the transmit-empty status bit. The app owns
|
// the SCC through the Misc Tool Set's serial vector, a Super Serial Card
|
||||||
// the port exclusively between jlSerialOpen and jlSerialClose.
|
// through its "other interrupt" vector -- and the handler moves every byte
|
||||||
|
// into the ring the moment it lands. Nothing outside this add-on has to pump
|
||||||
|
// the port: the Z8530 holds 3 received bytes (~3 ms at 9600 baud) and the 6551
|
||||||
|
// one (~1 ms), while a full-screen present takes ~74 ms. TX is a bounded poll
|
||||||
|
// on the transmit-empty status bit. The app owns the port exclusively between
|
||||||
|
// jlSerialOpen and jlSerialClose, and MUST close it before it exits: until
|
||||||
|
// then a system interrupt vector points into the program.
|
||||||
|
//
|
||||||
|
// The one thing an interrupt cannot beat is code that masks interrupts, and a
|
||||||
|
// byte that arrives then waits for the CLI. While a port is open this add-on
|
||||||
|
// holds the HAL's tight interrupt-latency bound (iigsIrqLatencyTighten), which
|
||||||
|
// cuts the present's chunked SEI (see iigsBlitStageToShr), the stage fast
|
||||||
|
// clear, the joystick read and the NinjaTrackerPlus effect calls into holds
|
||||||
|
// of at most ~1.7 ms. GS/OS disk I/O and jlAudioPlayMod's instrument upload
|
||||||
|
// are not JoeyLib's to cut.
|
||||||
//
|
//
|
||||||
// SCC access uses the register-pointer protocol: to reach WRn/RRn, first write
|
// SCC access uses the register-pointer protocol: to reach WRn/RRn, first write
|
||||||
// n to the channel's command register, then write (WRn) or read (RRn) it; RR0
|
// n to the channel's command register, then write (WRn) or read (RRn) it; RR0
|
||||||
// (status) and WR0 need no pointer step. The two writes must stay adjacent
|
// (status) and WR0 need no pointer step. The pointer is ONE register for the
|
||||||
// (the pointer is not re-entrant), which they do here -- no serial ISR runs.
|
// whole chip, and the ROM's interrupt entry moves it on every interrupt (it
|
||||||
|
// reads RR3 to find AppleTalk's and the serial vector's work), so a pointer
|
||||||
|
// write and its access must never be split by an interrupt: sccWrite masks
|
||||||
|
// them, and the handlers run masked anyway.
|
||||||
|
//
|
||||||
|
// A Super Serial Card only raises its interrupt with its SW2-6 switch ON (MAME
|
||||||
|
// models the switch, default off, like the card). With it off, nothing reaches
|
||||||
|
// the handler and the port still works, drained by jlSerialPoll and
|
||||||
|
// jlSerialWrite -- but loses whatever arrives while the program is elsewhere.
|
||||||
//
|
//
|
||||||
// CAVEAT: the SSC 6551 window offset (+8 within the slot's 16-byte I/O space,
|
// CAVEAT: the SSC 6551 window offset (+8 within the slot's 16-byte I/O space,
|
||||||
// the classic Apple Super Serial Card decode) and the slot's Control-Panel
|
// the classic Apple Super Serial Card decode) and the slot's Control-Panel
|
||||||
// "Your Card" setting should be confirmed against the target hardware/emulator
|
// "Your Card" setting should be confirmed against the target hardware/emulator
|
||||||
// before relying on the SSC path.
|
// before relying on the SSC path.
|
||||||
|
|
||||||
|
#include <iigs/toolbox.h>
|
||||||
|
|
||||||
#include "joey/serial.h"
|
#include "joey/serial.h"
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
||||||
|
|
@ -38,8 +62,7 @@
|
||||||
|
|
||||||
#define SCC_B_CTRL ((volatile uint8_t *)0x00C038L) // modem port command/control (ch B)
|
#define SCC_B_CTRL ((volatile uint8_t *)0x00C038L) // modem port command/control (ch B)
|
||||||
#define SCC_A_CTRL ((volatile uint8_t *)0x00C039L) // printer port command/control (ch A)
|
#define SCC_A_CTRL ((volatile uint8_t *)0x00C039L) // printer port command/control (ch A)
|
||||||
#define SCC_B_DATA ((volatile uint8_t *)0x00C03AL) // modem port data (ch B)
|
#define SCC_DATA_OFFSET 2 // a channel's data register sits 2 above its command register
|
||||||
#define SCC_A_DATA ((volatile uint8_t *)0x00C03BL) // printer port data (ch A)
|
|
||||||
|
|
||||||
// WR9 bits 7-6 are the reset command: 00 none, 01 channel B, 10 channel A,
|
// WR9 bits 7-6 are the reset command: 00 none, 01 channel B, 10 channel A,
|
||||||
// 11 FORCE HARDWARE RESET. Only ever issue the one for the channel being
|
// 11 FORCE HARDWARE RESET. Only ever issue the one for the channel being
|
||||||
|
|
@ -48,130 +71,170 @@
|
||||||
#define SCC_WR9_RESET_A 0x80u
|
#define SCC_WR9_RESET_A 0x80u
|
||||||
#define SCC_WR9_RESET_B 0x40u
|
#define SCC_WR9_RESET_B 0x40u
|
||||||
|
|
||||||
// RR0 (status) bits, read directly from the command register.
|
// The rest of WR9, written along with the reset command: master interrupt
|
||||||
#define SCC_RR0_RX_AVAIL 0x01
|
// enable + no vector. WR9 is chip-wide and write-only, so this rewrites the
|
||||||
#define SCC_RR0_TX_EMPTY 0x04
|
// interrupt-control bits for BOTH channels -- with the value every IIgs driver
|
||||||
|
// of the chip uses: the ROM serial firmware's EnableInt writes $0A, AppleTalk
|
||||||
|
// resets its channel with $8A (Bank FF serialdrvr / Bank FD lap_includes). So
|
||||||
|
// AppleTalk's interrupts on the other channel stay on, and ours can happen.
|
||||||
|
#define SCC_WR9_MIE_NV 0x0Au
|
||||||
|
|
||||||
// RR1 bit 5: RX overrun - the 3-byte hardware FIFO was full when another byte finished, and that byte
|
// WR1 receive-interrupt mode: interrupt on every character (and on a special
|
||||||
// is GONE. Latched until an Error Reset (WR0 command 0x30), so it survives to the next pump pass.
|
// receive condition). 0 = this channel never interrupts.
|
||||||
#define SCC_RR1_RX_OVRN 0x20
|
#define SCC_WR1_RX_INT_ALL 0x10u
|
||||||
#define SCC_WR0_ERR_RESET 0x30
|
#define SCC_WR1_NO_INTS 0x00u
|
||||||
|
|
||||||
// 6551 status bits.
|
// RR3 interrupt-pending bits (ext/status, Tx, Rx) of each channel. The ROM's
|
||||||
#define ACIA_ST_RX_FULL 0x08
|
// interrupt entry only calls the serial vector for bits that are also in
|
||||||
#define ACIA_ST_TX_EMPTY 0x10
|
// SERFLAG, and dismisses any others untouched -- so a channel's bits must be
|
||||||
#define ACIA_ST_RX_OVRN 0x04
|
// there before it may interrupt, or its level-triggered IRQ would re-enter the
|
||||||
|
// ROM forever. The ROM firmware's EnableInt claims all three the same way
|
||||||
|
// (MAME also reports a special receive condition on the ext/status bit).
|
||||||
|
#define SCC_RR3_CHAN_A 0x38u
|
||||||
|
#define SCC_RR3_CHAN_B 0x07u
|
||||||
|
|
||||||
|
// RR0 transmit buffer empty.
|
||||||
|
#define SCC_RR0_TX_EMPTY 0x04u
|
||||||
|
|
||||||
|
// WR5 control-line and transmitter bits (the Tx bits-per-char field, bits 6-5, comes from the config).
|
||||||
|
#define SCC_WR5_DTR 0x80u
|
||||||
|
#define SCC_WR5_TX_EN 0x08u
|
||||||
|
#define SCC_WR5_RTS 0x02u
|
||||||
|
|
||||||
// SCC baud time constant: BRG output = PCLK(3.6864MHz) / (2*(TC+2)) with the
|
// SCC baud time constant: BRG output = PCLK(3.6864MHz) / (2*(TC+2)) with the
|
||||||
// /16 clock mode, so baud = 115200 / (TC+2) -> TC = 115200/baud - 2.
|
// /16 clock mode, so baud = 115200 / (TC+2) -> TC = 115200/baud - 2.
|
||||||
#define SCC_TC_NUM 115200L
|
#define SCC_TC_NUM 115200L
|
||||||
|
|
||||||
// SSC 6551 lives at the high half of slot s's I/O window: $C080 + s*16 + 8.
|
// ----- 6551 (Super Serial Card) -----
|
||||||
#define SSC_BASE(_slot) ((volatile uint8_t *)(0x00C088L + (long)(_slot) * 16L))
|
|
||||||
|
|
||||||
// Bank-0 RX ring (power of two).
|
// SSC 6551 lives at the high half of slot s's I/O window: $C080 + s*16 + 8
|
||||||
|
// (data +0, status +1, command +2, control +3).
|
||||||
|
#define SSC_BASE(_slot) ((volatile uint8_t *)(0x00C088L + (long)(_slot) * 16L))
|
||||||
|
#define ACIA_STATUS 1
|
||||||
|
#define ACIA_COMMAND 2
|
||||||
|
#define ACIA_CONTROL 3
|
||||||
|
|
||||||
|
// Command register: bit 0 DTR -- which on this chip also enables the receiver and its interrupt, so
|
||||||
|
// dropping DTR stops the card receiving -- bit 1 receiver
|
||||||
|
// interrupt DISABLE, bits 3-2 transmitter control (%10 = RTS asserted, transmit
|
||||||
|
// interrupt off; %00 = RTS dropped and transmitter off), bits 7-5 parity.
|
||||||
|
#define ACIA_CMD_DTR 0x01u
|
||||||
|
#define ACIA_CMD_RX_IRQ_OFF 0x02u
|
||||||
|
#define ACIA_CMD_TX_MASK 0x0Cu
|
||||||
|
#define ACIA_CMD_RTS_ON 0x08u
|
||||||
|
#define ACIA_CMD_PARITY_ODD 0x20u
|
||||||
|
#define ACIA_CMD_PARITY_EVEN 0x60u
|
||||||
|
|
||||||
|
#define ACIA_ST_TX_EMPTY 0x10u
|
||||||
|
|
||||||
|
// ----- Interrupt plumbing -----
|
||||||
|
|
||||||
|
// Misc Tool Set vector reference numbers (GetVector/SetVector): the ROM
|
||||||
|
// interrupt manager's hooks for SCC interrupts AppleTalk refused ($E1/0024)
|
||||||
|
// and for interrupts no built-in source claimed, i.e. slot cards ($E1/005C).
|
||||||
|
#define IIGS_VECTOR_SERIAL 0x0009u
|
||||||
|
#define IIGS_VECTOR_OTHER 0x0017u
|
||||||
|
|
||||||
|
// SERFLAG: the RR3 bits the ROM's interrupt entry hands to the serial vector
|
||||||
|
// (Bank FF monitor_bram.intr071, INTHNDLR: `AND SERFLAG / JSL IRQ_SERIAL`).
|
||||||
|
#define IIGS_SERFLAG ((volatile uint8_t *)0xE10104L)
|
||||||
|
|
||||||
|
// RX ring (power of two).
|
||||||
#define RX_RING_SIZE 512u
|
#define RX_RING_SIZE 512u
|
||||||
#define RX_RING_MASK (RX_RING_SIZE - 1u)
|
#define RX_RING_MASK (RX_RING_SIZE - 1u)
|
||||||
|
|
||||||
|
// What is open, for the handlers. serialIrq.s spells the same values as
|
||||||
|
// SER_KIND_SCC / SER_KIND_ACIA.
|
||||||
|
typedef enum {
|
||||||
|
IIGS_SER_CLOSED = 0,
|
||||||
|
IIGS_SER_SCC = 1,
|
||||||
|
IIGS_SER_ACIA = 2
|
||||||
|
} IigsSerKindE;
|
||||||
|
|
||||||
// ----- Module state -----
|
|
||||||
|
|
||||||
static uint8_t gRxRing[RX_RING_SIZE];
|
// ----- State shared with serialIrq.s (read there with long addressing) -----
|
||||||
|
|
||||||
// Flow counters for the RetroNet RXSTAT diagnostic (read via extern): bytes moved SCC->ring and
|
// The receive ring. One producer -- the interrupt handler, or the same drain
|
||||||
|
// run by the mainline with interrupts masked -- stores the byte and then
|
||||||
|
// publishes gIigsRxTail with a single 16-bit store; the mainline consumer only
|
||||||
|
// ever moves gIigsRxHead.
|
||||||
|
uint8_t gIigsRxRing[RX_RING_SIZE];
|
||||||
|
const uint16_t gIigsRxRingMask = RX_RING_MASK;
|
||||||
|
volatile uint16_t gIigsRxHead = 0u;
|
||||||
|
volatile uint16_t gIigsRxTail = 0u;
|
||||||
|
|
||||||
|
uint16_t gIigsSerKind = IIGS_SER_CLOSED; // IigsSerKindE of the open port
|
||||||
|
uint16_t gIigsSerPort = 0u; // SCC command register / 6551 data register (I/O address)
|
||||||
|
uint8_t gIigsSerIpMask = 0u; // the open SCC channel's RR3 bits; 0 for the 6551
|
||||||
|
uint32_t gIigsSerChain = 0ul; // the vector's previous owner, minus one (the handlers RTL to it)
|
||||||
|
|
||||||
|
// Flow counters for the RetroNet RXSTAT diagnostic (read via extern): bytes moved chip->ring and
|
||||||
// ring->caller since open. Cheap enough to keep unconditionally.
|
// ring->caller since open. Cheap enough to keep unconditionally.
|
||||||
uint16_t gIigsRxPumped = 0u;
|
uint16_t gIigsRxPumped = 0u;
|
||||||
uint16_t gIigsRxRead = 0u;
|
uint16_t gIigsRxRead = 0u;
|
||||||
|
|
||||||
// Receive-loss counters, same read-via-extern contract. Overruns = loss EVENTS the hardware latched
|
// Receive-loss counters, same read-via-extern contract. Overruns = loss EVENTS the hardware latched
|
||||||
// (SCC RR1 overrun / 6551 status bit 2) - one latch observation may stand for SEVERAL destroyed bytes,
|
// (SCC RR1 overrun, checked after every byte / 6551 status bit 2) - one latch observation may stand for
|
||||||
// so this is a loss indicator, not a byte count. Before it existed a pump-discipline gap was invisible
|
// SEVERAL destroyed bytes, so this is a loss indicator, not a byte count; MAME's z80scc never surfaces
|
||||||
// except as a frame CRC drop and a retransmit stall. Dropped = bytes the SOFT ring refused because it
|
// the SCC flag, so there it stays 0. Dropped = bytes the SOFT ring refused because it was full (read
|
||||||
// was full (read from the chip, then discarded); saturating, like the overrun count.
|
// from the chip, then discarded). Both saturate.
|
||||||
uint16_t gIigsRxOverruns = 0u;
|
uint16_t gIigsRxOverruns = 0u;
|
||||||
uint16_t gIigsRxDropped = 0u;
|
uint16_t gIigsRxDropped = 0u;
|
||||||
static uint16_t gRxHead;
|
|
||||||
static uint16_t gRxTail;
|
|
||||||
static bool gOpen;
|
// ----- Module state -----
|
||||||
static bool gIsScc; // true: 8530 SCC; false: 6551 ACIA
|
|
||||||
static volatile uint8_t *gStatus; // SCC command reg (RR0) / 6551 status reg
|
static volatile uint8_t *gReg; // SCC command register / 6551 command register
|
||||||
static uint8_t gWr5; // the SCC's live WR5 (Tx bits + DTR + RTS), so jlpSerialSetRts
|
static volatile uint8_t *gData; // data register (both)
|
||||||
|
static uint8_t gTxReadyBit; // transmit-empty bit of the status iigsSerialService returns
|
||||||
|
static uint8_t gWr5; // the SCC's live WR5 (Tx bits + DTR + RTS), so serialSetLine
|
||||||
// can flip ONE bit without guessing the rest back: WR5 is
|
// can flip ONE bit without guessing the rest back: WR5 is
|
||||||
// write-only on this chip, and rebuilding it from scratch would
|
// write-only on this chip, and rebuilding it from scratch would
|
||||||
// silently drop the data-bit and Tx-enable settings
|
// silently drop the data-bit and Tx-enable settings
|
||||||
static volatile uint8_t *gData; // data register (both)
|
static uint8_t gAciaCmd; // the 6551's command register, same reason
|
||||||
|
static uint16_t gVector; // the Misc Tool Set vector the handler sits on
|
||||||
|
static uint32_t gVectorOld; // ...and what it held before, restored at close
|
||||||
|
static uint8_t gSerflagOld; // our channel's SERFLAG bits before open
|
||||||
|
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
extern uint8_t jlpIoRead8(const volatile uint8_t *p);
|
extern uint16_t iigsIrqOff(void);
|
||||||
static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg);
|
extern void iigsIrqRestore(uint16_t p);
|
||||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, uint8_t reset, const jlSerialConfigT *cfg);
|
extern uint32_t iigsSerialIsrAddress(uint16_t kind);
|
||||||
static bool serialTxReady(void);
|
extern uint16_t iigsSerialService(void);
|
||||||
static uint8_t sccRead(volatile uint8_t *ctrl, uint8_t reg);
|
static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val);
|
||||||
static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val);
|
static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg);
|
||||||
|
static void serialInitScc(volatile uint8_t *ctrl, uint8_t reset, const jlSerialConfigT *cfg);
|
||||||
|
static void serialIrqInstall(uint16_t kind, volatile uint8_t *port, uint8_t rr3Bits, uint16_t vector);
|
||||||
// ----- Guaranteed 8-bit I/O read -----
|
static void serialIrqRemove(void);
|
||||||
//
|
static void serialOpenScc(volatile uint8_t *ctrl, uint8_t reset, uint8_t rr3Bits, const jlSerialConfigT *cfg);
|
||||||
// llvm816 widens a volatile uint8_t LOAD through a variable long pointer to a 16-bit `lda [dp],y`
|
static void serialSetLine(uint8_t sccBit, uint8_t aciaMask, uint8_t aciaOn, bool on);
|
||||||
// (m=0), so every such register read also performs a phantom bus read of address+1 - and the SCC's
|
|
||||||
// four registers are adjacent, so a data read on one channel phantom-pops the OTHER channel's data
|
|
||||||
// FIFO ($C03A reads $C03B: channel A, the AppleTalk port). Constant-address loads are sep-wrapped
|
|
||||||
// correctly; only the pointer form is affected, and STORES are always sep-wrapped. Filed in
|
|
||||||
// llvm816/LLVM816-ASKS.md; until the toolchain honours volatile width, every serial register READ
|
|
||||||
// goes through this hand-asm helper - a global function so no call site can be inlined back into
|
|
||||||
// the widened form. ABI from a compiled probe: pointer lo16 in A, bank in X; return in A (high
|
|
||||||
// byte cleared), X zeroed; $e0-$e3 are call-clobbered imaginary-register scratch, D points at the
|
|
||||||
// imaginary page on entry (the same assumption every compiled function makes).
|
|
||||||
__asm__(
|
|
||||||
".section .text.jlpIoRead8,\"ax\",@progbits\n"
|
|
||||||
".globl jlpIoRead8\n"
|
|
||||||
"jlpIoRead8:\n"
|
|
||||||
"\trep #$30\n" // known width state: 16-bit A and index
|
|
||||||
"\tsta $e0\n" // pointer low 16 -> $e0-$e1
|
|
||||||
"\ttxa\n"
|
|
||||||
"\tsta $e2\n" // bank -> $e2 ($e3 catches the junk high byte - scratch)
|
|
||||||
"\tldy #0\n"
|
|
||||||
"\tsep #$20\n" // 8-bit accumulator: the actual fix
|
|
||||||
"\tlda [$e0],y\n" // ONE byte on the bus - no phantom companion read
|
|
||||||
"\trep #$30\n"
|
|
||||||
"\tand #$ff\n" // the hidden B accumulator held junk; clear it for the return
|
|
||||||
"\tldx #0\n"
|
|
||||||
"\trtl\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// ----- SCC helpers -----
|
// ----- SCC helpers -----
|
||||||
|
|
||||||
// Register-pointer protocol: point at WRn (if not WR0), then write the value.
|
// Register-pointer protocol: point at WRn (if not WR0), then write the value -- with interrupts
|
||||||
|
// masked, because the ROM's interrupt entry moves the chip-wide pointer (see the file header).
|
||||||
static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val) {
|
static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val) {
|
||||||
|
uint16_t p;
|
||||||
|
|
||||||
|
p = iigsIrqOff();
|
||||||
if (reg != 0u) {
|
if (reg != 0u) {
|
||||||
*ctrl = reg;
|
*ctrl = reg;
|
||||||
}
|
}
|
||||||
*ctrl = val;
|
*ctrl = val;
|
||||||
|
iigsIrqRestore(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Point at RRn, then read it. Same adjacency rule as sccWrite: nothing may touch the SCC between the
|
// Program a channel and start it, with its interrupts still off.
|
||||||
// pointer write and the read (safe here - WR1=0 means this channel never interrupts, and the HAL is
|
static void serialInitScc(volatile uint8_t *ctrl, uint8_t reset, const jlSerialConfigT *cfg) {
|
||||||
// single-threaded polled).
|
|
||||||
static uint8_t sccRead(volatile uint8_t *ctrl, uint8_t reg) {
|
|
||||||
if (reg != 0u) {
|
|
||||||
*ctrl = reg;
|
|
||||||
}
|
|
||||||
return jlpIoRead8(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, uint8_t reset, const jlSerialConfigT *cfg) {
|
|
||||||
uint8_t wr4;
|
uint8_t wr4;
|
||||||
uint8_t wr3;
|
uint8_t wr3;
|
||||||
uint8_t wr5;
|
uint8_t wr5;
|
||||||
int32_t tc;
|
int32_t tc;
|
||||||
|
|
||||||
(void)data;
|
|
||||||
|
|
||||||
// WR4: /16 clock, stop bits, parity.
|
// WR4: /16 clock, stop bits, parity.
|
||||||
wr4 = 0x40u; // /16 clock rate
|
wr4 = 0x40u; // /16 clock rate
|
||||||
wr4 |= (cfg->stopBits == 2u) ? 0x0Cu : 0x04u; // 2 or 1 stop bit
|
wr4 |= (cfg->stopBits == 2u) ? 0x0Cu : 0x04u; // 2 or 1 stop bit
|
||||||
|
|
@ -203,36 +266,62 @@ static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, uint8_
|
||||||
// but MAME's z80scc powers up unreset and its TX path stays dead without it (measured: every
|
// but MAME's z80scc powers up unreset and its TX path stays dead without it (measured: every
|
||||||
// register write landed - DTR visibly rose at the far end - yet no data byte ever left the chip).
|
// register write landed - DTR visibly rose at the far end - yet no data byte ever left the chip).
|
||||||
// This used to be WR9 = 0xC0, a force-hardware-reset that took the OTHER channel down with it.
|
// This used to be WR9 = 0xC0, a force-hardware-reset that took the OTHER channel down with it.
|
||||||
//
|
// The same write sets the chip-wide interrupt control (SCC_WR9_MIE_NV); a channel reset leaves
|
||||||
// Not fully solved, and it cannot be from here: WR9 is a single chip-wide register AND is
|
// this channel's WR1 interrupt enables clear, and it stays quiet until serialOpenScc has the
|
||||||
// write-only, so the reset command unavoidably rewrites the chip's interrupt-control bits
|
// handler in place.
|
||||||
// (MIE/VIS/NV) with our zeroes, and there is no way to read the previous value back to preserve
|
sccWrite(ctrl, 9u, (uint8_t)(reset | SCC_WR9_MIE_NV));
|
||||||
// them. A driver on the other channel that takes interrupts -- AppleTalk -- is therefore still
|
sccWrite(ctrl, 1u, SCC_WR1_NO_INTS); // WR1 is per-channel, WR9 is not
|
||||||
// disturbed, just no longer wiped. Sharing the SCC properly would mean going through GS/OS's
|
|
||||||
// serial drivers, which this HAL deliberately bypasses (see the file header).
|
|
||||||
sccWrite(ctrl, 9u, reset);
|
|
||||||
sccWrite(ctrl, 1u, 0x00u); // no interrupts from THIS channel -- WR1 is per-channel, WR9 is not
|
|
||||||
sccWrite(ctrl, 10u, 0x00u); // NRZ, no loop/mark-idle: a CHANNEL reset preserves the
|
sccWrite(ctrl, 10u, 0x00u); // NRZ, no loop/mark-idle: a CHANNEL reset preserves the
|
||||||
// encoding bits the old chip-wide reset cleared, so a
|
// encoding bits the old chip-wide reset cleared, so a
|
||||||
// channel left in FM0 by LocalTalk would stay there.
|
// channel left in FM0 by LocalTalk would stay there.
|
||||||
sccWrite(ctrl, 4u, wr4);
|
sccWrite(ctrl, 4u, wr4);
|
||||||
sccWrite(ctrl, 3u, wr3); // Rx bits, not yet enabled
|
sccWrite(ctrl, 3u, wr3); // Rx bits, not yet enabled
|
||||||
sccWrite(ctrl, 5u, (uint8_t)(wr5 | 0x82u)); // Tx bits + DTR + RTS, not yet enabled
|
sccWrite(ctrl, 5u, (uint8_t)(wr5 | SCC_WR5_DTR | SCC_WR5_RTS)); // Tx bits + DTR + RTS, not yet enabled
|
||||||
sccWrite(ctrl, 11u, 0x50u); // Rx & Tx clock = baud rate generator
|
sccWrite(ctrl, 11u, 0x50u); // Rx & Tx clock = baud rate generator
|
||||||
sccWrite(ctrl, 12u, (uint8_t)(tc & 0xFFu)); // time constant low
|
sccWrite(ctrl, 12u, (uint8_t)(tc & 0xFFu)); // time constant low
|
||||||
sccWrite(ctrl, 13u, (uint8_t)((tc >> 8) & 0xFFu)); // time constant high
|
sccWrite(ctrl, 13u, (uint8_t)((tc >> 8) & 0xFFu)); // time constant high
|
||||||
sccWrite(ctrl, 14u, 0x03u); // BRG enable, source = PCLK
|
sccWrite(ctrl, 14u, 0x03u); // BRG enable, source = PCLK
|
||||||
sccWrite(ctrl, 3u, (uint8_t)(wr3 | 0x01u)); // Rx enable
|
sccWrite(ctrl, 3u, (uint8_t)(wr3 | 0x01u)); // Rx enable
|
||||||
gWr5 = (uint8_t)(wr5 | 0x8Au);
|
gWr5 = (uint8_t)(wr5 | SCC_WR5_DTR | SCC_WR5_TX_EN | SCC_WR5_RTS);
|
||||||
sccWrite(ctrl, 5u, gWr5); // Tx enable + DTR + RTS
|
sccWrite(ctrl, 5u, gWr5); // Tx enable + DTR + RTS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void serialOpenScc(volatile uint8_t *ctrl, uint8_t reset, uint8_t rr3Bits, const jlSerialConfigT *cfg) {
|
||||||
|
serialInitScc(ctrl, reset, cfg);
|
||||||
|
gReg = ctrl;
|
||||||
|
gData = ctrl + SCC_DATA_OFFSET;
|
||||||
|
gTxReadyBit = SCC_RR0_TX_EMPTY;
|
||||||
|
serialIrqInstall(IIGS_SER_SCC, ctrl, rr3Bits, IIGS_VECTOR_SERIAL);
|
||||||
|
sccWrite(ctrl, 1u, SCC_WR1_RX_INT_ALL); // handler in: let the channel interrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Raise or drop one control line of the open port, changing nothing else. SCC: `sccBit` of WR5,
|
||||||
|
// applied to the retained copy (WR5 is write-only, and rebuilding it would silently drop the data-bit
|
||||||
|
// and Tx-enable settings) and written with interrupts masked by sccWrite. 6551: the command
|
||||||
|
// register's `aciaMask` field becomes `aciaOn` when on and 0 when off; parity and the receiver
|
||||||
|
// interrupt setting ride along unchanged.
|
||||||
|
static void serialSetLine(uint8_t sccBit, uint8_t aciaMask, uint8_t aciaOn, bool on) {
|
||||||
|
if (gIigsSerKind == IIGS_SER_SCC) {
|
||||||
|
gWr5 = on ? (uint8_t)(gWr5 | sccBit) : (uint8_t)(gWr5 & (uint8_t)~sccBit);
|
||||||
|
sccWrite(gReg, 5u, gWr5);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gAciaCmd = (uint8_t)(gAciaCmd & (uint8_t)~aciaMask);
|
||||||
|
if (on) {
|
||||||
|
gAciaCmd = (uint8_t)(gAciaCmd | aciaOn);
|
||||||
|
}
|
||||||
|
*gReg = gAciaCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- 6551 (Super Serial Card) helper -----
|
// ----- 6551 (Super Serial Card) helper -----
|
||||||
|
|
||||||
|
// Reset and frame the ACIA and work out its command byte; the command register (which starts the
|
||||||
|
// receiver and its interrupt) is written by jlpSerialOpen once the handler is in place.
|
||||||
static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg) {
|
static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg) {
|
||||||
uint8_t control;
|
uint8_t control;
|
||||||
uint8_t command;
|
|
||||||
uint8_t baudCode;
|
uint8_t baudCode;
|
||||||
|
|
||||||
// 6551 internal baud rate codes (bits 3-0 of the control register).
|
// 6551 internal baud rate codes (bits 3-0 of the control register).
|
||||||
|
|
@ -262,227 +351,172 @@ static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg) {
|
||||||
control |= 0x80u;
|
control |= 0x80u;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command: DTR on (bit0) | rx IRQ disabled (bit1, polled) | RTS low, TX IRQ
|
// Command: DTR on, receiver interrupt ENABLED, RTS asserted with the transmit interrupt off, parity.
|
||||||
// off (bits 3-2 = 10) | parity.
|
gAciaCmd = (uint8_t)(ACIA_CMD_DTR | ACIA_CMD_RTS_ON);
|
||||||
command = 0x0Bu;
|
|
||||||
if (cfg->parity == JL_SERIAL_PARITY_ODD) {
|
if (cfg->parity == JL_SERIAL_PARITY_ODD) {
|
||||||
command |= 0x20u; // parity enable, odd
|
gAciaCmd |= ACIA_CMD_PARITY_ODD;
|
||||||
} else if (cfg->parity == JL_SERIAL_PARITY_EVEN) {
|
} else if (cfg->parity == JL_SERIAL_PARITY_EVEN) {
|
||||||
command |= 0x60u; // parity enable, even
|
gAciaCmd |= ACIA_CMD_PARITY_EVEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
base[1] = 0x00u; // write to status register = software reset
|
base[ACIA_STATUS] = 0x00u; // write to status register = programmed reset (DTR off: no interrupts)
|
||||||
base[3] = control; // control register
|
base[ACIA_CONTROL] = control;
|
||||||
base[2] = command; // command register
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- Status polling -----
|
// ----- Interrupt install / remove -----
|
||||||
|
|
||||||
static bool serialTxReady(void) {
|
// Point `vector` at the handler for `kind` and publish the port to it. For the SCC, rr3Bits go into
|
||||||
if (gIsScc) {
|
// SERFLAG so the ROM routes the channel's interrupts to the vector at all. Masked throughout: the
|
||||||
return (jlpIoRead8(gStatus) & SCC_RR0_TX_EMPTY) != 0u;
|
// vector, SERFLAG and the handler's view of the port change together.
|
||||||
}
|
static void serialIrqInstall(uint16_t kind, volatile uint8_t *port, uint8_t rr3Bits, uint16_t vector) {
|
||||||
return (jlpIoRead8(gStatus) & ACIA_ST_TX_EMPTY) != 0u;
|
uint16_t p;
|
||||||
|
|
||||||
|
p = iigsIrqOff();
|
||||||
|
gVector = vector;
|
||||||
|
gVectorOld = (uint32_t)GetVector(vector);
|
||||||
|
gIigsSerChain = gVectorOld - 1ul;
|
||||||
|
gIigsSerPort = (uint16_t)(uint32_t)port;
|
||||||
|
gIigsSerIpMask = rr3Bits;
|
||||||
|
gSerflagOld = (uint8_t)(*IIGS_SERFLAG & rr3Bits);
|
||||||
|
*IIGS_SERFLAG = (uint8_t)(*IIGS_SERFLAG | rr3Bits);
|
||||||
|
gIigsSerKind = kind;
|
||||||
|
SetVector(vector, (void *)iigsSerialIsrAddress(kind));
|
||||||
|
iigsIrqRestore(p);
|
||||||
|
iigsIrqLatencyTighten();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Undo serialIrqInstall. The caller has already stopped the chip interrupting and retired any
|
||||||
|
// request it raised before that.
|
||||||
|
static void serialIrqRemove(void) {
|
||||||
|
uint16_t p;
|
||||||
|
|
||||||
|
p = iigsIrqOff();
|
||||||
|
gIigsSerKind = IIGS_SER_CLOSED;
|
||||||
|
*IIGS_SERFLAG = (uint8_t)((*IIGS_SERFLAG & (uint8_t)~gIigsSerIpMask) | gSerflagOld);
|
||||||
|
gIigsSerIpMask = 0u;
|
||||||
|
SetVector(gVector, (void *)gVectorOld);
|
||||||
|
iigsIrqRestore(p);
|
||||||
|
iigsIrqLatencyRelax();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- HAL entry points (alphabetical) -----
|
// ----- HAL entry points (alphabetical) -----
|
||||||
|
|
||||||
uint16_t jlpSerialAvailable(void) {
|
uint16_t jlpSerialAvailable(void) {
|
||||||
return (uint16_t)((gRxTail - gRxHead) & RX_RING_MASK);
|
return (uint16_t)((gIigsRxTail - gIigsRxHead) & RX_RING_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The core calls this whether or not a port is open.
|
||||||
void jlpSerialClose(void) {
|
void jlpSerialClose(void) {
|
||||||
gOpen = false;
|
uint16_t p;
|
||||||
// Leave the chip configured; a polled port needs no teardown, and the app
|
|
||||||
// owns it until re-opened.
|
if (gIigsSerKind == IIGS_SER_CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = iigsIrqOff();
|
||||||
|
if (gIigsSerKind == IIGS_SER_SCC) {
|
||||||
|
sccWrite(gReg, 1u, SCC_WR1_NO_INTS);
|
||||||
|
} else {
|
||||||
|
gAciaCmd = (uint8_t)(gAciaCmd | ACIA_CMD_RX_IRQ_OFF);
|
||||||
|
*gReg = gAciaCmd;
|
||||||
|
}
|
||||||
|
// A request raised before the line went quiet is retired by taking its byte; left pending, a
|
||||||
|
// level-triggered IRQ whose bits are no longer claimed would re-enter the ROM forever.
|
||||||
|
(void)iigsSerialService();
|
||||||
|
serialIrqRemove();
|
||||||
|
iigsIrqRestore(p);
|
||||||
|
// Otherwise the chip stays configured (the transmitter keeps running) until the next open.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void jlpSerialFlush(void) {
|
void jlpSerialFlush(void) {
|
||||||
gRxHead = gRxTail;
|
gIigsRxHead = gIigsRxTail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
if (gOpen) {
|
volatile uint8_t *base;
|
||||||
return false;
|
|
||||||
}
|
gIigsRxHead = 0u;
|
||||||
gRxHead = 0u;
|
gIigsRxTail = 0u;
|
||||||
gRxTail = 0u;
|
|
||||||
|
|
||||||
switch (device) {
|
switch (device) {
|
||||||
case JL_SERIAL_PRINTER:
|
case JL_SERIAL_PRINTER:
|
||||||
gIsScc = true;
|
serialOpenScc(SCC_A_CTRL, SCC_WR9_RESET_A, SCC_RR3_CHAN_A, config);
|
||||||
gStatus = SCC_A_CTRL;
|
|
||||||
gData = SCC_A_DATA;
|
|
||||||
serialInitScc(SCC_A_CTRL, SCC_A_DATA, SCC_WR9_RESET_A, config);
|
|
||||||
break;
|
break;
|
||||||
case JL_SERIAL_SLOT: {
|
case JL_SERIAL_SLOT:
|
||||||
volatile uint8_t *base;
|
|
||||||
|
|
||||||
if (config->unit < 1u || config->unit > 7u) {
|
if (config->unit < 1u || config->unit > 7u) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
base = SSC_BASE(config->unit);
|
base = SSC_BASE(config->unit);
|
||||||
gIsScc = false;
|
gReg = &base[ACIA_COMMAND];
|
||||||
gStatus = &base[1];
|
gData = base;
|
||||||
gData = &base[0];
|
gTxReadyBit = ACIA_ST_TX_EMPTY;
|
||||||
serialInitAcia(base, config);
|
serialInitAcia(base, config);
|
||||||
|
serialIrqInstall(IIGS_SER_ACIA, base, 0u, IIGS_VECTOR_OTHER);
|
||||||
|
*gReg = gAciaCmd; // handler in: start the receiver and its interrupt
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case JL_SERIAL_MODEM:
|
case JL_SERIAL_MODEM:
|
||||||
case JL_SERIAL_DEFAULT:
|
case JL_SERIAL_DEFAULT:
|
||||||
default:
|
default:
|
||||||
gIsScc = true;
|
serialOpenScc(SCC_B_CTRL, SCC_WR9_RESET_B, SCC_RR3_CHAN_B, config);
|
||||||
gStatus = SCC_B_CTRL;
|
|
||||||
gData = SCC_B_DATA;
|
|
||||||
serialInitScc(SCC_B_CTRL, SCC_B_DATA, SCC_WR9_RESET_B, config);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
gOpen = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Drain the SCC hardware FIFO into the soft ring from ANY wait/blit/input site (the X68000 lesson:
|
// The interrupt keeps the ring filled; this only catches what it could not -- a Super Serial Card
|
||||||
// a polled port with a tiny hardware FIFO loses mid-frame bytes during every deaf window - the Z8530
|
// whose interrupt switch is off.
|
||||||
// RX FIFO is 3 bytes, ~3ms at 9600, and the client's VBL-paced loop is deaf for ~16ms per pass).
|
|
||||||
// Safe to call at any time; no-ops when the port is closed.
|
|
||||||
void iigsSerialPump(void) {
|
|
||||||
jlpSerialPoll();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// RTS on demand (jlSerialSetRts). SCC: WR5 bit 1, applied to the retained copy so the Tx enable, DTR and
|
|
||||||
// data-bit fields survive. 6551: the command register's bits 3:2 - %10 asserts RTS with the receiver
|
|
||||||
// interrupt disabled (this HAL polls), %00 deasserts it AND gates the transmitter off, which is why the
|
|
||||||
// deassert is only ever a pause.
|
|
||||||
void jlpSerialSetRts(bool on) {
|
|
||||||
if (!gOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gIsScc) {
|
|
||||||
gWr5 = on ? (uint8_t)(gWr5 | 0x02u) : (uint8_t)(gWr5 & (uint8_t)~0x02u);
|
|
||||||
sccWrite(gStatus, 5u, gWr5);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gStatus[1] = on ? 0x09u : 0x01u; // 6551 command: DTR on, RTS asserted / deasserted
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void jlpSerialPoll(void) {
|
void jlpSerialPoll(void) {
|
||||||
uint16_t tail;
|
(void)iigsSerialService();
|
||||||
uint16_t guard;
|
|
||||||
|
|
||||||
if (!gOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Drain the hardware into the ring under a bound (like input.c's FIFO
|
|
||||||
// drain). Local read-modify-write of the tail index dodges the `inc abs`
|
|
||||||
// DBR trap that file-scope `++` lowers to on the 65816.
|
|
||||||
tail = gRxTail;
|
|
||||||
guard = RX_RING_SIZE;
|
|
||||||
// Counter updates below use the guarded local read-modify-write from input.c's joystick counter -
|
|
||||||
// NOT because of the spelling (clang canonicalizes `x++` and `x = x + 1` identically) but because
|
|
||||||
// the saturation compare keeps the loaded value LIVE, which is what stops the backend fusing the
|
|
||||||
// update into a DBR-relative `inc abs`. Saturating is also the right behaviour for a loss counter.
|
|
||||||
while (guard != 0u) {
|
|
||||||
uint16_t next;
|
|
||||||
uint8_t st;
|
|
||||||
uint8_t b;
|
|
||||||
|
|
||||||
st = jlpIoRead8(gStatus); // RR0 (SCC) or 6551 status
|
|
||||||
if (gIsScc) {
|
|
||||||
if ((st & SCC_RR0_RX_AVAIL) == 0u) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The 6551 latches overrun ONLY while RDRF is still set, and the DATA read below retires
|
|
||||||
// both bits together - so the overrun must be sampled HERE, from the same status read that
|
|
||||||
// gates the byte. (A post-loop check can never see it: the drain already cleared it.)
|
|
||||||
if ((st & ACIA_ST_RX_OVRN) != 0u) {
|
|
||||||
uint16_t ovr = gIigsRxOverruns;
|
|
||||||
|
|
||||||
if (ovr < 0xFFFFu) {
|
|
||||||
gIigsRxOverruns = (uint16_t)(ovr + 1u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((st & ACIA_ST_RX_FULL) == 0u) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next = (uint16_t)((tail + 1u) & RX_RING_MASK);
|
|
||||||
b = jlpIoRead8(gData);
|
|
||||||
if (next != gRxHead) {
|
|
||||||
gRxRing[tail] = b;
|
|
||||||
tail = next;
|
|
||||||
gIigsRxPumped++;
|
|
||||||
} else {
|
|
||||||
uint16_t drop = gIigsRxDropped;
|
|
||||||
|
|
||||||
if (drop < 0xFFFFu) {
|
|
||||||
gIigsRxDropped = (uint16_t)(drop + 1u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard--;
|
|
||||||
}
|
|
||||||
gRxTail = tail;
|
|
||||||
// Harvest the SCC's own loss record: real silicon latches RX overrun in RR1 until an Error Reset,
|
|
||||||
// so one check per pump names a deaf-window loss even though the pump never saw the byte.
|
|
||||||
//
|
|
||||||
// DEFAULT OFF - REAL HARDWARE ONLY, on two measured grounds: (1) under MAME the counter is INERT
|
|
||||||
// (its z80scc never surfaces the flag into RR1), so enabling it under emulation buys nothing; and
|
|
||||||
// (2) with it enabled the MAME 0.264 session goes RX-deaf after HELLO - 3 of 4 runs, INCLUDING one
|
|
||||||
// through jlpIoRead8's true 8-bit reads, so the phantom companion read is NOT the mechanism (one
|
|
||||||
// green run said otherwise and the next red run retracted it - the two-greens-prove-nothing rule).
|
|
||||||
// Whatever the per-pass WR0-pointer/RR1 dance disturbs in MAME's SCC remains unpinned; it is not
|
|
||||||
// worth more rig time for a diagnostic that cannot fire there. JL_IIGS_RR1_HARVEST enables it
|
|
||||||
// for silicon validation.
|
|
||||||
#ifdef JL_IIGS_RR1_HARVEST
|
|
||||||
if (gIsScc) {
|
|
||||||
if ((sccRead(gStatus, 1u) & SCC_RR1_RX_OVRN) != 0u) {
|
|
||||||
uint16_t ovr = gIigsRxOverruns;
|
|
||||||
|
|
||||||
if (ovr < 0xFFFFu) {
|
|
||||||
gIigsRxOverruns = (uint16_t)(ovr + 1u);
|
|
||||||
}
|
|
||||||
sccWrite(gStatus, 0u, SCC_WR0_ERR_RESET);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
uint16_t head;
|
uint16_t head;
|
||||||
|
uint16_t tail;
|
||||||
|
|
||||||
head = gRxHead;
|
head = gIigsRxHead;
|
||||||
for (n = 0u; n < max && head != gRxTail; n++) {
|
tail = gIigsRxTail; // one snapshot: the handler may append while we copy
|
||||||
buf[n] = gRxRing[head];
|
for (n = 0u; n < max && head != tail; n++) {
|
||||||
|
buf[n] = gIigsRxRing[head];
|
||||||
head = (uint16_t)((head + 1u) & RX_RING_MASK);
|
head = (uint16_t)((head + 1u) & RX_RING_MASK);
|
||||||
}
|
}
|
||||||
gRxHead = head;
|
gIigsRxHead = head;
|
||||||
gIigsRxRead = (uint16_t)(gIigsRxRead + n);
|
gIigsRxRead = (uint16_t)(gIigsRxRead + n);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DTR on demand (jlSerialSetDtr, how a program hangs up). SCC: WR5 bit 7. 6551: command bit 0, which
|
||||||
|
// is also the receiver's enable -- with DTR dropped the card neither receives nor interrupts until DTR
|
||||||
|
// is raised again. Close keeps whatever this left, so a dropped DTR stays dropped.
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
serialSetLine(SCC_WR5_DTR, ACIA_CMD_DTR, ACIA_CMD_DTR, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RTS on demand (jlSerialSetRts). SCC: WR5 bit 1. 6551: the command register's bits 3:2 - %10 asserts
|
||||||
|
// RTS, %00 deasserts it AND gates the transmitter off, which is why the deassert is only ever a pause.
|
||||||
|
void jlpSerialSetRts(bool on) {
|
||||||
|
serialSetLine(SCC_WR5_RTS, ACIA_CMD_TX_MASK, ACIA_CMD_RTS_ON, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
|
|
||||||
for (n = 0u; n < len; n++) {
|
for (n = 0u; n < len; n++) {
|
||||||
uint16_t guard = 0u;
|
uint16_t guard = 0u;
|
||||||
|
|
||||||
while (!serialTxReady()) {
|
// The status read goes through iigsSerialService, which also drains the receiver: a 6551
|
||||||
// Full-duplex: drain the RX FIFO while waiting for TX-ready. Each TX byte at 9600 is a
|
// status read clears its interrupt request, so whoever reads it must take the byte.
|
||||||
// ~1ms wait and the Z8530 RX FIFO is 3 bytes - an unpumped uplink mid-downlink-burst
|
while ((iigsSerialService() & gTxReadyBit) == 0u) {
|
||||||
// measurably ate 4-byte clumps out of arriving frames.
|
|
||||||
jlpSerialPoll();
|
|
||||||
guard++;
|
guard++;
|
||||||
if (guard == 0u) { // wrapped 65536 spins -- link stalled
|
if (guard == 0u) { // wrapped 65536 spins -- link stalled
|
||||||
return n;
|
return n;
|
||||||
|
|
@ -490,6 +524,5 @@ uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
}
|
}
|
||||||
*gData = buf[n];
|
*gData = buf[n];
|
||||||
}
|
}
|
||||||
jlpSerialPoll();
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
364
src/iigs/serialIrq.s
Normal file
364
src/iigs/serialIrq.s
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
; serialIrq.s - interrupt-driven receive for the IIgs serial add-on (serial.c).
|
||||||
|
;
|
||||||
|
; GAS syntax for the llvm-mos w65816 toolchain (conventions: peislam.s,
|
||||||
|
; joeyDraw.s). Everything is ONE section, so the handlers, the shared drain
|
||||||
|
; routines and the C-callable helpers reach each other with 16-bit jsr/rts.
|
||||||
|
;
|
||||||
|
; The ROM's interrupt manager (Bank FF monitor_bram.intr071 INTHNDLR) hands
|
||||||
|
; each source to a Misc Tool Set vector, which serial.c points here with
|
||||||
|
; GetVector/SetVector:
|
||||||
|
;
|
||||||
|
; $0009 serial ($E1/0024) -> iigsSccIsr. Called straight from INTHNDLR's
|
||||||
|
; fast path, for an SCC interrupt AppleTalk refused and whose RR3 bits
|
||||||
|
; are in SERFLAG ($E1/0104), with M=X=8, DBR=$E1, D=0 and
|
||||||
|
; A = RR3 & SERFLAG -- and BEFORE the ROM has switched to its own
|
||||||
|
; stack or normalised STATEREG, so this runs on the interrupted
|
||||||
|
; program's stack with whatever RAMRD/RAMWRT it had set (the ROM's
|
||||||
|
; own serial handler, serialdrvr SERPINT, saves and clears them).
|
||||||
|
; $0017 other ($E1/005C) -> iigsSscIsr. Called last, for an interrupt no
|
||||||
|
; built-in source claimed (a slot card), after the ROM has saved the
|
||||||
|
; machine and normalised it: M=X=8, DBR=0, D=0, main memory.
|
||||||
|
;
|
||||||
|
; Both return carry clear for "serviced" and M=X=8 (the ROM ROR/PLAs a byte
|
||||||
|
; right after the call). Anything that is not ours goes on to the vector's
|
||||||
|
; previous owner by an RTL to gIigsSerChain (its address minus one), with the
|
||||||
|
; registers as the ROM left them.
|
||||||
|
;
|
||||||
|
; Receive ring protocol, single producer / single consumer: the producer
|
||||||
|
; (ringPut, only ever run by an interrupt handler or under SEI) stores the
|
||||||
|
; byte, then publishes gIigsRxTail with one 16-bit store; the mainline only
|
||||||
|
; ever moves gIigsRxHead. C globals are reached with long addressing, so the
|
||||||
|
; data bank does not matter; the I/O is DBR-relative through Y, and every path
|
||||||
|
; sets DBR to a bank where $C0xx is I/O ($E1 here, or $00 in iigsSscIsr where
|
||||||
|
; the ROM has I/O shadowing on).
|
||||||
|
;
|
||||||
|
; Port layout (gIigsSerPort, from serial.c):
|
||||||
|
; SCC Y = channel command register ($C038 B / $C039 A); data at 2,y.
|
||||||
|
; 6551 Y = data register ($C0n8); status at 1,y, command at 2,y.
|
||||||
|
|
||||||
|
.section .text.iigsSerialIrq,"ax"
|
||||||
|
.globl iigsSccIsr
|
||||||
|
.globl iigsSscIsr
|
||||||
|
.globl iigsSerialService
|
||||||
|
.globl iigsSerialIsrAddress
|
||||||
|
.globl iigsIrqOff
|
||||||
|
.globl iigsIrqRestore
|
||||||
|
|
||||||
|
SER_KIND_SCC = 1 ; gIigsSerKind values (IigsSerKindE in serial.c)
|
||||||
|
SER_KIND_ACIA = 2
|
||||||
|
STATEREG = 0xE0C068
|
||||||
|
SERFLAG = 0xE10104
|
||||||
|
SCC_A_CTRL = 0xC039 ; RR3 exists on channel A only
|
||||||
|
SCC_RR0_RX = 0x01
|
||||||
|
SCC_RR1_ERRORS = 0x70 ; framing | overrun | parity
|
||||||
|
SCC_RR1_OVERRUN = 0x20
|
||||||
|
SCC_WR0_ERR_RST = 0x30
|
||||||
|
SCC_WR0_EXT_RST = 0x10
|
||||||
|
ACIA_ST_IRQ = 0x80
|
||||||
|
ACIA_ST_RDRF = 0x08
|
||||||
|
ACIA_ST_OVERRUN = 0x04
|
||||||
|
STATE_MAIN_MEM = 0x8F ; clears PAGE2, RAMRD, RAMWRT (SERPINT's mask)
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; SCC receive interrupt (serial vector).
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
iigsSccIsr:
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
pha ; RR3 & SERFLAG, handed on if we chain
|
||||||
|
lda >STATEREG
|
||||||
|
pha
|
||||||
|
and #STATE_MAIN_MEM
|
||||||
|
sta >STATEREG
|
||||||
|
lda #3
|
||||||
|
sta SCC_A_CTRL ; point at RR3 (DBR = $E1)
|
||||||
|
lda SCC_A_CTRL
|
||||||
|
and >gIigsSerIpMask
|
||||||
|
beq sccIsrPass ; none of ours pending
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
lda >gIigsSerPort
|
||||||
|
tay
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
jsr sccDrain
|
||||||
|
lda #3
|
||||||
|
sta SCC_A_CTRL
|
||||||
|
lda SCC_A_CTRL
|
||||||
|
and >SERFLAG
|
||||||
|
ora >gIigsSerIpMask
|
||||||
|
eor >gIigsSerIpMask ; = pending bits the previous owner asked for
|
||||||
|
bne sccIsrPass
|
||||||
|
pla
|
||||||
|
sta >STATEREG
|
||||||
|
pla
|
||||||
|
sep #0x30
|
||||||
|
.i8
|
||||||
|
clc
|
||||||
|
rtl
|
||||||
|
sccIsrPass:
|
||||||
|
pla
|
||||||
|
sta >STATEREG
|
||||||
|
pla
|
||||||
|
sep #0x30
|
||||||
|
.i8
|
||||||
|
bra chain
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; 6551 receive interrupt (other-interrupt vector).
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
iigsSscIsr:
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
phb
|
||||||
|
pea 0xE1E1
|
||||||
|
plb
|
||||||
|
plb ; DBR = $E1: $C0xx is I/O whatever the shadow state
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
lda >gIigsSerKind
|
||||||
|
cmp #SER_KIND_ACIA
|
||||||
|
bne sscIsrPass
|
||||||
|
lda >gIigsSerPort
|
||||||
|
tay
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
lda 1,y ; status; the read also clears the IRQ bit
|
||||||
|
bpl sscIsrPass ; IRQ bit clear: another card's interrupt
|
||||||
|
jsr sscDrain
|
||||||
|
plb
|
||||||
|
sep #0x30
|
||||||
|
.i8
|
||||||
|
clc
|
||||||
|
rtl
|
||||||
|
sscIsrPass:
|
||||||
|
plb
|
||||||
|
sep #0x30
|
||||||
|
.a8
|
||||||
|
.i8
|
||||||
|
|
||||||
|
; Hand the interrupt to the vector's previous owner: push its address - 1 and
|
||||||
|
; RTL there. A is parked in B, which 8-bit loads leave alone.
|
||||||
|
chain:
|
||||||
|
xba
|
||||||
|
lda >gIigsSerChain+2
|
||||||
|
pha
|
||||||
|
lda >gIigsSerChain+1
|
||||||
|
pha
|
||||||
|
lda >gIigsSerChain
|
||||||
|
pha
|
||||||
|
xba
|
||||||
|
rtl
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; uint16_t iigsSerialService(void)
|
||||||
|
;
|
||||||
|
; The mainline's view of the open port: under SEI, drain whatever the chip
|
||||||
|
; holds into the ring (the same drain the handlers run) and return the status
|
||||||
|
; register -- SCC RR0 after the drain, 6551 status before it -- for the
|
||||||
|
; transmit-ready test. A 6551 status read clears its IRQ bit, so it may only
|
||||||
|
; ever happen here or in the handler, both of which then take the byte.
|
||||||
|
; Returns 0 when no port is open.
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
iigsSerialService:
|
||||||
|
php
|
||||||
|
sei
|
||||||
|
phb
|
||||||
|
pea 0xE1E1
|
||||||
|
plb
|
||||||
|
plb
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
lda >gIigsSerPort
|
||||||
|
tay
|
||||||
|
lda >gIigsSerKind
|
||||||
|
cmp #SER_KIND_SCC
|
||||||
|
beq svcScc
|
||||||
|
cmp #SER_KIND_ACIA
|
||||||
|
beq svcAcia
|
||||||
|
lda #0
|
||||||
|
bra svcDone
|
||||||
|
svcScc:
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
jsr sccDrain
|
||||||
|
lda 0,y ; RR0 again: transmit-buffer-empty is bit 2
|
||||||
|
bra svcWiden
|
||||||
|
svcAcia:
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
lda 1,y
|
||||||
|
pha
|
||||||
|
jsr sscDrain
|
||||||
|
pla
|
||||||
|
svcWiden:
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
and #0x00FF
|
||||||
|
svcDone:
|
||||||
|
plb
|
||||||
|
plp
|
||||||
|
rtl
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; Drains. M=8, X=16, Y = port (see the header), DBR = an I/O bank.
|
||||||
|
; Clobber A and X; preserve Y.
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
sccDrain:
|
||||||
|
.a8
|
||||||
|
.i16
|
||||||
|
lda 0,y ; RR0 (the pointer rests at 0)
|
||||||
|
and #SCC_RR0_RX
|
||||||
|
beq sccDrainDone
|
||||||
|
lda 2,y ; the byte
|
||||||
|
jsr ringPut
|
||||||
|
lda #1
|
||||||
|
sta 0,y ; point at RR1: did that byte carry an error?
|
||||||
|
lda 0,y
|
||||||
|
and #SCC_RR1_ERRORS
|
||||||
|
beq sccDrain
|
||||||
|
and #SCC_RR1_OVERRUN
|
||||||
|
beq sccErrReset
|
||||||
|
jsr countOverrun
|
||||||
|
sccErrReset:
|
||||||
|
lda #SCC_WR0_ERR_RST ; unlatch the error (and unlock the FIFO)
|
||||||
|
sta 0,y
|
||||||
|
lda #SCC_WR0_EXT_RST ; MAME raises the special condition on the
|
||||||
|
sta 0,y ; ext/status IP, which only this clears
|
||||||
|
bra sccDrain
|
||||||
|
sccDrainDone:
|
||||||
|
rts
|
||||||
|
|
||||||
|
; A = a 6551 status byte just read.
|
||||||
|
sscDrain:
|
||||||
|
.a8
|
||||||
|
.i16
|
||||||
|
bit #ACIA_ST_RDRF
|
||||||
|
beq sscDrainDone
|
||||||
|
bit #ACIA_ST_OVERRUN ; the byte before this one was lost
|
||||||
|
beq sscTake
|
||||||
|
jsr countOverrun
|
||||||
|
sscTake:
|
||||||
|
lda 0,y ; the byte; clears RDRF and the error bits
|
||||||
|
jsr ringPut
|
||||||
|
lda 1,y
|
||||||
|
bra sscDrain
|
||||||
|
sscDrainDone:
|
||||||
|
rts
|
||||||
|
|
||||||
|
; A (8-bit) = received byte. Store it, then publish the tail; a full ring
|
||||||
|
; drops it and counts the drop. Clobbers X.
|
||||||
|
ringPut:
|
||||||
|
.a8
|
||||||
|
.i16
|
||||||
|
pha ; the byte
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda >gIigsRxTail
|
||||||
|
tax ; X = slot to fill
|
||||||
|
inc a
|
||||||
|
and >gIigsRxRingMask
|
||||||
|
cmp >gIigsRxHead
|
||||||
|
beq ringPutFull
|
||||||
|
pha ; the next tail; the byte is now at 3,s
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
lda 3,s
|
||||||
|
sta >gIigsRxRing,x ; store BEFORE publishing
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
pla
|
||||||
|
sta >gIigsRxTail
|
||||||
|
lda >gIigsRxPumped
|
||||||
|
inc a
|
||||||
|
sta >gIigsRxPumped
|
||||||
|
bra ringPutDone
|
||||||
|
ringPutFull:
|
||||||
|
lda >gIigsRxDropped
|
||||||
|
inc a
|
||||||
|
beq ringPutDone ; saturated at 0xFFFF
|
||||||
|
sta >gIigsRxDropped
|
||||||
|
ringPutDone:
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Saturating gIigsRxOverruns++. M=8 in and out; clobbers A.
|
||||||
|
countOverrun:
|
||||||
|
.a8
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda >gIigsRxOverruns
|
||||||
|
inc a
|
||||||
|
beq countOverrunDone
|
||||||
|
sta >gIigsRxOverruns
|
||||||
|
countOverrunDone:
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
rts
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; uint32_t iigsSerialIsrAddress(uint16_t kind)
|
||||||
|
;
|
||||||
|
; The 24-bit address of the handler for `kind`, for SetVector: the offset is
|
||||||
|
; a same-section immediate (the loader bank-aligns segments, so it equals the
|
||||||
|
; runtime offset) and the bank is K. Returned in A (low 16) : X (bank).
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
iigsSerialIsrAddress:
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
cmp #SER_KIND_ACIA
|
||||||
|
beq isrAddrAcia
|
||||||
|
lda #iigsSccIsr
|
||||||
|
bra isrAddrBank
|
||||||
|
isrAddrAcia:
|
||||||
|
lda #iigsSscIsr
|
||||||
|
isrAddrBank:
|
||||||
|
pha
|
||||||
|
phk
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
pla
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
and #0x00FF
|
||||||
|
tax
|
||||||
|
pla
|
||||||
|
rtl
|
||||||
|
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
; uint16_t iigsIrqOff(void) -- SEI; returns the caller's P
|
||||||
|
; void iigsIrqRestore(uint16_t p) -- puts that P (and its I bit) back
|
||||||
|
;
|
||||||
|
; For the mainline's two-access SCC register-pointer sequences: the ROM's
|
||||||
|
; interrupt entry reads RR3 through the same chip-wide pointer on EVERY
|
||||||
|
; interrupt, so an unmasked pointer write can land as data in the wrong
|
||||||
|
; register.
|
||||||
|
; ----------------------------------------------------------------
|
||||||
|
iigsIrqOff:
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
php
|
||||||
|
sei
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
pla
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
and #0x00FF
|
||||||
|
rtl
|
||||||
|
|
||||||
|
iigsIrqRestore:
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
pha
|
||||||
|
plp ; M/X back to the caller's 16/16
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
rtl
|
||||||
|
|
@ -197,11 +197,6 @@ static void spreadSetPalBase(uint8_t palBase);
|
||||||
static void uploadGfxPalette(const jlSurfaceT *src);
|
static void uploadGfxPalette(const jlSurfaceT *src);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// serial.c: drain the tiny (~64 B) RSDRV receive ring into the port's soft ring. Called from the
|
|
||||||
// long-running paths below (present / VBL wait / input poll) so mid-frame serial bytes are never
|
|
||||||
// lost while the HAL is busy -- see the soft-ring note in serial.c.
|
|
||||||
extern void x68kSerialPump(void);
|
|
||||||
|
|
||||||
static void vdispHandler(void);
|
static void vdispHandler(void);
|
||||||
static void vdispPoll(void);
|
static void vdispPoll(void);
|
||||||
|
|
||||||
|
|
@ -735,7 +730,6 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
uint16_t plane;
|
uint16_t plane;
|
||||||
uint16_t firstByte;
|
uint16_t firstByte;
|
||||||
uint16_t rowBytes;
|
uint16_t rowBytes;
|
||||||
uint16_t blitRows = 0;
|
|
||||||
|
|
||||||
// Four rows of dirty band read as ONE long. A clean row's minWord is the
|
// Four rows of dirty band read as ONE long. A clean row's minWord is the
|
||||||
// 0xFF CLEAN sentinel and any touched row's is <= STAGE_DIRTY_FULL_MAX, so
|
// 0xFF CLEAN sentinel and any touched row's is <= STAGE_DIRTY_FULL_MAX, so
|
||||||
|
|
@ -768,7 +762,6 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
applyScbBands();
|
applyScbBands();
|
||||||
gStageScbDirty = false;
|
gStageScbDirty = false;
|
||||||
}
|
}
|
||||||
x68kSerialPump();
|
|
||||||
for (g = 0u; g < (uint16_t)(SURFACE_HEIGHT / 4u); g++) {
|
for (g = 0u; g < (uint16_t)(SURFACE_HEIGHT / 4u); g++) {
|
||||||
if (minGroups[g] == 0xFFFFFFFFul) {
|
if (minGroups[g] == 0xFFFFFFFFul) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -778,12 +771,6 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
if (!STAGE_DIRTY_ROW_TOUCHED(y) || STAGE_DIRTY_ROW_CLEAN(y)) {
|
if (!STAGE_DIRTY_ROW_TOUCHED(y) || STAGE_DIRTY_ROW_CLEAN(y)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Same pump cadence as the graphics path: a blitted row is line
|
|
||||||
// time, so keep the 64-byte RSDRV ring from overflowing.
|
|
||||||
if ((blitRows & 1u) == 0u) {
|
|
||||||
x68kSerialPump();
|
|
||||||
}
|
|
||||||
blitRows++;
|
|
||||||
// Bands are 16-bit stage words = 4 pixels = 2 bytes of a plane.
|
// Bands are 16-bit stage words = 4 pixels = 2 bytes of a plane.
|
||||||
firstByte = (uint16_t)(gStageMinWord[y] >> 1);
|
firstByte = (uint16_t)(gStageMinWord[y] >> 1);
|
||||||
rowBytes = (uint16_t)((gStageMaxWord[y] >> 1) - firstByte + 1u);
|
rowBytes = (uint16_t)((gStageMaxWord[y] >> 1) - firstByte + 1u);
|
||||||
|
|
@ -816,7 +803,6 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
const uint8_t *sp;
|
const uint8_t *sp;
|
||||||
uint16_t b;
|
uint16_t b;
|
||||||
uint8_t palBase;
|
uint8_t palBase;
|
||||||
uint16_t blitRows = 0;
|
|
||||||
|
|
||||||
if (src == NULL) {
|
if (src == NULL) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -837,18 +823,10 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sp = (const uint8_t *)&gSpreadPair[0][0][0];
|
sp = (const uint8_t *)&gSpreadPair[0][0][0];
|
||||||
x68kSerialPump(); // the palette upload + spread-table build above are already line time
|
|
||||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||||
if (!STAGE_DIRTY_ROW_TOUCHED(y) || STAGE_DIRTY_ROW_CLEAN(y)) {
|
if (!STAGE_DIRTY_ROW_TOUCHED(y) || STAGE_DIRTY_ROW_CLEAN(y)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// A blitted row costs ~0.5-0.7 ms, so pump every second one: that keeps the gap near ~1 ms,
|
|
||||||
// inside the ~2.8 ms the 64-byte RSDRV ring covers at the port's fastest rate (230400).
|
|
||||||
// Clean rows are a flag test and skip the pump, so an idle present stays trap-free.
|
|
||||||
if ((blitRows & 1u) == 0u) {
|
|
||||||
x68kSerialPump();
|
|
||||||
}
|
|
||||||
blitRows++;
|
|
||||||
// Bands are 16-bit stage words = 4 pixels = 2 bytes of a 1bpp plane.
|
// Bands are 16-bit stage words = 4 pixels = 2 bytes of a 1bpp plane.
|
||||||
firstByte = (uint16_t)(gStageMinWord[y] >> 1);
|
firstByte = (uint16_t)(gStageMinWord[y] >> 1);
|
||||||
rowBytes = (uint16_t)((gStageMaxWord[y] >> 1) - firstByte + 1u);
|
rowBytes = (uint16_t)((gStageMaxWord[y] >> 1) - firstByte + 1u);
|
||||||
|
|
@ -1015,9 +993,6 @@ void jlpInputPoll(void) {
|
||||||
uint32_t mouseButtons;
|
uint32_t mouseButtons;
|
||||||
|
|
||||||
for (group = 0; group < X68K_KEYGROUP_COUNT; group++) {
|
for (group = 0; group < X68K_KEYGROUP_COUNT; group++) {
|
||||||
// Per-group, not per-poll: the whole bitsns trap storm can pass the ~2.8 ms the 64-byte RSDRV
|
|
||||||
// ring covers at the port's fastest rate, but one group's trap never does.
|
|
||||||
x68kSerialPump();
|
|
||||||
groups[group] = (uint8_t)(_iocs_bitsns((int)group) & 0xFF);
|
groups[group] = (uint8_t)(_iocs_bitsns((int)group) & 0xFF);
|
||||||
}
|
}
|
||||||
// Write both states each poll: the bitmap is live key-DOWN data,
|
// Write both states each poll: the bitmap is live key-DOWN data,
|
||||||
|
|
@ -1148,11 +1123,6 @@ void jlpWaitVBL(void) {
|
||||||
guard = 0ul;
|
guard = 0ul;
|
||||||
while (gFrameCount == start && guard < X68K_VBL_SPIN_LIMIT) {
|
while (gFrameCount == start && guard < X68K_VBL_SPIN_LIMIT) {
|
||||||
vdispPoll();
|
vdispPoll();
|
||||||
// Every 64 spins ~= a few hundred microseconds: comfortably inside the ~2.8 ms the 64-byte
|
|
||||||
// RSDRV ring covers at 230400. Trap cost is free here -- this is a blocking wait anyway.
|
|
||||||
if ((guard & 63ul) == 0ul) {
|
|
||||||
x68kSerialPump();
|
|
||||||
}
|
|
||||||
guard++;
|
guard++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,89 @@
|
||||||
// Sharp X68000 serial HAL: RS-232C via the IOCS polled API.
|
// Sharp X68000 serial HAL: RS-232C on SCC channel A.
|
||||||
//
|
//
|
||||||
// Structurally the Atari ST HAL (src/atarist/serial.c): ride the OS's polled
|
// Transmit goes through IOCS, whose calls map one-for-one onto the ST HAL's
|
||||||
// interface, hijack no vector, buffer nothing ourselves. The IOCS calls map
|
// XBIOS ones (src/atarist/serial.c) --
|
||||||
// one-for-one onto the ST's XBIOS ones --
|
|
||||||
// _iocs_set232c <- Rsconf (baud + framing, one packed word)
|
// _iocs_set232c <- Rsconf (baud + framing, one packed word)
|
||||||
// _iocs_isns232c <- Bconstat (input status)
|
// _iocs_isns232c <- Bconstat (input status)
|
||||||
// _iocs_inp232c <- Bconin (read one byte, BLOCKS if empty)
|
// _iocs_inp232c <- Bconin (read one byte, BLOCKS if empty)
|
||||||
// _iocs_osns232c <- Bcostat (transmitter ready)
|
// _iocs_osns232c <- Bcostat (transmitter ready)
|
||||||
// _iocs_out232c <- Bconout (write one byte)
|
// _iocs_out232c <- Bconout (write one byte)
|
||||||
|
// Receive is our own: the channel A RX interrupt feeds a soft ring (see the
|
||||||
|
// takeover section below), and when that cannot be installed the polled
|
||||||
|
// pump drains RSDRV.SYS's ring into the same soft ring instead.
|
||||||
//
|
//
|
||||||
// The machine has one RS-232C line (SCC channel A); channel B is the mouse and
|
// The machine has one RS-232C line (SCC channel A); channel B is the mouse and
|
||||||
// is not reachable here, so jlSerialDeviceE is accepted and ignored exactly as
|
// is not reachable here, so jlSerialDeviceE is accepted and ignored exactly as
|
||||||
// on the ST.
|
// on the ST.
|
||||||
|
|
||||||
|
#include <x68k/dos.h>
|
||||||
#include <x68k/iocs.h>
|
#include <x68k/iocs.h>
|
||||||
|
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
||||||
|
|
||||||
// LINE CONFIGURATION IS DELIBERATELY NOT APPLIED YET.
|
// ----- Line configuration --------------------------------------------------
|
||||||
//
|
//
|
||||||
// _iocs_set232c takes a single packed mode word, and the packing could not be
|
// _iocs_set232c's mode word, from the IOCS call reference (reproduced on Data
|
||||||
// confirmed. What IS confirmed, from XEiJ's RS232CTerminal.java (which reads
|
// Crystal's X68k/IOCS page):
|
||||||
// the live mode word out of the RSDRV.SYS work area):
|
// bits 15-14 stop bits 1 = 1, 2 = 1.5, 0 or 3 = 2
|
||||||
// bit 7 RTS/CTS flow control
|
// bits 13-12 parity 1 = odd, 3 = even, 0 or 2 = none
|
||||||
// bit 9 XON/XOFF flow control
|
// bits 11-10 data bits 0 = 5 or fewer, 1 = 6, 2 = 7, 3 = 8
|
||||||
// and its baud-rate table is
|
// bit 9 XON/XOFF
|
||||||
// 0=75 1=150 2=300 3=600 4=1200 5=2400 6=4800 7=9600 8=19200 9=31250
|
// bit 8 SI/SO (does not work in ROM IOCS; never set)
|
||||||
// 10=38400 11=50000 12=57600 13=76800 14=115200 15=230400
|
// bit 7 RTS/CTS (RSDRV.SYS only, below)
|
||||||
// i.e. the rate is a FOUR-bit field, so it cannot sit at bits 0-2 with parity
|
// bits 6-0 rate index 0..7 = 75 150 300 600 1200 2400 4800 9600
|
||||||
// at bit 3. The positions of the data/stop/parity fields remain unknown.
|
// It returns the mode it replaced, which jlpSerialClose puts back.
|
||||||
//
|
//
|
||||||
// Writing a guessed mode word is worse than writing none: a wrong framing
|
// Two implementations answer it, and they differ above 9600 (both read from
|
||||||
// setting still transmits, it just corrupts every byte, which is exactly the
|
// their code). The ROM's (IPL-ROM 1.0) takes indexes 0..7 and turns anything
|
||||||
// failure this port hit first time out (every received byte came back as 0x00
|
// else -- bit 7 included -- into 1200. Human68k's RS-232C driver RSDRV.SYS
|
||||||
// or 0x80 -- only the MSB surviving).
|
// (1.13 on the Human68k 3.02 system disk, whose CONFIG.SYS loads it) replaces
|
||||||
//
|
// the RS-232C IOCS calls, takes indexes 0..9 -- 8 = 19200, 9 = 38400 (time
|
||||||
// So jlpSerialOpen inherits whatever RSDRV.SYS configured at boot. The Human68k
|
// constant 2, really 39062 bps, 1.7% fast) -- and reads bit 7 as RTS/CTS,
|
||||||
// "RS-232C DRIVER for X68000 version 2.02" banner appears during startup and
|
// which overrides XON/XOFF: its _OSNS232C reports "not ready" while CTS is
|
||||||
// leaves the port at its default, which pairs with MAME's null_modem default
|
// off, and its receive side drops RTS as its buffer fills. So the rates above
|
||||||
// of 9600 8N1. Honour jlSerialConfigT properly once the layout is confirmed
|
// 9600 and RTS/CTS are used only when RSDRV is detected (x68kRsdrvPresent);
|
||||||
// against hardware documentation.
|
// without it a faster request clamps to 9600 and RTS/CTS falls back to no
|
||||||
|
// handshaking. With this HAL's receive interrupt installed (below) RSDRV's
|
||||||
|
// receive side never runs, so RTS stays up and the 4 KB soft ring is the
|
||||||
|
// receive buffer; transmit still waits for CTS.
|
||||||
|
#define X68K_MODE_STOP_1 0x4000u
|
||||||
|
#define X68K_MODE_STOP_2 0xC000u
|
||||||
|
#define X68K_MODE_PARITY_ODD 0x1000u
|
||||||
|
#define X68K_MODE_PARITY_EVEN 0x3000u
|
||||||
|
#define X68K_MODE_DATA_SHIFT 10
|
||||||
|
#define X68K_MODE_DATA_MASK 0x03u
|
||||||
|
#define X68K_MODE_XONXOFF 0x0200u
|
||||||
|
#define X68K_MODE_RTSCTS 0x0080u
|
||||||
|
#define X68K_DATA_BITS_MIN 5u
|
||||||
|
#define X68K_DATA_BITS_MAX 8u
|
||||||
|
#define X68K_RATE_COUNT_ROM 8u
|
||||||
|
#define X68K_RATE_COUNT_RSDRV 10u
|
||||||
|
|
||||||
|
static const uint16_t kX68kRates[X68K_RATE_COUNT_RSDRV] = { 75u, 150u, 300u, 600u, 1200u, 2400u, 4800u, 9600u, 19200u, 38400u };
|
||||||
|
|
||||||
|
// RSDRV.SYS detection. The driver adds an extended RS-232C IOCS call, $F1
|
||||||
|
// (d2 = function | port << 8), whose function $37 answers $0201 and touches
|
||||||
|
// nothing (read from its code). The ROM leaves $F1 unassigned: its vector
|
||||||
|
// holds the call number in the top byte over the ROM's error handler
|
||||||
|
// ($F1FF1B1E on IPL-ROM 1.0), and calling that is an error, so the call is
|
||||||
|
// made only when the vector points into RAM. DOS _INTVCG reads the vector
|
||||||
|
// from user mode; IOCS call n is its vector $100 + n.
|
||||||
|
#define X68K_IOCS_RS232C_EXT 0xF1
|
||||||
|
#define X68K_INTVC_IOCS_BASE 0x100
|
||||||
|
#define X68K_RSDRV_IDENT_FN 0x37
|
||||||
|
#define X68K_RSDRV_IDENT 0x0201
|
||||||
|
#define X68K_RAM_END 0x00C00000ul
|
||||||
|
|
||||||
|
// WR5 as _SET232C writes it -- the ROM and RSDRV alike: DTR, Tx enable and
|
||||||
|
// RTS, plus the Tx bits-per-char code in bits 6-5 -- so jlpSerialSetDtr can
|
||||||
|
// change DTR alone (WR5 is write-only). The data-bits code of the mode word
|
||||||
|
// (0 = 5 .. 3 = 8) becomes the Z8530's (5 = 0, 6 = 2, 7 = 1, 8 = 3) by
|
||||||
|
// swapping 6 and 7.
|
||||||
|
#define X68K_WR5_BASE 0x8Au
|
||||||
|
#define X68K_WR5_DTR 0x80u
|
||||||
|
#define X68K_WR5_BITS_SHIFT 5
|
||||||
|
#define X68K_SCC_WR5 5u
|
||||||
|
|
||||||
// Bounded transmitter-ready spin, so a stalled link (no CTS) reports a partial
|
// Bounded transmitter-ready spin, so a stalled link (no CTS) reports a partial
|
||||||
// write instead of hanging the frame. Same guard the ST HAL uses.
|
// write instead of hanging the frame. Same guard the ST HAL uses.
|
||||||
|
|
@ -48,24 +91,18 @@
|
||||||
|
|
||||||
// ----- Soft receive ring -----------------------------------------------------
|
// ----- Soft receive ring -----------------------------------------------------
|
||||||
//
|
//
|
||||||
// RSDRV.SYS's own receive ring is tiny -- MEASURED at ~64 bytes on MAME: a live capture showed a
|
// The IOCS receive ring is tiny -- MEASURED at ~64 bytes on MAME: a live capture showed a 203-byte
|
||||||
// 203-byte frame delivered as its first 63 bytes, a 139-byte hole, then its final byte, exactly the
|
// frame delivered as its first 63 bytes, a 139-byte hole, then its final byte, exactly the signature
|
||||||
// signature of a 64-entry ring filling while the client was busy and discarding until drained. Every
|
// of a 64-entry ring filling while the client was busy and discarding until drained. So this port
|
||||||
// other JoeyLib target rides a big interrupt-fed ring (DOS hardware IRQ ring, ST XBIOS IOREC, and the
|
// takes the receive interrupt over (below) and fills a soft ring of its own, the way DOS rides a
|
||||||
// 8-bit clients' IRQ rings), so only this port could lose mid-frame bytes whenever the caller went
|
// hardware IRQ ring and the ST TOS's IOREC: the program can go deaf for a whole present without
|
||||||
// deaf for longer than ~66 ms at 9600 baud -- and jlpPresent's full-stage blit, jlpWaitVBL's V-DISP
|
// losing a byte. Only when the takeover cannot be installed does x68kSerialPump drain the IOCS
|
||||||
// spin, and jlpInputPoll's IOCS trap storm all can. The fix: drain the IOCS ring into this soft ring
|
// ring instead, from the add-on's own entry points -- then the app must call jlSerialPoll often.
|
||||||
// from INSIDE those long-running paths (x68kSerialPump below), so the 64-byte hardware-side window
|
|
||||||
// only ever has to cover the gap between pumps, never a whole present. No ISR is involved -- the
|
|
||||||
// pump is a plain call -- so none of the vdispst-style vector hazards apply.
|
|
||||||
//
|
//
|
||||||
// SIZED FOR THE FASTEST LINE, not just the 9600 default: this port inherits RSDRV.SYS's boot rate
|
// 4 KB holds over four seconds at 9600 (jlpSerialOpen's ceiling, see the mode-word note above),
|
||||||
// (the mode word is deliberately not written), and RSDRV's table reaches 230400, where the 64-byte
|
// longer than the worst deaf window (a >100 ms all-dirty present), so even an unpaced burst -- the
|
||||||
// IOCS ring covers only ~2.8 ms -- so every pump site keeps its interval near 1 ms (present: every
|
// server's flowWait timeout degrade streams a whole screen without acks -- fits while the loop is
|
||||||
// 2 blitted rows; input poll: every bitsns group; VBL wait: every 64 spins). 4 KB here holds ~178 ms
|
// busy. It is plain RAM on a 4 MB machine; do not shrink it to save bytes.
|
||||||
// of full-rate 230400 traffic, longer than the worst deaf window (a >100 ms all-dirty present), so
|
|
||||||
// even an unpaced burst -- the server's flowWait timeout degrade streams a whole screen without acks
|
|
||||||
// -- fits while the loop is busy. It is plain RAM on a 4 MB machine; do not shrink it to save bytes.
|
|
||||||
#define X68K_RX_RING_CAP 4096u
|
#define X68K_RX_RING_CAP 4096u
|
||||||
|
|
||||||
// Shared with x68kSccRxIsr.s (the SCC channel A RX interrupt handler): the ISR is the producer
|
// Shared with x68kSccRxIsr.s (the SCC channel A RX interrupt handler): the ISR is the producer
|
||||||
|
|
@ -92,43 +129,46 @@ volatile uint16_t gX68kRxTail = 0u; // push side
|
||||||
extern void x68kSccRxIsr(void);
|
extern void x68kSccRxIsr(void);
|
||||||
extern void x68kSccSpIsr(void);
|
extern void x68kSccSpIsr(void);
|
||||||
|
|
||||||
static uint16_t x68kIrqMask(void);
|
|
||||||
static void x68kIrqRestore(uint16_t sr);
|
|
||||||
static void sccIsrInstall(void);
|
static void sccIsrInstall(void);
|
||||||
static void sccIsrUninstall(void);
|
static void sccIsrUninstall(void);
|
||||||
|
static bool x68kInSupervisor(void);
|
||||||
|
static uint16_t x68kIrqMask(void);
|
||||||
|
static void x68kIrqRestore(uint16_t sr);
|
||||||
|
static bool x68kRsdrvPresent(void);
|
||||||
|
static uint16_t x68kSerialMode(const jlSerialConfigT *config, bool rsdrv);
|
||||||
|
static void x68kSerialPump(void);
|
||||||
|
static int32_t x68kSuperEnter(void);
|
||||||
|
static void x68kSuperLeave(int32_t ssp);
|
||||||
|
static uint8_t x68kWr5ForMode(uint16_t mode);
|
||||||
|
static void x68kWr5Write(uint8_t wr5);
|
||||||
|
|
||||||
static bool gSccIsrInstalled = false;
|
static bool gSccIsrInstalled = false;
|
||||||
// Has the app opened the port at all? Without this the polled pump traps into
|
// Has the app opened the port at all? Without this the polled pump traps into
|
||||||
// IOCS on every call, and jlpPresent calls it once every two blitted rows --
|
// IOCS on every call, and jlpPresent calls it once every two blitted rows --
|
||||||
// about fifty _ISNS232C traps per frame for a game that never touched serial.
|
// about fifty _ISNS232C traps per frame for a game that never touched serial.
|
||||||
static bool gSerialOpened = false;
|
static bool gSerialOpened = false;
|
||||||
|
// The mode _iocs_set232c replaced at open, put back at close.
|
||||||
|
static int gPrevMode = 0;
|
||||||
|
// Channel A's WR5 as last written (see X68K_WR5_BASE), for jlpSerialSetDtr.
|
||||||
|
static uint8_t gX68kWr5 = 0u;
|
||||||
static uint8_t gSccRxVec = 0u;
|
static uint8_t gSccRxVec = 0u;
|
||||||
static uint32_t gSccOldRx = 0ul;
|
static uint32_t gSccOldRx = 0ul;
|
||||||
static uint32_t gSccOldSp = 0ul;
|
static uint32_t gSccOldSp = 0ul;
|
||||||
|
|
||||||
|
|
||||||
static uint16_t x68kIrqMask(void) {
|
|
||||||
uint16_t sr;
|
|
||||||
|
|
||||||
__asm__ volatile("move.w %%sr,%0\n\tori.w #0x0700,%%sr" : "=d"(sr) : : "cc");
|
|
||||||
return sr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void x68kIrqRestore(uint16_t sr) {
|
|
||||||
__asm__ volatile("move.w %0,%%sr" : : "d"(sr) : "cc");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void sccIsrInstall(void) {
|
static void sccIsrInstall(void) {
|
||||||
uint16_t sr;
|
uint16_t sr;
|
||||||
uint8_t base;
|
uint8_t base;
|
||||||
|
|
||||||
if (gSccIsrInstalled) {
|
// Only a program already in supervisor mode (every jlInit app) takes the
|
||||||
|
// interrupt over. One in user mode (SERTEST) stays on the polled pump,
|
||||||
|
// which needs no privilege and races nothing -- and since there is then
|
||||||
|
// no ISR, jlpSerialWrite needs no interrupt mask either.
|
||||||
|
if (gSccIsrInstalled || !x68kInSupervisor()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// RR2 read is a two-access pointer sequence on the shared SCC, so no interrupt (the channel B
|
// RR2 read is a two-access pointer sequence on the shared SCC, so no interrupt (the channel B
|
||||||
// mouse) may interleave. We run supervisor (jlpInit's _dos_super), so the SR mask is legal.
|
// mouse) may interleave.
|
||||||
sr = x68kIrqMask();
|
sr = x68kIrqMask();
|
||||||
*X68K_SCC_A_CTRL = 2u;
|
*X68K_SCC_A_CTRL = 2u;
|
||||||
base = *X68K_SCC_A_CTRL;
|
base = *X68K_SCC_A_CTRL;
|
||||||
|
|
@ -151,24 +191,108 @@ static void sccIsrInstall(void) {
|
||||||
|
|
||||||
static void sccIsrUninstall(void) {
|
static void sccIsrUninstall(void) {
|
||||||
uint16_t sr;
|
uint16_t sr;
|
||||||
|
int32_t ssp;
|
||||||
|
|
||||||
if (!gSccIsrInstalled) {
|
if (!gSccIsrInstalled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Supervisor was a given at install time, but the app may have left it
|
||||||
|
// since (jlShutdown before jlSerialClose), so make sure of it here.
|
||||||
|
ssp = x68kSuperEnter();
|
||||||
sr = x68kIrqMask();
|
sr = x68kIrqMask();
|
||||||
*X68K_VECTOR(gSccRxVec) = gSccOldRx;
|
*X68K_VECTOR(gSccRxVec) = gSccOldRx;
|
||||||
*X68K_VECTOR(gSccRxVec + 2u) = gSccOldSp;
|
*X68K_VECTOR(gSccRxVec + 2u) = gSccOldSp;
|
||||||
x68kIrqRestore(sr);
|
x68kIrqRestore(sr);
|
||||||
|
x68kSuperLeave(ssp);
|
||||||
gSccIsrInstalled = false;
|
gSccIsrInstalled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Drain everything the IOCS ring holds into the soft ring. Called from the HAL's own long-running
|
// Installing and removing the RX ISR touches the vector table, the SCC and
|
||||||
// paths (present / VBL wait / input poll) and from every serial entry point. With the SCC ISR
|
// the SR, all supervisor-only. Inside a JoeyLib app jlInit has already
|
||||||
// installed this is a cheap no-op guard (RSDRV's ring never fills); without it, it is the polled
|
// switched to supervisor, but the add-on must not depend on that: SERTEST
|
||||||
// fallback that keeps the port working, with the caller deaf-tolerant up to the SOFT ring instead
|
// opens the port without jlInit. _dos_super(0) switches and returns the old
|
||||||
// of RSDRV's ~64 bytes.
|
// stack pointer, or a negative value when the CPU already was supervisor;
|
||||||
void x68kSerialPump(void) {
|
// hand that value to x68kSuperLeave, which only switches back if this did.
|
||||||
|
static bool x68kInSupervisor(void) {
|
||||||
|
int32_t ssp;
|
||||||
|
|
||||||
|
ssp = x68kSuperEnter();
|
||||||
|
x68kSuperLeave(ssp);
|
||||||
|
return ssp < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t x68kIrqMask(void) {
|
||||||
|
uint16_t sr;
|
||||||
|
|
||||||
|
__asm__ volatile("move.w %%sr,%0\n\tori.w #0x0700,%%sr" : "=d"(sr) : : "cc");
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void x68kIrqRestore(uint16_t sr) {
|
||||||
|
__asm__ volatile("move.w %0,%%sr" : : "d"(sr) : "cc");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Is RSDRV.SYS loaded? See X68K_IOCS_RS232C_EXT for the test.
|
||||||
|
static bool x68kRsdrvPresent(void) {
|
||||||
|
register int32_t ret __asm__("d0");
|
||||||
|
register int32_t fn __asm__("d2");
|
||||||
|
|
||||||
|
if ((uint32_t)_dos_intvcg(X68K_INTVC_IOCS_BASE + X68K_IOCS_RS232C_EXT) >= X68K_RAM_END) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ret = X68K_IOCS_RS232C_EXT;
|
||||||
|
fn = X68K_RSDRV_IDENT_FN;
|
||||||
|
__asm__ volatile("trap #15" : "+d"(ret) : "d"(fn) : "d1", "a0", "a1", "cc", "memory");
|
||||||
|
return ret == X68K_RSDRV_IDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The _iocs_set232c mode word for a JoeyLib line configuration (see the table
|
||||||
|
// above): the rate snaps down to the nearest one the IOCS in charge supports,
|
||||||
|
// and RTS/CTS becomes no handshaking without RSDRV.SYS.
|
||||||
|
static uint16_t x68kSerialMode(const jlSerialConfigT *config, bool rsdrv) {
|
||||||
|
uint16_t mode;
|
||||||
|
uint8_t rate;
|
||||||
|
uint8_t rateCount;
|
||||||
|
uint8_t dataBits;
|
||||||
|
|
||||||
|
rateCount = rsdrv ? X68K_RATE_COUNT_RSDRV : X68K_RATE_COUNT_ROM;
|
||||||
|
rate = 0u;
|
||||||
|
while (rate + 1u < rateCount && kX68kRates[rate + 1u] <= config->baud) {
|
||||||
|
rate++;
|
||||||
|
}
|
||||||
|
dataBits = config->dataBits;
|
||||||
|
if (dataBits < X68K_DATA_BITS_MIN) {
|
||||||
|
dataBits = X68K_DATA_BITS_MIN;
|
||||||
|
}
|
||||||
|
if (dataBits > X68K_DATA_BITS_MAX) {
|
||||||
|
dataBits = X68K_DATA_BITS_MAX;
|
||||||
|
}
|
||||||
|
mode = (uint16_t)(rate | ((uint16_t)(dataBits - X68K_DATA_BITS_MIN) << X68K_MODE_DATA_SHIFT));
|
||||||
|
mode |= (config->stopBits >= 2u) ? X68K_MODE_STOP_2 : X68K_MODE_STOP_1;
|
||||||
|
if (config->parity == JL_SERIAL_PARITY_ODD) {
|
||||||
|
mode |= X68K_MODE_PARITY_ODD;
|
||||||
|
} else if (config->parity == JL_SERIAL_PARITY_EVEN) {
|
||||||
|
mode |= X68K_MODE_PARITY_EVEN;
|
||||||
|
}
|
||||||
|
if (config->flow == JL_SERIAL_FLOW_XONXOFF) {
|
||||||
|
mode |= X68K_MODE_XONXOFF;
|
||||||
|
}
|
||||||
|
if (rsdrv && config->flow == JL_SERIAL_FLOW_RTSCTS) {
|
||||||
|
mode |= X68K_MODE_RTSCTS;
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Drain everything the IOCS ring holds into the soft ring. Called from every serial entry point.
|
||||||
|
// With the SCC ISR installed this is a cheap no-op guard (the IOCS ring never fills); without it, it
|
||||||
|
// is the polled fallback that keeps the port working.
|
||||||
|
static void x68kSerialPump(void) {
|
||||||
if (gSccIsrInstalled || !gSerialOpened) {
|
if (gSccIsrInstalled || !gSerialOpened) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +308,48 @@ void x68kSerialPump(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t x68kSuperEnter(void) {
|
||||||
|
return (int32_t)_dos_super(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void x68kSuperLeave(int32_t ssp) {
|
||||||
|
if (ssp >= 0) {
|
||||||
|
(void)_dos_super((int)ssp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WR5 as _SET232C writes it for `mode` (see X68K_WR5_BASE).
|
||||||
|
static uint8_t x68kWr5ForMode(uint16_t mode) {
|
||||||
|
uint8_t code;
|
||||||
|
|
||||||
|
code = (uint8_t)((mode >> X68K_MODE_DATA_SHIFT) & X68K_MODE_DATA_MASK);
|
||||||
|
if (code == 1u || code == 2u) {
|
||||||
|
code = (uint8_t)(code ^ X68K_MODE_DATA_MASK);
|
||||||
|
}
|
||||||
|
return (uint8_t)(X68K_WR5_BASE | (uint8_t)(code << X68K_WR5_BITS_SHIFT));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Write channel A's WR5 and remember it. The pointer write and the data write
|
||||||
|
// are two accesses to the chip-wide register pointer, so no interrupt (our RX
|
||||||
|
// ISR, the channel B mouse) may come between them -- see jlpSerialWrite. The
|
||||||
|
// SCC is supervisor-only, and the app may have left supervisor mode.
|
||||||
|
static void x68kWr5Write(uint8_t wr5) {
|
||||||
|
int32_t ssp;
|
||||||
|
uint16_t sr;
|
||||||
|
|
||||||
|
gX68kWr5 = wr5;
|
||||||
|
ssp = x68kSuperEnter();
|
||||||
|
sr = x68kIrqMask();
|
||||||
|
*X68K_SCC_A_CTRL = X68K_SCC_WR5;
|
||||||
|
*X68K_SCC_A_CTRL = wr5;
|
||||||
|
x68kIrqRestore(sr);
|
||||||
|
x68kSuperLeave(ssp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- HAL entry points (alphabetical) -----
|
// ----- HAL entry points (alphabetical) -----
|
||||||
|
|
||||||
uint16_t jlpSerialAvailable(void) {
|
uint16_t jlpSerialAvailable(void) {
|
||||||
|
|
@ -192,8 +358,21 @@ uint16_t jlpSerialAvailable(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Close leaves DTR as it is. _SET232C raises it whenever it runs, so a line the
|
||||||
|
// program hung up (jlSerialSetDtr) is dropped again straight after the restore:
|
||||||
|
// it is up only for the rest of that call, 0.17 ms under MAME with RSDRV.SYS.
|
||||||
void jlpSerialClose(void) {
|
void jlpSerialClose(void) {
|
||||||
|
bool dtrOn;
|
||||||
|
|
||||||
|
if (!gSerialOpened) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dtrOn = (gX68kWr5 & X68K_WR5_DTR) != 0u;
|
||||||
sccIsrUninstall(); // hand channel A RX back to RSDRV before the vectors dangle
|
sccIsrUninstall(); // hand channel A RX back to RSDRV before the vectors dangle
|
||||||
|
(void)_iocs_set232c(gPrevMode);
|
||||||
|
if (!dtrOn) {
|
||||||
|
x68kWr5Write((uint8_t)(x68kWr5ForMode((uint16_t)gPrevMode) & (uint8_t)~X68K_WR5_DTR));
|
||||||
|
}
|
||||||
gSerialOpened = false;
|
gSerialOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,16 +386,15 @@ void jlpSerialFlush(void) {
|
||||||
|
|
||||||
|
|
||||||
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
|
uint16_t mode;
|
||||||
|
|
||||||
(void)device; // one RS-232C line; channel B is the mouse.
|
(void)device; // one RS-232C line; channel B is the mouse.
|
||||||
if (config == NULL) {
|
if (config == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Line config is accepted but NOT applied -- see the mode-word note above. The
|
mode = x68kSerialMode(config, x68kRsdrvPresent());
|
||||||
// port keeps whatever RSDRV.SYS set at boot. (An RTS/CTS enable via an
|
gPrevMode = _iocs_set232c((int)mode);
|
||||||
// _iocs_set232c(-1) read-modify-write was tried and REVERTED: the -1-as-query
|
gX68kWr5 = x68kWr5ForMode(mode); // _SET232C just wrote this, DTR on
|
||||||
// idiom is a _CRTMOD contract, undocumented for _SET232C -- XEiJ reads the
|
|
||||||
// RSDRV work area instead -- so the write could not be trusted.)
|
|
||||||
(void)config;
|
|
||||||
gSerialOpened = true;
|
gSerialOpened = true;
|
||||||
sccIsrInstall(); // own the RX interrupt; falls back to the polled pump if the vectors look wrong
|
sccIsrInstall(); // own the RX interrupt; falls back to the polled pump if the vectors look wrong
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -242,6 +420,22 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DTR on demand (jlSerialSetDtr, how a program hangs up): WR5 bit 7, written
|
||||||
|
// directly -- neither the ROM nor RSDRV.SYS has an IOCS call for it that keeps
|
||||||
|
// the data bits. Whatever runs _SET232C next (the next open, another program)
|
||||||
|
// raises DTR again; close keeps a dropped one down.
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
if (!gSerialOpened) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (on) {
|
||||||
|
x68kWr5Write((uint8_t)(gX68kWr5 | X68K_WR5_DTR));
|
||||||
|
} else {
|
||||||
|
x68kWr5Write((uint8_t)(gX68kWr5 & (uint8_t)~X68K_WR5_DTR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// The Z8530's register pointer is ONE register for the whole chip, shared by
|
// The Z8530's register pointer is ONE register for the whole chip, shared by
|
||||||
// both channels, and selecting a register is a separate access from using it.
|
// both channels, and selecting a register is a separate access from using it.
|
||||||
// So any two-access sequence has to be atomic or the two accesses belong to
|
// So any two-access sequence has to be atomic or the two accesses belong to
|
||||||
|
|
@ -271,12 +465,18 @@ uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t sr;
|
uint16_t sr;
|
||||||
int ready;
|
int ready;
|
||||||
|
|
||||||
|
// The masks keep our RX ISR out of the IOCS calls' SCC accesses; with no
|
||||||
|
// ISR installed (the polled path) there is nothing to keep out.
|
||||||
for (n = 0u; n < len; n++) {
|
for (n = 0u; n < len; n++) {
|
||||||
guard = 0ul;
|
guard = 0ul;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sr = x68kIrqMask();
|
if (gSccIsrInstalled) {
|
||||||
ready = _iocs_osns232c();
|
sr = x68kIrqMask();
|
||||||
x68kIrqRestore(sr);
|
ready = _iocs_osns232c();
|
||||||
|
x68kIrqRestore(sr);
|
||||||
|
} else {
|
||||||
|
ready = _iocs_osns232c();
|
||||||
|
}
|
||||||
if (ready != 0) {
|
if (ready != 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -285,9 +485,13 @@ uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sr = x68kIrqMask();
|
if (gSccIsrInstalled) {
|
||||||
_iocs_out232c((int)buf[n]);
|
sr = x68kIrqMask();
|
||||||
x68kIrqRestore(sr);
|
_iocs_out232c((int)buf[n]);
|
||||||
|
x68kIrqRestore(sr);
|
||||||
|
} else {
|
||||||
|
_iocs_out232c((int)buf[n]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
// Sharp X68000 planar tile ops -- adapted verbatim from src/amiga/x68kTile.h.
|
// Sharp X68000 planar tile ops -- adapted verbatim from src/amiga/amigaTile.h.
|
||||||
// The surface layout is identical (40 bytes per row per plane), so an 8x8 tile
|
// The surface layout is identical (40 bytes per row per plane), so an 8x8 tile
|
||||||
// is 8 rows x 4 planes x 1 byte on both machines and the .tbk blob format is
|
// is 8 rows x 4 planes x 1 byte on both machines and the .tbk payload is
|
||||||
// byte-compatible; only the type names differ.
|
// byte-compatible (assetbake --target x68000 writes the Amiga layout under its
|
||||||
// Amiga tile ops as always-inline functions (NATIVE-PERF Phase 2
|
// own target byte); only the type names differ.
|
||||||
// R4a). Bodies moved verbatim from src/amiga/hal.c so the public
|
// Tile ops as always-inline functions, so the public wrappers in
|
||||||
// wrappers in src/core/tile.c inline them, deleting the second full
|
// src/core/tile.c inline them, deleting the second full cdecl call layer
|
||||||
// cdecl call layer (~130-190 cycles/op on the 68000). The jlpTile*
|
// (~130-190 cycles/op on the 68000). The jlpTile* dispatch names are
|
||||||
// dispatch names are macro-aliased to these in port.h, which also
|
// macro-aliased to these in port.h, which also #includes this header at its
|
||||||
// #includes this header at its tail -- AFTER the jlpGenericTile*
|
// tail -- AFTER the jlpGenericTile* prototypes the portData == NULL fallbacks
|
||||||
// prototypes the portData == NULL fallbacks call. Do not include this
|
// call. Do not include this header directly; it comes with port.h.
|
||||||
// header directly; it comes with port.h on the Amiga.
|
|
||||||
#ifndef JOEY_X68K_TILE_H
|
#ifndef JOEY_X68K_TILE_H
|
||||||
#define JOEY_X68K_TILE_H
|
#define JOEY_X68K_TILE_H
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
// final tally; exits nonzero if any check failed, so it doubles as a CI gate.
|
// final tally; exits nonzero if any check failed, so it doubles as a CI gate.
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "joey/serial.h"
|
#include "joey/serial.h"
|
||||||
|
|
@ -27,6 +28,10 @@ static uint16_t gFifoTail;
|
||||||
static bool gMockOpen;
|
static bool gMockOpen;
|
||||||
static jlSerialConfigT gMockLastCfg; // config the core handed the HAL
|
static jlSerialConfigT gMockLastCfg; // config the core handed the HAL
|
||||||
static bool gMockOpenResult = true; // test can force open failure
|
static bool gMockOpenResult = true; // test can force open failure
|
||||||
|
static bool gMockRts = true; // last RTS level the core handed the HAL
|
||||||
|
static uint16_t gMockRtsCalls = 0u;
|
||||||
|
static bool gMockDtr = true; // last DTR level the core handed the HAL
|
||||||
|
static uint16_t gMockDtrCalls = 0u;
|
||||||
|
|
||||||
|
|
||||||
// ----- Mock HAL (satisfies the jlpSerial* dispatch under -DJL_HAS_SERIAL_*) -----
|
// ----- Mock HAL (satisfies the jlpSerial* dispatch under -DJL_HAS_SERIAL_*) -----
|
||||||
|
|
@ -72,6 +77,18 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
gMockDtr = on;
|
||||||
|
gMockDtrCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialSetRts(bool on) {
|
||||||
|
gMockRts = on;
|
||||||
|
gMockRtsCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
|
|
||||||
|
|
@ -103,6 +120,19 @@ static void check(const char *name, bool ok) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Registered before the first jlSerialOpen, so it runs after the serial add-on's
|
||||||
|
// own atexit handler (last registered, first run): the port main leaves open
|
||||||
|
// must be closed by then.
|
||||||
|
static void checkClosedAtExit(void) {
|
||||||
|
if (gMockOpen) {
|
||||||
|
printf(" FAIL atexit: a port left open was not closed\n");
|
||||||
|
fflush(stdout);
|
||||||
|
_Exit(1);
|
||||||
|
}
|
||||||
|
printf(" ok atexit: a port left open was closed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static jlSerialConfigT makeConfig(uint32_t baud, uint8_t dataBits, uint8_t stopBits) {
|
static jlSerialConfigT makeConfig(uint32_t baud, uint8_t dataBits, uint8_t stopBits) {
|
||||||
jlSerialConfigT cfg;
|
jlSerialConfigT cfg;
|
||||||
|
|
||||||
|
|
@ -122,6 +152,7 @@ int main(void) {
|
||||||
int16_t b;
|
int16_t b;
|
||||||
|
|
||||||
printf("serial-host: core gate/normalize/loopback\n");
|
printf("serial-host: core gate/normalize/loopback\n");
|
||||||
|
atexit(checkClosedAtExit);
|
||||||
|
|
||||||
// 1. Every call is inert before a successful open.
|
// 1. Every call is inert before a successful open.
|
||||||
check("closed: available == 0", jlSerialAvailable() == 0u);
|
check("closed: available == 0", jlSerialAvailable() == 0u);
|
||||||
|
|
@ -129,6 +160,10 @@ int main(void) {
|
||||||
check("closed: write == 0", jlSerialWrite((const uint8_t *)"x", 1u) == 0u);
|
check("closed: write == 0", jlSerialWrite((const uint8_t *)"x", 1u) == 0u);
|
||||||
check("closed: readByte == -1", jlSerialReadByte() == -1);
|
check("closed: readByte == -1", jlSerialReadByte() == -1);
|
||||||
check("closed: writeByte == false", jlSerialWriteByte('x') == false);
|
check("closed: writeByte == false", jlSerialWriteByte('x') == false);
|
||||||
|
jlSerialSetRts(false);
|
||||||
|
check("closed: setRts never reaches the HAL", gMockRtsCalls == 0u);
|
||||||
|
jlSerialSetDtr(false);
|
||||||
|
check("closed: setDtr never reaches the HAL", gMockDtrCalls == 0u);
|
||||||
|
|
||||||
// 2. NULL config and out-of-range framing are rejected, and a rejected
|
// 2. NULL config and out-of-range framing are rejected, and a rejected
|
||||||
// open must leave the port closed (a later valid open still works).
|
// open must leave the port closed (a later valid open still works).
|
||||||
|
|
@ -144,6 +179,15 @@ int main(void) {
|
||||||
// 3. Zero fields normalize to library defaults before reaching the HAL.
|
// 3. Zero fields normalize to library defaults before reaching the HAL.
|
||||||
cfg = makeConfig(0u, 0u, 0u);
|
cfg = makeConfig(0u, 0u, 0u);
|
||||||
check("open: zero-field config accepted", jlSerialOpen(JL_SERIAL_DEFAULT, &cfg) == true);
|
check("open: zero-field config accepted", jlSerialOpen(JL_SERIAL_DEFAULT, &cfg) == true);
|
||||||
|
jlSerialSetRts(false);
|
||||||
|
check("setRts: false reaches the HAL", gMockRtsCalls == 1u && !gMockRts);
|
||||||
|
jlSerialSetRts(true);
|
||||||
|
check("setRts: true reaches the HAL", gMockRtsCalls == 2u && gMockRts);
|
||||||
|
jlSerialSetDtr(false);
|
||||||
|
check("setDtr: false reaches the HAL", gMockDtrCalls == 1u && !gMockDtr);
|
||||||
|
jlSerialSetDtr(true);
|
||||||
|
check("setDtr: true reaches the HAL", gMockDtrCalls == 2u && gMockDtr);
|
||||||
|
check("setDtr: RTS untouched", gMockRtsCalls == 2u && gMockRts);
|
||||||
check("normalize: baud -> 9600", gMockLastCfg.baud == 9600u);
|
check("normalize: baud -> 9600", gMockLastCfg.baud == 9600u);
|
||||||
check("normalize: dataBits -> 8", gMockLastCfg.dataBits == 8u);
|
check("normalize: dataBits -> 8", gMockLastCfg.dataBits == 8u);
|
||||||
check("normalize: stopBits -> 1", gMockLastCfg.stopBits == 1u);
|
check("normalize: stopBits -> 1", gMockLastCfg.stopBits == 1u);
|
||||||
|
|
@ -172,8 +216,14 @@ int main(void) {
|
||||||
jlSerialFlush();
|
jlSerialFlush();
|
||||||
check("flush: available == 0", jlSerialAvailable() == 0u);
|
check("flush: available == 0", jlSerialAvailable() == 0u);
|
||||||
|
|
||||||
// 8. Close re-gates the API; a fresh open works afterward.
|
// 8. Close re-gates the API; a fresh open works afterward. A hang-up
|
||||||
|
// (DTR dropped before the close) is the HAL's to keep: the core must
|
||||||
|
// not touch DTR on the way out.
|
||||||
|
jlSerialSetDtr(false);
|
||||||
jlSerialClose();
|
jlSerialClose();
|
||||||
|
check("close: core leaves DTR alone", gMockDtrCalls == 3u && !gMockDtr);
|
||||||
|
jlSerialSetDtr(true);
|
||||||
|
check("closed again: setDtr never reaches the HAL", gMockDtrCalls == 3u && !gMockDtr);
|
||||||
check("closed again: write == 0", jlSerialWrite((const uint8_t *)"x", 1u) == 0u);
|
check("closed again: write == 0", jlSerialWrite((const uint8_t *)"x", 1u) == 0u);
|
||||||
cfg = makeConfig(2400u, 8u, 1u);
|
cfg = makeConfig(2400u, 8u, 1u);
|
||||||
check("reopen after close", jlSerialOpen(JL_SERIAL_PRINTER, &cfg) == true);
|
check("reopen after close", jlSerialOpen(JL_SERIAL_PRINTER, &cfg) == true);
|
||||||
|
|
@ -187,6 +237,10 @@ int main(void) {
|
||||||
check("open: closed after HAL failure", jlSerialWriteByte('x') == false);
|
check("open: closed after HAL failure", jlSerialWriteByte('x') == false);
|
||||||
gMockOpenResult = true;
|
gMockOpenResult = true;
|
||||||
|
|
||||||
|
// 10. A port left open is closed at exit (checked by checkClosedAtExit).
|
||||||
|
cfg = makeConfig(9600u, 8u, 1u);
|
||||||
|
check("left open for the exit check", jlSerialOpen(JL_SERIAL_DEFAULT, &cfg) == true);
|
||||||
|
|
||||||
printf("serial-host: %d passed, %d failed\n", gPass, gFail);
|
printf("serial-host: %d passed, %d failed\n", gPass, gFail);
|
||||||
return (gFail == 0) ? 0 : 1;
|
return (gFail == 0) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export AMIGA_AR='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/m68k-ami
|
||||||
export AMIGA_LD='/home/scott/claude/joeylib/toolchains/opt/amigaGcc/bin/m68k-amigaos-ld'
|
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_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_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 CADIUS='/home/scott/claude/joeylib/toolchains/opt/cadius/bin/cadius'
|
||||||
export DOS_CWSDPMI='/home/scott/claude/joeylib/toolchains/opt/cwsdpmi/bin'
|
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_CWSDSTUB='/home/scott/claude/joeylib/toolchains/opt/cwsdpmi/bin/CWSDSTUB.EXE'
|
||||||
|
|
|
||||||
138
tools/adfTool.py
Executable file
138
tools/adfTool.py
Executable file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""adfTool.py - read files out of an Amiga OFS/FFS disk image (.adf).
|
||||||
|
|
||||||
|
adfTool.py list IMAGE every file, one path per line
|
||||||
|
adfTool.py extract IMAGE PATH OUT one file (PATH like Devs/serial.device)
|
||||||
|
adfTool.py extractall IMAGE OUTDIR every file, keeping the drawer tree
|
||||||
|
|
||||||
|
Read-only. Used by the installer's `import` (a Workbench disk image is how
|
||||||
|
most people own AmigaOS files such as Devs/serial.device) and by the Amiga
|
||||||
|
gates. Handles DD (880 KB) and HD (1.76 MB) images; names match case-
|
||||||
|
insensitively, as AmigaDOS does.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
BLOCK_SIZE = 512
|
||||||
|
HASH_SLOTS = 72 # hash-table entries in a 512-byte header block
|
||||||
|
TYPE_HEADER = 2 # primary type of a root / drawer / file header
|
||||||
|
SEC_TYPE_ROOT = 1
|
||||||
|
SEC_TYPE_USERDIR = 2
|
||||||
|
SEC_TYPE_FILE = -3
|
||||||
|
OFS_DATA_HEADER = 24 # OFS data blocks start with a 24-byte header
|
||||||
|
|
||||||
|
|
||||||
|
class AdfImage:
|
||||||
|
def __init__(self, path):
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
self.image = f.read()
|
||||||
|
if len(self.image) % BLOCK_SIZE != 0 or self.image[0:3] != b"DOS":
|
||||||
|
raise ValueError("%s is not an AmigaDOS disk image" % path)
|
||||||
|
self.isFfs = (self.image[3] & 1) != 0
|
||||||
|
self.rootBlock = (len(self.image) // BLOCK_SIZE) // 2
|
||||||
|
|
||||||
|
def block(self, number):
|
||||||
|
return self.image[number * BLOCK_SIZE:(number + 1) * BLOCK_SIZE]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def long(data, offset):
|
||||||
|
# Negative offsets count from the end of the block, as the
|
||||||
|
# AmigaDOS structure definitions do.
|
||||||
|
if offset < 0:
|
||||||
|
offset += BLOCK_SIZE
|
||||||
|
return struct.unpack(">l", data[offset:offset + 4])[0]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def name(data):
|
||||||
|
length = data[BLOCK_SIZE - 80]
|
||||||
|
return data[BLOCK_SIZE - 79:BLOCK_SIZE - 79 + length].decode("latin-1")
|
||||||
|
|
||||||
|
def entries(self, dirBlock):
|
||||||
|
# (name, header block, secondary type) for everything in a drawer.
|
||||||
|
data = self.block(dirBlock)
|
||||||
|
found = []
|
||||||
|
for slot in range(HASH_SLOTS):
|
||||||
|
number = self.long(data, 24 + 4 * slot)
|
||||||
|
while number:
|
||||||
|
header = self.block(number)
|
||||||
|
if self.long(header, 0) == TYPE_HEADER:
|
||||||
|
found.append((self.name(header), number, self.long(header, -4)))
|
||||||
|
number = self.long(header, -16)
|
||||||
|
return sorted(found, key=lambda e: e[0].upper())
|
||||||
|
|
||||||
|
def walk(self, dirBlock=None, prefix=""):
|
||||||
|
# Yield (path, header block) for every file under a drawer.
|
||||||
|
if dirBlock is None:
|
||||||
|
dirBlock = self.rootBlock
|
||||||
|
for entryName, number, secType in self.entries(dirBlock):
|
||||||
|
path = prefix + entryName
|
||||||
|
if secType == SEC_TYPE_USERDIR:
|
||||||
|
yield from self.walk(number, path + "/")
|
||||||
|
elif secType == SEC_TYPE_FILE:
|
||||||
|
yield path, number
|
||||||
|
|
||||||
|
def find(self, path):
|
||||||
|
current = self.rootBlock
|
||||||
|
for part in path.split("/"):
|
||||||
|
match = [n for entryName, n, _ in self.entries(current) if entryName.upper() == part.upper()]
|
||||||
|
if not match:
|
||||||
|
raise KeyError("not found in image: %s" % path)
|
||||||
|
current = match[0]
|
||||||
|
return current
|
||||||
|
|
||||||
|
def read(self, headerBlock):
|
||||||
|
header = self.block(headerBlock)
|
||||||
|
size = self.long(header, -188)
|
||||||
|
data = bytearray()
|
||||||
|
chunk = header
|
||||||
|
while True:
|
||||||
|
count = self.long(chunk, 8)
|
||||||
|
# Data-block pointers fill the table from the END (slot 71 first).
|
||||||
|
pointers = [self.long(chunk, 24 + 4 * i) for i in range(HASH_SLOTS)][::-1][:count]
|
||||||
|
for pointer in pointers:
|
||||||
|
blockData = self.block(pointer)
|
||||||
|
if self.isFfs:
|
||||||
|
data += blockData
|
||||||
|
else:
|
||||||
|
data += blockData[OFS_DATA_HEADER:OFS_DATA_HEADER + self.long(blockData, 12)]
|
||||||
|
extension = self.long(chunk, -8)
|
||||||
|
if not extension:
|
||||||
|
break
|
||||||
|
chunk = self.block(extension)
|
||||||
|
return bytes(data[:size])
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 3 or argv[1] not in ("list", "extract", "extractall"):
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
try:
|
||||||
|
image = AdfImage(argv[2])
|
||||||
|
if argv[1] == "list":
|
||||||
|
for path, _ in image.walk():
|
||||||
|
print(path)
|
||||||
|
elif argv[1] == "extract":
|
||||||
|
if len(argv) != 5:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
with open(argv[4], "wb") as f:
|
||||||
|
f.write(image.read(image.find(argv[3])))
|
||||||
|
else:
|
||||||
|
if len(argv) != 4:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
for path, number in image.walk():
|
||||||
|
out = os.path.join(argv[3], *path.split("/"))
|
||||||
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
||||||
|
with open(out, "wb") as f:
|
||||||
|
f.write(image.read(number))
|
||||||
|
except (OSError, ValueError, KeyError) as error:
|
||||||
|
sys.stderr.write("adfTool.py: %s\n" % error)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
# Each target's runtime can use the baked bytes directly:
|
# Each target's runtime can use the baked bytes directly:
|
||||||
# * Tile banks (.tbk) are per-target planar (or chunky for DOS/IIgs).
|
# * Tile banks (.tbk) are per-target planar (or chunky for DOS/IIgs).
|
||||||
# The Phase 11 walker reads sprite tile data as chunky 4bpp, but
|
# The Phase 11 walker reads sprite tile data as chunky 4bpp, but
|
||||||
# tile-paste is platform-private -- Amiga is plane-major, ST is
|
# tile-paste is platform-private -- Amiga and X68000 are plane-major,
|
||||||
# row-major-with-planes-per-row, DOS / IIgs are chunky 4bpp.
|
# ST is row-major-with-planes-per-row, DOS / IIgs are chunky 4bpp.
|
||||||
# * Sprite cels (.spr) are cross-target chunky 4bpp. The sprite walker
|
# * Sprite cels (.spr) are cross-target chunky 4bpp. The sprite walker
|
||||||
# reads chunky and does c2p inline at draw time, so the same blob
|
# reads chunky and does c2p inline at draw time, so the same blob
|
||||||
# works on every platform.
|
# works on every platform.
|
||||||
|
|
@ -27,7 +27,8 @@
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# offset bytes field
|
# offset bytes field
|
||||||
# 0 4 magic "JTB1"
|
# 0 4 magic "JTB1"
|
||||||
# 4 1 target (1=amiga, 2=atarist, 3=dos, 4=iigs)
|
# 4 1 target (1=amiga, 2=atarist, 3=dos, 4=iigs,
|
||||||
|
# 5=x68000)
|
||||||
# 5 1 hasPalette (0 or 1)
|
# 5 1 hasPalette (0 or 1)
|
||||||
# 6 2 tileCount (LE16)
|
# 6 2 tileCount (LE16)
|
||||||
# 8 4 reserved (zero)
|
# 8 4 reserved (zero)
|
||||||
|
|
@ -38,6 +39,8 @@
|
||||||
# [r0_p0 r0_p1 r0_p2 r0_p3][r1_p0..]
|
# [r0_p0 r0_p1 r0_p2 r0_p3][r1_p0..]
|
||||||
# dos : chunky 4bpp [r0_b0..3][r1_b0..3]...
|
# dos : chunky 4bpp [r0_b0..3][r1_b0..3]...
|
||||||
# iigs : chunky 4bpp (same as dos)
|
# iigs : chunky 4bpp (same as dos)
|
||||||
|
# x68000 : plane-major (same as amiga; its own byte so
|
||||||
|
# each bank names the build it was made for)
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# .spr format
|
# .spr format
|
||||||
|
|
@ -70,12 +73,14 @@ TARGET_AMIGA = 1
|
||||||
TARGET_ATARIST = 2
|
TARGET_ATARIST = 2
|
||||||
TARGET_DOS = 3
|
TARGET_DOS = 3
|
||||||
TARGET_IIGS = 4
|
TARGET_IIGS = 4
|
||||||
|
TARGET_X68000 = 5
|
||||||
|
|
||||||
TARGET_NAMES = {
|
TARGET_NAMES = {
|
||||||
"amiga": TARGET_AMIGA,
|
"amiga": TARGET_AMIGA,
|
||||||
"atarist": TARGET_ATARIST,
|
"atarist": TARGET_ATARIST,
|
||||||
"dos": TARGET_DOS,
|
"dos": TARGET_DOS,
|
||||||
"iigs": TARGET_IIGS,
|
"iigs": TARGET_IIGS,
|
||||||
|
"x68000": TARGET_X68000,
|
||||||
}
|
}
|
||||||
|
|
||||||
TILE_PX = 8 # 8x8 tile
|
TILE_PX = 8 # 8x8 tile
|
||||||
|
|
@ -134,7 +139,7 @@ def encodeTileChunky(grid, ox, oy):
|
||||||
|
|
||||||
|
|
||||||
def encodeTilePlaneMajor(grid, ox, oy):
|
def encodeTilePlaneMajor(grid, ox, oy):
|
||||||
# Amiga: 4 planes * 8 rows, one byte per row per plane.
|
# Amiga and X68000: 4 planes * 8 rows, one byte per row per plane.
|
||||||
# Plane 0 bytes 0..7, plane 1 bytes 8..15, etc.
|
# Plane 0 bytes 0..7, plane 1 bytes 8..15, etc.
|
||||||
out = bytearray()
|
out = bytearray()
|
||||||
for k in range(4):
|
for k in range(4):
|
||||||
|
|
@ -167,6 +172,7 @@ TILE_ENCODERS = {
|
||||||
TARGET_ATARIST: encodeTileRowPlanesPerRow,
|
TARGET_ATARIST: encodeTileRowPlanesPerRow,
|
||||||
TARGET_DOS: encodeTileChunky,
|
TARGET_DOS: encodeTileChunky,
|
||||||
TARGET_IIGS: encodeTileChunky,
|
TARGET_IIGS: encodeTileChunky,
|
||||||
|
TARGET_X68000: encodeTilePlaneMajor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue