Mouse added to X68000. Many serial fixes.
This commit is contained in:
parent
e1cae64e7b
commit
30c61609e7
26 changed files with 1210 additions and 88 deletions
5
cfg/default.cfg
Normal file
5
cfg/default.cfg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="default" />
|
||||
</mameconfig>
|
||||
19
cfg/x68000.cfg
Normal file
19
cfg/x68000.cfg
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
|
||||
<mameconfig version="10">
|
||||
<system name="x68000">
|
||||
<image_directories>
|
||||
<device instance="floppydisk1" directory="/tmp/joey-x68gold.mQ0r7E/" />
|
||||
<device instance="floppydisk2" directory="/tmp/joey-x68gold.mQ0r7E/" />
|
||||
<device instance="floppydisk3" directory="" />
|
||||
<device instance="floppydisk4" directory="" />
|
||||
<device instance="sasihd" directory="" />
|
||||
</image_directories>
|
||||
<input>
|
||||
<keyboard tag=":keyboard:x68k" enabled="1" />
|
||||
</input>
|
||||
<video>
|
||||
<target index="0" view="Disk Drive and Keyboard LEDs" />
|
||||
</video>
|
||||
</system>
|
||||
</mameconfig>
|
||||
|
|
@ -7,10 +7,12 @@
|
|||
// whatever you type -- letters, digits, shifted punctuation -- appears
|
||||
// there, Backspace deletes, Return clears. It doubles as the
|
||||
// acceptance test for the typed-character queue ("192.168.1.10:6510"
|
||||
// must be typeable on every port). The bottom scanline additionally
|
||||
// encodes every received character as raw pixel nibbles (2 pixels =
|
||||
// 1 byte) behind an A5A5 sentinel so emulator harnesses can verify
|
||||
// the exact bytes from a framebuffer dump.
|
||||
// must be typeable on every port). The bottom two scanlines
|
||||
// additionally encode verification data as raw pixel nibbles behind
|
||||
// sentinels: row 198 carries every received character (A5A5 sentinel,
|
||||
// 2 pixels = 1 byte) and row 199 the live mouse state (5A5A sentinel,
|
||||
// present/button flags plus position), so emulator harnesses can
|
||||
// verify the exact bytes from a framebuffer dump.
|
||||
//
|
||||
// The render loop only redraws cells whose target lit state changed
|
||||
// since last frame -- so on idle frames, the only work is the cursor
|
||||
|
|
@ -58,26 +60,52 @@
|
|||
// nibble) pixel pairs. An emulator harness reads this row back from
|
||||
// the framebuffer to prove each typed byte arrived.
|
||||
#define VERIFY_Y 198
|
||||
#define VERIFY_SENTINEL_0 0xA
|
||||
#define VERIFY_SENTINEL_1 0x5
|
||||
#define VERIFY_SENTINEL 0xA5A5
|
||||
#define VERIFY_COUNT_X 4
|
||||
#define VERIFY_CHARS_X 8
|
||||
#define MAX_RECEIVED 150
|
||||
|
||||
// Every field on both verification rows is stamped high nibble first,
|
||||
// one nibble per pixel: a byte is two pixels wide, a 16-bit value four.
|
||||
#define VERIFY_BYTE_NIBBLES 2
|
||||
#define VERIFY_WORD_NIBBLES 4
|
||||
|
||||
// Mouse verification scanline (the row below the typed-character one):
|
||||
// its own 5A5A sentinel, then present/left/right/middle as one nibble
|
||||
// each, then jlMouseX and jlMouseY as four nibbles each.
|
||||
// Re-stamped only when the reported state changes.
|
||||
#define VERIFY_MOUSE_Y 199
|
||||
#define VERIFY_MOUSE_SENTINEL 0x5A5A
|
||||
#define VERIFY_MOUSE_FLAGS_X 4
|
||||
#define VERIFY_MOUSE_XPOS_X 8
|
||||
#define VERIFY_MOUSE_YPOS_X 12
|
||||
|
||||
// The flags field packed as one nibble per predicate, in the order the
|
||||
// row stamps them. This packing is the ONLY definition of that layout:
|
||||
// it is both what gets drawn and what the change check compares.
|
||||
#define MOUSE_FLAG_PRESENT 0x1000
|
||||
#define MOUSE_FLAG_LEFT 0x0100
|
||||
#define MOUSE_FLAG_RIGHT 0x0010
|
||||
#define MOUSE_FLAG_MIDDLE 0x0001
|
||||
|
||||
static void buildPalette(jlSurfaceT *screen);
|
||||
static void cellAtPoint(int16_t px, int16_t py, int16_t *outCol, int16_t *outRow);
|
||||
static bool cellTargetLit(int16_t col, int16_t row, int16_t cursorCol, int16_t cursorRow);
|
||||
static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit);
|
||||
static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y);
|
||||
static void drawMouseVerifyRow(jlSurfaceT *screen);
|
||||
static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *text, uint8_t color);
|
||||
static void drawVerifyRow(jlSurfaceT *screen);
|
||||
static int glyphIdx(char c);
|
||||
static void initialPaint(jlSurfaceT *screen);
|
||||
static void logTypedHistory(void);
|
||||
static uint16_t mouseFlags(void);
|
||||
static void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
||||
static void processTypedChars(jlSurfaceT *screen);
|
||||
static void redrawTextStrip(jlSurfaceT *screen);
|
||||
static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint16_t value, int16_t nibbles);
|
||||
static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
||||
static void updateMouseVerifyRow(jlSurfaceT *screen);
|
||||
|
||||
// Keys laid out row-by-row. KEY_NONE cells stay blank. Shape roughly
|
||||
// resembles a real keyboard (top number row, then QWERTY rows, then a
|
||||
|
|
@ -97,6 +125,12 @@ static int16_t gLastCursorY = -100;
|
|||
static int16_t gLastCursorCol = CELL_NONE;
|
||||
static int16_t gLastCursorRow = CELL_NONE;
|
||||
|
||||
// Last state stamped into the mouse verification row; updateMouseVerifyRow
|
||||
// only redraws when this tuple changes, so idle frames stay draw-free.
|
||||
static int16_t gLastMouseRowX = -1;
|
||||
static int16_t gLastMouseRowY = -1;
|
||||
static uint16_t gLastMouseRowFlags = 0xFFFF;
|
||||
|
||||
// Typed-text line state plus the full received-character history for
|
||||
// the verification row.
|
||||
static char gLine[MAX_LINE + 1];
|
||||
|
|
@ -258,8 +292,33 @@ static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit) {
|
|||
}
|
||||
|
||||
|
||||
// The block is clipped against the verification scanlines rather than
|
||||
// drawn over them: emulator harnesses sample those rows between
|
||||
// frames, so even a stamp-then-repair within one loop iteration is a
|
||||
// visible corruption window to them.
|
||||
static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y) {
|
||||
jlFillRect(screen, x, y, CURSOR_W, CURSOR_H, COLOR_CURSOR);
|
||||
int16_t h;
|
||||
|
||||
h = CURSOR_H;
|
||||
if ((int16_t)(y + h) > VERIFY_Y) {
|
||||
h = (int16_t)(VERIFY_Y - y);
|
||||
}
|
||||
if (h <= 0) {
|
||||
return;
|
||||
}
|
||||
jlFillRect(screen, x, y, CURSOR_W, h, COLOR_CURSOR);
|
||||
}
|
||||
|
||||
|
||||
// Stamp the mouse verification scanline: sentinel, present/button
|
||||
// flags, then the pointer position as nibble pixels. An emulator
|
||||
// harness reads this row back from the framebuffer to prove mouse
|
||||
// motion and button state crossed the HAL (see verify-x68000-mouse.sh).
|
||||
static void drawMouseVerifyRow(jlSurfaceT *screen) {
|
||||
stampNibbles(screen, 0, VERIFY_MOUSE_Y, VERIFY_MOUSE_SENTINEL, VERIFY_WORD_NIBBLES);
|
||||
stampNibbles(screen, VERIFY_MOUSE_FLAGS_X, VERIFY_MOUSE_Y, mouseFlags(), VERIFY_WORD_NIBBLES);
|
||||
stampNibbles(screen, VERIFY_MOUSE_XPOS_X, VERIFY_MOUSE_Y, (uint16_t)jlMouseX(), VERIFY_WORD_NIBBLES);
|
||||
stampNibbles(screen, VERIFY_MOUSE_YPOS_X, VERIFY_MOUSE_Y, (uint16_t)jlMouseY(), VERIFY_WORD_NIBBLES);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -296,19 +355,15 @@ static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *t
|
|||
// to redraw whole.
|
||||
static void drawVerifyRow(jlSurfaceT *screen) {
|
||||
int16_t i;
|
||||
uint8_t ch;
|
||||
|
||||
jlDrawPixel(screen, 0, VERIFY_Y, VERIFY_SENTINEL_0);
|
||||
jlDrawPixel(screen, 1, VERIFY_Y, VERIFY_SENTINEL_1);
|
||||
jlDrawPixel(screen, 2, VERIFY_Y, VERIFY_SENTINEL_0);
|
||||
jlDrawPixel(screen, 3, VERIFY_Y, VERIFY_SENTINEL_1);
|
||||
jlDrawPixel(screen, VERIFY_COUNT_X, VERIFY_Y, (uint8_t)((gReceivedCount >> 4) & 0x0F));
|
||||
jlDrawPixel(screen, VERIFY_COUNT_X + 1, VERIFY_Y, (uint8_t)(gReceivedCount & 0x0F));
|
||||
stampNibbles(screen, 0, VERIFY_Y, VERIFY_SENTINEL, VERIFY_WORD_NIBBLES);
|
||||
stampNibbles(screen, VERIFY_COUNT_X, VERIFY_Y, (uint16_t)gReceivedCount, VERIFY_BYTE_NIBBLES);
|
||||
for (i = 0; i < gReceivedCount; i++) {
|
||||
ch = gReceived[i];
|
||||
jlDrawPixel(screen, (int16_t)(VERIFY_CHARS_X + 2 * i), VERIFY_Y, (uint8_t)((ch >> 4) & 0x0F));
|
||||
jlDrawPixel(screen, (int16_t)(VERIFY_CHARS_X + 2 * i + 1), VERIFY_Y, (uint8_t)(ch & 0x0F));
|
||||
stampNibbles(screen, (int16_t)(VERIFY_CHARS_X + VERIFY_BYTE_NIBBLES * i), VERIFY_Y, gReceived[i], VERIFY_BYTE_NIBBLES);
|
||||
}
|
||||
// The strip repaint that called us blanked the mouse row too, so
|
||||
// restore it in the same pass.
|
||||
drawMouseVerifyRow(screen);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -376,6 +431,17 @@ static void logTypedHistory(void) {
|
|||
}
|
||||
|
||||
|
||||
// The mouse predicates packed one nibble per flag, in the order
|
||||
// drawMouseVerifyRow stamps them. Sampled once per use so the drawn
|
||||
// row and updateMouseVerifyRow's change check can never disagree.
|
||||
static uint16_t mouseFlags(void) {
|
||||
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) |
|
||||
(jlMouseDown(MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) |
|
||||
(jlMouseDown(MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0) |
|
||||
(jlMouseDown(MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0));
|
||||
}
|
||||
|
||||
|
||||
static void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow) {
|
||||
int16_t col;
|
||||
int16_t row;
|
||||
|
|
@ -450,6 +516,20 @@ static void redrawTextStrip(jlSurfaceT *screen) {
|
|||
}
|
||||
|
||||
|
||||
// Stamp a value into consecutive pixels as nibbles, high nibble
|
||||
// first -- the encoding both verification rows and every emulator
|
||||
// harness that decodes them share.
|
||||
static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint16_t value, int16_t nibbles) {
|
||||
int16_t i;
|
||||
int16_t shift;
|
||||
|
||||
for (i = 0; i < nibbles; i++) {
|
||||
shift = (int16_t)(4 * (nibbles - 1 - i));
|
||||
jlDrawPixel(screen, (int16_t)(x + i), y, (uint8_t)((value >> shift) & 0x0F));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Erase the previous cursor (by redrawing the cell that held it) and
|
||||
// stamp the new cursor at the current mouse position. Both rects are
|
||||
// presented; if the cursor stayed inside the same cell only one rect
|
||||
|
|
@ -487,6 +567,27 @@ static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRo
|
|||
}
|
||||
|
||||
|
||||
// Re-stamp the mouse verification row only when the reported state
|
||||
// changed since the last stamp, so idle frames stay draw-free.
|
||||
static void updateMouseVerifyRow(jlSurfaceT *screen) {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t flags;
|
||||
|
||||
x = jlMouseX();
|
||||
y = jlMouseY();
|
||||
flags = mouseFlags();
|
||||
if (x == gLastMouseRowX && y == gLastMouseRowY && flags == gLastMouseRowFlags) {
|
||||
return;
|
||||
}
|
||||
drawMouseVerifyRow(screen);
|
||||
jlStagePresent();
|
||||
gLastMouseRowX = x;
|
||||
gLastMouseRowY = y;
|
||||
gLastMouseRowFlags = flags;
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
jlConfigT config;
|
||||
jlSurfaceT *screen;
|
||||
|
|
@ -521,6 +622,7 @@ int main(void) {
|
|||
cellAtPoint(jlMouseX(), jlMouseY(), &cursorCol, &cursorRow);
|
||||
presentChangedCells(screen, cursorCol, cursorRow);
|
||||
updateCursor(screen, cursorCol, cursorRow);
|
||||
updateMouseVerifyRow(screen);
|
||||
processTypedChars(screen);
|
||||
}
|
||||
|
||||
|
|
|
|||
118
examples/sccprobe/sccprobe.c
Normal file
118
examples/sccprobe/sccprobe.c
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Does opening one built-in SCC port survive the OTHER one?
|
||||
//
|
||||
// The Zilog 8530 in a IIgs carries both built-in ports: channel A is the
|
||||
// printer port (where AppleTalk lives), channel B is the modem port. WR9's
|
||||
// reset command is chip-wide, so an open that issues the force-hardware-reset
|
||||
// (0xC0) takes the other channel down with it -- registers, FIFOs, baud
|
||||
// generator and, decisively for this probe, the transmit enable in WR5.
|
||||
//
|
||||
// So: bring channel A up through the real HAL, hand it back (the IIgs close
|
||||
// deliberately leaves the chip configured), then open channel B -- the
|
||||
// operation under test -- and finally poke a byte STRAIGHT into channel A's
|
||||
// data register, bypassing the library. If channel A survived, its transmitter
|
||||
// is still enabled and the byte reaches the host peer. If the open reset the
|
||||
// whole chip, WR5 went to zero, the transmitter is off, and nothing arrives.
|
||||
//
|
||||
// Run headless by scripts/verify-iigs-serial.sh, which reads the bytes off a
|
||||
// socket and the progress flags out of the SHR framebuffer.
|
||||
|
||||
#include <joey/serial.h>
|
||||
|
||||
// SHR framebuffer, used purely as a mailbox the emulator harness can read: the
|
||||
// probe never calls jlInit, so nothing else is drawing here.
|
||||
#define SHR_BASE ((volatile uint8_t *)0x00E12000L)
|
||||
#define MB_SIGNATURE_0 0
|
||||
#define MB_SIGNATURE_1 1
|
||||
#define MB_PRINTER_OPEN 2
|
||||
#define MB_PRINTER_WROTE 3
|
||||
#define MB_MODEM_OPEN 4
|
||||
#define MB_DIRECT_WROTE 5
|
||||
#define MB_SIG_VALUE_0 0xA5u
|
||||
#define MB_SIG_VALUE_1 0x5Au
|
||||
|
||||
// Channel A (printer port) registers -- the channel this probe checks for
|
||||
// survival. Deliberately spelled out here rather than shared with the HAL: the
|
||||
// point is to look at the hardware independently of the code under test.
|
||||
#define SCC_A_CTRL ((volatile uint8_t *)0x00C039L)
|
||||
#define SCC_A_DATA ((volatile uint8_t *)0x00C03BL)
|
||||
#define SCC_RR0_TX_EMPTY 0x04u
|
||||
|
||||
// Bounded so a dead transmitter reports "nothing arrived" instead of hanging
|
||||
// the machine and stalling the gate.
|
||||
#define TX_SPIN_LIMIT 200000ul
|
||||
|
||||
static void mailbox(uint16_t slot, uint8_t value);
|
||||
static bool pokeChannelA(uint8_t byte);
|
||||
|
||||
|
||||
static void mailbox(uint16_t slot, uint8_t value) {
|
||||
SHR_BASE[slot] = value;
|
||||
}
|
||||
|
||||
|
||||
// Write one byte to channel A without going through the HAL. Returns false if
|
||||
// the transmitter never reports empty, which is exactly what a wiped channel
|
||||
// looks like.
|
||||
static bool pokeChannelA(uint8_t byte) {
|
||||
uint32_t spin;
|
||||
|
||||
for (spin = 0; spin < TX_SPIN_LIMIT; spin++) {
|
||||
if ((*SCC_A_CTRL & SCC_RR0_TX_EMPTY) != 0u) {
|
||||
*SCC_A_DATA = byte;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
jlSerialConfigT cfg;
|
||||
uint16_t i;
|
||||
|
||||
mailbox(MB_SIGNATURE_0, MB_SIG_VALUE_0);
|
||||
mailbox(MB_SIGNATURE_1, MB_SIG_VALUE_1);
|
||||
|
||||
cfg.baud = 9600u;
|
||||
cfg.dataBits = 8u;
|
||||
cfg.stopBits = 1u;
|
||||
cfg.parity = JL_SERIAL_PARITY_NONE;
|
||||
cfg.flow = JL_SERIAL_FLOW_NONE;
|
||||
cfg.unit = 0u;
|
||||
|
||||
// 1. Bring channel A (printer) up through the HAL and prove it transmits.
|
||||
if (!jlSerialOpen(JL_SERIAL_PRINTER, &cfg)) {
|
||||
return 1;
|
||||
}
|
||||
mailbox(MB_PRINTER_OPEN, 1u);
|
||||
if (jlSerialWrite((const uint8_t *)"A1\r\n", 4u) == 4u) {
|
||||
mailbox(MB_PRINTER_WROTE, 1u);
|
||||
}
|
||||
jlSerialFlush();
|
||||
jlSerialClose();
|
||||
|
||||
// 2. Open channel B (modem). THIS is the operation under test: with the
|
||||
// old chip-wide 0xC0 reset it also wipes channel A.
|
||||
if (!jlSerialOpen(JL_SERIAL_MODEM, &cfg)) {
|
||||
return 1;
|
||||
}
|
||||
mailbox(MB_MODEM_OPEN, 1u);
|
||||
|
||||
// 3. Channel A is untouched by the library from here on. Poke it directly:
|
||||
// the byte only leaves the chip if channel A's transmitter is still on.
|
||||
if (pokeChannelA('A')) {
|
||||
mailbox(MB_DIRECT_WROTE, 1u);
|
||||
}
|
||||
(void)pokeChannelA('2');
|
||||
(void)pokeChannelA('\r');
|
||||
(void)pokeChannelA('\n');
|
||||
|
||||
// Hold the machine still so the harness can read the mailbox, and give the
|
||||
// last byte time to clock out at 9600 baud.
|
||||
for (i = 0; i < 60000u; i++) {
|
||||
(void)*SCC_A_CTRL;
|
||||
}
|
||||
jlSerialClose();
|
||||
for (;;) {
|
||||
}
|
||||
}
|
||||
|
|
@ -144,6 +144,9 @@ bool jlKeyDown(jlKeyE key);
|
|||
bool jlKeyPressed(jlKeyE key);
|
||||
bool jlKeyReleased(jlKeyE key);
|
||||
|
||||
// True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset;
|
||||
// Amiga/ST/IIgs/X68000: the OS mouse is always wired).
|
||||
bool jlMousePresent(void);
|
||||
int16_t jlMouseX(void);
|
||||
int16_t jlMouseY(void);
|
||||
bool jlMouseDown(jlMouseButtonE button);
|
||||
|
|
|
|||
16
make/iigs.mk
16
make/iigs.mk
|
|
@ -26,7 +26,7 @@ LLVM_MC := $(LLVM816_ROOT)/tools/llvm-mos-build/bin/llvm-mc
|
|||
AR := $(LLVM816_ROOT)/tools/llvm-mos-sdk/bin/llvm-ar
|
||||
CCWRAP := $(LLVM816_ROOT)/scripts/ccRegallocFallback.sh
|
||||
RT_INC := $(LLVM816_ROOT)/runtime/include
|
||||
CFLAGS := --target=w65816 -O2 -ffreestanding -ffunction-sections
|
||||
CFLAGS := --target=w65816 -O2 -ffreestanding -ffunction-sections $(EXTRA_CFLAGS)
|
||||
INCLUDES := -I$(RT_INC) -I$(INCLUDE_DIR) -I$(INCLUDE_DIR)/joey -I$(SRC_CORE) -I$(REPO_DIR)/src/codegen
|
||||
LIBDIR := $(BUILD)/lib
|
||||
LIB := $(LIBDIR)/libjoey.a
|
||||
|
|
@ -58,6 +58,7 @@ DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
|||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
SERIAL_SRC := $(EXAMPLES)/serial/serial.c
|
||||
SERTEST_SRC := $(EXAMPLES)/sertest/sertest.c
|
||||
SCCPROBE_SRC := $(EXAMPLES)/sccprobe/sccprobe.c
|
||||
SAVE_SRC := $(EXAMPLES)/save/save.c
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||
|
|
@ -78,7 +79,7 @@ NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
|||
NTP_ASM := $(BUILD)/audio/ntpdata.s
|
||||
IIGS_MERLIN := $(REPO_DIR)/toolchains/iigs/merlin32/bin/merlin32
|
||||
|
||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-shrtail clean-iigs clean
|
||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-verify iigs-verify-all iigs-verify-save iigs-verify-serial iigs-verify-shrtail clean-iigs clean
|
||||
|
||||
# Default: compile-check the library + run the end-to-end smoke test.
|
||||
all iigs: iigs-lib iigs-clang-smoke
|
||||
|
|
@ -125,6 +126,11 @@ iigs-disk: iigs-examples $(NTP_BIN) $(STAXI_IIGS_SPC)
|
|||
iigs-verify: iigs-disk
|
||||
$(REPO_DIR)/scripts/verify-iigs.sh $(or $(VERIFY_EXAMPLE),draw)
|
||||
|
||||
# Cross-channel SCC gate: opening the modem port must leave the printer channel
|
||||
# (AppleTalk's) transmitting. Builds its own one-example disk; ~2 min.
|
||||
iigs-verify-serial: $(BINDIR)/SCCPROBE
|
||||
$(REPO_DIR)/scripts/verify-iigs-serial.sh
|
||||
|
||||
# Boot-verify every launchable example on joey.2mg (per-example thresholds).
|
||||
iigs-verify-all: iigs-disk
|
||||
$(REPO_DIR)/scripts/verify-iigs-all.sh
|
||||
|
|
@ -231,6 +237,12 @@ $(BINDIR)/SERTEST: $(SERTEST_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
|||
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SERTEST.d $(INCLUDES) -o $@ $(SERTEST_SRC) $(LIB)
|
||||
|
||||
# Cross-channel SCC check: does opening the modem port leave the printer port
|
||||
# (AppleTalk's channel) alive? Driven by scripts/verify-iigs-serial.sh.
|
||||
$(BINDIR)/SCCPROBE: $(SCCPROBE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SCCPROBE.d $(INCLUDES) -o $@ $(SCCPROBE_SRC) $(LIB)
|
||||
|
||||
$(BINDIR)/SAVE: $(SAVE_SRC) $(LIB) $(IIGS_CLANG_BUILD)
|
||||
@mkdir -p $(dir $@) $(DEP_DIR)
|
||||
$(IIGS_CLANG_BUILD) -M $(DEP_DIR)/SAVE.d $(INCLUDES) -o $@ $(SAVE_SRC) $(LIB)
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ UBER_SRC := $(EXAMPLES)/uber/uber.c
|
|||
AUDIO_SRC := $(EXAMPLES)/audio/audio.c
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
|
||||
.PHONY: all x68000 x68000-lib x68000-examples x68000-verify-serial 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
|
||||
|
||||
all x68000: x68000-lib x68000-examples
|
||||
|
||||
|
|
@ -170,6 +170,11 @@ $(BINDIR)/KEYS.X: $(KEYS_SRC) $(LIB) $(LIBXMP_AR)
|
|||
x68000-verify-serial: $(BINDIR)/SERIAL.X
|
||||
$(REPO_DIR)/scripts/verify-x68000-serial.sh
|
||||
|
||||
# Mouse gate: injected MAME mouse packets through the IOCS ROM into
|
||||
# jlMouse* state, read back from KEYS.X's verification scanline.
|
||||
x68000-verify-mouse: $(BINDIR)/KEYS.X
|
||||
$(REPO_DIR)/scripts/verify-x68000-mouse.sh
|
||||
|
||||
# Golden-hash gate against the Apple IIgs reference (~70 min: UBER on the
|
||||
# generic renderer is slow and must reach jlLogFlush before hashes exist).
|
||||
x68000-verify-golden: $(LIB) $(LIBXMP_AR)
|
||||
|
|
|
|||
BIN
nvram/x68000_0/nvram
Normal file
BIN
nvram/x68000_0/nvram
Normal file
Binary file not shown.
18
patches/mame-0.264-apple2gs-scc-clock.patch
Normal file
18
patches/mame-0.264-apple2gs-scc-clock.patch
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
--- /tmp/apple2gs.cpp.orig 2026-08-13 15:22:16.445448309 -0500
|
||||
+++ toolchains/cache/mame-mame0264/src/mame/apple/apple2gs.cpp 2026-08-13 15:22:16.497448131 -0500
|
||||
@@ -3839,8 +3839,13 @@
|
||||
ADDRESS_MAP_BANK(config, A2GS_C300_TAG).set_map(&apple2gs_state::c300bank_map).set_options(ENDIANNESS_LITTLE, 8, 32, 0x100);
|
||||
|
||||
/* serial */
|
||||
- SCC85C30(config, m_scc, A2GS_14M / 2);
|
||||
- m_scc->configure_channels(3'686'400, 3'686'400, 3'686'400, 3'686'400);
|
||||
+ // RetroNet patch: a real IIgs feeds the SCC BRG a 3.6864 MHz PCLK (the classic 115200
|
||||
+ // time-constant base); A2GS_14M/2 = 7.159 MHz made every firmware/native BRG rate ~1.94x
|
||||
+ // fast. Zeroed rxc/txc: the m_rxc>0 path in z80scc update_serial() otherwise FORCES the
|
||||
+ // receive rate to rxc/16 = 230400 regardless of the programmed BRG, which no rs232 peer
|
||||
+ // can match.
|
||||
+ SCC85C30(config, m_scc, 3'686'400);
|
||||
+ m_scc->configure_channels(0, 0, 0, 0);
|
||||
m_scc->out_int_callback().set(FUNC(apple2gs_state::scc_irq_w));
|
||||
m_scc->out_txda_callback().set("printer", FUNC(rs232_port_device::write_txd));
|
||||
m_scc->out_txdb_callback().set("modem", FUNC(rs232_port_device::write_txd));
|
||||
179
scripts/verify-iigs-serial.sh
Executable file
179
scripts/verify-iigs-serial.sh
Executable file
|
|
@ -0,0 +1,179 @@
|
|||
#!/usr/bin/env bash
|
||||
# verify-iigs-serial.sh - Cross-channel SCC gate for the Apple IIgs port.
|
||||
#
|
||||
# The 8530 carries both built-in ports on one chip: channel A is the printer
|
||||
# port (AppleTalk's), channel B is the modem port. WR9's reset command is
|
||||
# chip-wide, so an open that issues the force-hardware-reset (0xC0) wipes the
|
||||
# other channel too. This gate proves it does not.
|
||||
#
|
||||
# SCCPROBE (examples/sccprobe/sccprobe.c) brings channel A up through the real
|
||||
# HAL and sends "A1", hands it back, opens channel B -- the operation under
|
||||
# test -- and then pokes "A2" straight into channel A's data register. Both
|
||||
# strings reach the host only if channel A's transmitter is still enabled after
|
||||
# the modem-port open, so:
|
||||
#
|
||||
# A1 present, A2 present -> PASS (channel A survived)
|
||||
# A1 present, A2 missing -> FAIL (the open wiped the other channel)
|
||||
# A1 missing -> the probe never ran / printer path is broken
|
||||
#
|
||||
# MAME CONNECTS OUT to the bitbanger socket, so the peer here listens first.
|
||||
# Needs the PATCHED apple2gs (patches/mame-0.264-apple2gs-scc-clock.patch):
|
||||
# stock 0.264 feeds the SCC BRG the wrong clock and pins RX at 230400, so no
|
||||
# baud ever matches the null_modem.
|
||||
#
|
||||
# scripts/verify-iigs-serial.sh
|
||||
set -uo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
MAME="$repo/toolchains/cache/mame-mame0264/apple2gs"
|
||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
||||
PORT="${IIGS_SCC_PORT:-45231}"
|
||||
readFrame="${MAME_READ_FRAME:-9000}"
|
||||
WALL="${IIGS_SCC_WALL:-420}"
|
||||
|
||||
[ -x "$MAME" ] || { echo "verify-iigs-serial: missing patched MAME at $MAME" >&2; exit 2; }
|
||||
[ -f "$sys_disk" ] || { echo "verify-iigs-serial: missing $sys_disk" >&2; exit 2; }
|
||||
[ -f "$repo/build/iigs/bin/SCCPROBE" ] || { echo "verify-iigs-serial: build SCCPROBE first" >&2; exit 2; }
|
||||
|
||||
work=$(mktemp -d -t joeylib-iigs-scc.XXXXXX)
|
||||
peer_pid=""
|
||||
cleanup() {
|
||||
[ -n "$peer_pid" ] && kill -9 "$peer_pid" 2>/dev/null
|
||||
rm -rf "$work"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# A disk holding just the probe, so this never disturbs the normal joey.2mg.
|
||||
JOEY_DISK_EXAMPLES="SCCPROBE" bash "$repo/scripts/make-iigs-disk.sh" "$work/scc.2mg" >/dev/null 2>&1 || {
|
||||
echo "verify-iigs-serial: FAIL - could not build the probe disk" >&2
|
||||
exit 1
|
||||
}
|
||||
cp "$sys_disk" "$work/boot.po"
|
||||
|
||||
# Serial peer: listen, then log every byte the IIgs sends.
|
||||
cat > "$work/peer.py" <<'PY'
|
||||
import socket, sys, time
|
||||
|
||||
port = int(sys.argv[1])
|
||||
out = sys.argv[2]
|
||||
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(300)
|
||||
try:
|
||||
conn, _ = srv.accept()
|
||||
except socket.timeout:
|
||||
open(out, "wb").write(b"")
|
||||
sys.exit(0)
|
||||
conn.settimeout(1.0)
|
||||
buf = b""
|
||||
deadline = time.time() + 280
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
chunk = conn.recv(256)
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
if not chunk:
|
||||
break
|
||||
buf += chunk
|
||||
with open(out, "wb") as f:
|
||||
f.write(buf)
|
||||
with open(out, "wb") as f:
|
||||
f.write(buf)
|
||||
PY
|
||||
python3 "$work/peer.py" "$PORT" "$work/rx.bin" &
|
||||
peer_pid=$!
|
||||
sleep 1
|
||||
|
||||
# Finder keystroke timeline, same calibration as verify-iigs.sh: select the
|
||||
# JOEYLIB volume, Cmd-O to open it, type the program name, Cmd-O to launch.
|
||||
cat > "$work/scc.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 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 report()
|
||||
-- Probe mailbox, stamped into the SHR framebuffer (it never calls jlInit,
|
||||
-- so nothing else writes here): signature, then one flag per step.
|
||||
local base = 0xE12000
|
||||
io.write(string.format("VERIFY-IIGS-SCC frame=%d sig=%02X%02X printerOpen=%d printerWrote=%d modemOpen=%d directWrote=%d\n",
|
||||
frame,
|
||||
mem:read_u8(base), mem:read_u8(base + 1),
|
||||
mem:read_u8(base + 2), mem:read_u8(base + 3),
|
||||
mem:read_u8(base + 4), mem:read_u8(base + 5)))
|
||||
io.flush()
|
||||
end
|
||||
|
||||
local steps = {
|
||||
{3000, function() nat:post("J") end},
|
||||
{3120, function() press(key_cmd) end},
|
||||
{3126, function() nat:post("o") end},
|
||||
{3180, function() release(key_cmd) end},
|
||||
{3540, function() nat:post("SCCPROBE") end},
|
||||
{3660, function() press(key_cmd) end},
|
||||
{3666, function() nat:post("o") end},
|
||||
{3720, function() release(key_cmd) end},
|
||||
{$readFrame, function() report(); manager.machine:exit() 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
|
||||
end)
|
||||
LUA
|
||||
|
||||
cd "$work"
|
||||
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout -s KILL "$WALL" "$MAME" apple2gs \
|
||||
-rompath "$rompath" \
|
||||
-flop3 "$work/boot.po" -flop4 "$work/scc.2mg" \
|
||||
-printer null_modem -bitb "socket.127.0.0.1:$PORT" \
|
||||
-video none -sound none -nothrottle \
|
||||
-autoboot_script "$work/scc.lua" </dev/null 2>&1) || true
|
||||
|
||||
kill -9 "$peer_pid" 2>/dev/null
|
||||
peer_pid=""
|
||||
sleep 1
|
||||
|
||||
line=$(echo "$out" | grep -E '^VERIFY-IIGS-SCC ' | tail -1)
|
||||
rx=$(od -A n -c "$work/rx.bin" 2>/dev/null | tr -s ' ' | tr -d '\n')
|
||||
echo "$line"
|
||||
echo " printer-port bytes:$rx"
|
||||
|
||||
if [ -z "$line" ]; then
|
||||
echo "verify-iigs-serial: FAIL - MAME produced no report" >&2
|
||||
echo "$out" | tail -10 >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "A1" "$work/rx.bin" 2>/dev/null; then
|
||||
echo "verify-iigs-serial: FAIL - the printer port never sent 'A1'." >&2
|
||||
echo " Either SCCPROBE did not run (check the flags above) or the" >&2
|
||||
echo " channel A transmit path is broken." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "A2" "$work/rx.bin" 2>/dev/null; then
|
||||
echo "verify-iigs-serial: FAIL - 'A1' arrived but 'A2' did not." >&2
|
||||
echo " Opening the MODEM port killed the PRINTER channel: that is the" >&2
|
||||
echo " chip-wide WR9 0xC0 force-hardware-reset. Use the per-channel" >&2
|
||||
echo " reset (0x80 channel A / 0x40 channel B) in serialInitScc." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "verify-iigs-serial: PASS (printer channel still transmits after the modem port was opened)"
|
||||
|
|
@ -6,8 +6,14 @@
|
|||
# runs it headless under the patched MAME, extracts joeylog.txt back off the
|
||||
# image with xdftool, and diffs the hashes against the Apple IIgs reference.
|
||||
#
|
||||
# Takes roughly 70 minutes: UBER on the generic renderer is slow, and the run
|
||||
# must reach jlLogFlush at the very end before any hashes exist on disk.
|
||||
# Takes a few minutes. The run EXITS ON COMPLETION, not on a frame budget: UBER
|
||||
# ends by clearing the whole stage to colour index 2 and presenting it before it
|
||||
# blocks in jlWaitForAnyKey, so the Lua below watches GVRAM for that screen.
|
||||
# (It used to burn a flat 300,000 frames -- ~90 minutes of emulated time -- a
|
||||
# budget sized back when this port still drew through the slow generic renderer.
|
||||
# The actual work is a Human68k boot plus well under 10,000 frames.)
|
||||
# X68K_GOLDEN_FRAMES is now only a backstop: reaching it means the done-screen
|
||||
# was never detected, which is a FAILURE, not a normal exit.
|
||||
#
|
||||
# X68K_SCRATCH=<dir with x68mame/> bash scripts/verify-x68000-golden.sh
|
||||
set -uo pipefail
|
||||
|
|
@ -17,7 +23,7 @@ SP="${X68K_SCRATCH:?set X68K_SCRATCH to a work dir containing x68mame/}"
|
|||
MAME="$repo/toolchains/cache/mame-mame0264/x68k"
|
||||
TEMPLATE="$SP/x68mame/HUMAN302.XDF"
|
||||
GOLDEN="${X68K_GOLDEN:-$repo/tests/goldens/uber/iigs.txt}"
|
||||
FRAMES="${X68K_GOLDEN_FRAMES:-300000}"
|
||||
FRAMES="${X68K_GOLDEN_FRAMES:-60000}"
|
||||
WALL="${X68K_GOLDEN_WALL:-7200}"
|
||||
|
||||
export PATH="$repo/toolchains/x68000/m68k-xelf/bin:$PATH"
|
||||
|
|
@ -34,14 +40,13 @@ m68k-xelf-gcc -s -O2 -m68000 -fomit-frame-pointer \
|
|||
"$repo/build/x68000/lib/libjoey.a" "$repo/build/x68000/lib/libxmplite.a" -lm \
|
||||
-o "$work/UBER.X" || exit 1
|
||||
|
||||
# TWO DISKS. UBER.X is ~240 KB and Human68k needs ~90 KB, which leaves a 1232 KB
|
||||
# floppy with under a cluster spare -- so joeylog.txt cannot be written and the
|
||||
# run silently produces nothing after ~50 minutes. The boot disk therefore holds
|
||||
# only the binary, and a blank second disk on B: takes the log. AUTOEXEC switches
|
||||
# to B: before launching so the log lands there.
|
||||
printf 'B:\r\nA:\\UBER.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
||||
# TWO DISKS. UBER.X is ~250 KB and Human68k needs ~90 KB, which no longer fit
|
||||
# together on a 1232 KB floppy at all -- so 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 launching so the log lands there too.
|
||||
printf 'B:\r\nB:\\UBER.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
||||
cp "$TEMPLATE" "$work/gold.xdf"
|
||||
# Blank data disk = the template with every file removed (keeps the format).
|
||||
# Data disk = the template with every file removed (keeps the format).
|
||||
cp "$TEMPLATE" "$work/data.xdf"
|
||||
for f in HUMAN.SYS CONFIG.SYS KEY.SYS USKCG.SYS BEEP.SYS STARTUP.ENV COMMAND.X AUTOEXEC.BAT; do
|
||||
python3 "$repo/tools/xdftool.py" delete "$work/data.xdf" "$f" >/dev/null 2>&1
|
||||
|
|
@ -50,27 +55,100 @@ done
|
|||
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
|
||||
python3 "$repo/tools/xdftool.py" add "$work/gold.xdf" "$work/UBER.X" UBER.X >/dev/null || exit 1
|
||||
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
|
||||
|
||||
# UBER ends on jlWaitForAnyKey, AFTER jlLogFlush -- post keys late so it exits
|
||||
# cleanly rather than being killed mid-write.
|
||||
# Completion-driven exit. UBER's last act before jlWaitForAnyKey is
|
||||
# jlSurfaceClear(gStage, 2) + jlStagePresent, so an all-colour-2 stage in GVRAM
|
||||
# is the "hashes are computed and flushed" signal. The benchmark's own
|
||||
# jlSurfaceClear/jlStagePresent ops can paint that colour for an instant, so the
|
||||
# reading only counts once it has held for GREEN_STABLE frames -- the finished
|
||||
# screen never changes again, a mid-benchmark frame never lasts.
|
||||
#
|
||||
# jlLogFlush only fflush()es; the directory entry is finalised by the atexit
|
||||
# fclose. So the keypress that releases jlWaitForAnyKey still has to be posted,
|
||||
# and MAME must not be killed until UBER has returned to Human68k -- hence the
|
||||
# repeated posts and the EXIT_SETTLE wait before exiting.
|
||||
cat > "$work/gold.lua" <<LUA
|
||||
local frame = 0
|
||||
local mem = manager.machine.devices[":maincpu"].spaces["program"]
|
||||
local frame = 0
|
||||
local greenRun = 0
|
||||
local exitAt = 0
|
||||
local done = false
|
||||
|
||||
local FRAMES = $FRAMES
|
||||
local GREEN_STABLE = 600 -- ~11 s of emulated time held steady
|
||||
local EXIT_SETTLE = 900 -- keypress -> fclose -> back at the Human68k prompt
|
||||
|
||||
-- crtmod 13, 256-colour graphics plane: one 16-bit word per pixel at \$C00000,
|
||||
-- 512 words per row, the 320x200 stage centred at origin (96, 156).
|
||||
local function stagePixel(px, py)
|
||||
return mem:read_u8(0xC00000 + ((156 + py) * 512 + 96 + px) * 2 + 1)
|
||||
end
|
||||
|
||||
local kProbes = { {4,4}, {160,4}, {315,4}, {4,100}, {160,100}, {315,100},
|
||||
{4,196}, {160,196}, {315,196}, {80,50}, {240,150}, {200,80} }
|
||||
|
||||
local function screenIsDone()
|
||||
for _, p in ipairs(kProbes) do
|
||||
if stagePixel(p[1], p[2]) ~= 2 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frame = frame + 1
|
||||
if frame % 20000 == 0 then io.write("GOLD f"..frame.."\n"); io.flush() end
|
||||
if frame > ($FRAMES - 60000) and frame % 2000 == 0 then
|
||||
manager.machine.natkeyboard:post(" ")
|
||||
if done then return end
|
||||
if frame % 2000 == 0 then io.write("GOLD f"..frame.."\n"); io.flush() end
|
||||
|
||||
if exitAt == 0 then
|
||||
if screenIsDone() then
|
||||
greenRun = greenRun + 1
|
||||
if greenRun >= GREEN_STABLE then
|
||||
io.write("GOLD done-screen f"..frame.."\n"); io.flush()
|
||||
exitAt = frame + EXIT_SETTLE
|
||||
end
|
||||
else
|
||||
greenRun = 0
|
||||
end
|
||||
else
|
||||
-- Release jlWaitForAnyKey; extra presses land harmlessly at the prompt.
|
||||
if frame % 60 == 0 then manager.machine.natkeyboard:post(" ") end
|
||||
if frame >= exitAt then
|
||||
done = true
|
||||
io.write("GOLD exit f"..frame.."\n"); io.flush()
|
||||
manager.machine:exit()
|
||||
end
|
||||
end
|
||||
|
||||
-- Backstop only: reaching this means the done-screen never appeared.
|
||||
if frame > FRAMES then
|
||||
done = true
|
||||
io.write("GOLD budget-exhausted f"..frame.."\n"); io.flush()
|
||||
manager.machine:exit()
|
||||
end
|
||||
if frame > $FRAMES then manager.machine:exit() end
|
||||
end)
|
||||
LUA
|
||||
|
||||
timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||
runLog=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||
-rompath "$SP/x68mame/roms" -flop1 "$work/gold.xdf" -flop2 "$work/data.xdf" \
|
||||
-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')
|
||||
echo "$runLog"
|
||||
|
||||
# The backstop is not a normal exit: it means UBER never reached its done-screen,
|
||||
# so whatever log is on the disk (if any) is from an unfinished run.
|
||||
if echo "$runLog" | grep -q 'budget-exhausted'; then
|
||||
echo "verify-x68000-golden: FAIL - UBER's done-screen never appeared within $FRAMES frames" >&2
|
||||
echo " (raise X68K_GOLDEN_FRAMES if UBER legitimately got slower; otherwise the run died early)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$runLog" | grep -q 'done-screen'; then
|
||||
echo "verify-x68000-golden: FAIL - MAME stopped before UBER finished (wall timeout ${WALL}s?)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 "$repo/tools/xdftool.py" extract "$work/data.xdf" joeylog.txt "$work/x68.txt" || {
|
||||
echo "verify-x68000-golden: FAIL - no joeylog.txt (run did not reach jlLogFlush)" >&2
|
||||
|
|
|
|||
227
scripts/verify-x68000-mouse.sh
Executable file
227
scripts/verify-x68000-mouse.sh
Executable file
|
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env bash
|
||||
# verify-x68000-mouse.sh - Headless mouse acceptance gate for the X68000
|
||||
# port. Boots Human68k under the patched MAME with KEYS.X in
|
||||
# AUTOEXEC.BAT, waits for the mouse verification scanline's 5A5A
|
||||
# sentinel in GVRAM, then drives the emulated mouse (an x68k_mouse
|
||||
# serial device on SCC channel B, decoded by the real IOCS ROM) through
|
||||
# MAME's input-port override API and reads the scanline back after each
|
||||
# step. Covered: delta accumulation in both directions, clamping at all
|
||||
# four stage edges, movement after clamping, and press/hold/release of
|
||||
# both buttons.
|
||||
#
|
||||
# The mouse axes are 12-bit absolute counters the device diffs into
|
||||
# delta packets, so each Lua set_value(v) below moves the mouse by
|
||||
# (v - previous v). Values are biased to 2000 mid-boot for headroom in
|
||||
# both directions. MAME's analog override is timing-sensitive: an
|
||||
# injected delta is sometimes consumed once and sometimes re-sent over
|
||||
# several solicitations, so exact interior positions are NOT testable
|
||||
# through this API. Every position expectation therefore lands ON a
|
||||
# clamp edge, where over-travel is absorbed: each axis is slammed
|
||||
# independently into all four stage edges, which proves delta sign,
|
||||
# axis attribution, and all four clamps deterministically. Slams use
|
||||
# five 127-unit steps (635 > 319 even if two adjacent steps merge into
|
||||
# one clamped 127-unit packet).
|
||||
#
|
||||
# GVRAM geometry (crtmod 13, 256-colour graphics plane): 16-bit word
|
||||
# per pixel at $C00000, 512 words per row; the 320x200 stage is centred
|
||||
# at origin (96, 156). Mouse verification row = stage y 199 -> GVRAM
|
||||
# y 355.
|
||||
#
|
||||
# X68K_SCRATCH=<dir with x68mame/> bash scripts/verify-x68000-mouse.sh
|
||||
set -uo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
SP="${X68K_SCRATCH:?set X68K_SCRATCH to a work dir containing x68mame/}"
|
||||
MAME="$repo/toolchains/cache/mame-mame0264/x68k"
|
||||
TEMPLATE="$SP/x68mame/HUMAN302.XDF"
|
||||
WALL="${X68K_MOUSE_WALL:-900}"
|
||||
|
||||
[ -x "$MAME" ] || { echo "verify-x68000-mouse: missing patched MAME at $MAME" >&2; exit 2; }
|
||||
[ -f "$TEMPLATE" ] || { echo "verify-x68000-mouse: missing $TEMPLATE" >&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)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
printf 'A:\\KEYS.X\r\n\x1a' > "$work/AUTOEXEC.BAT"
|
||||
cp "$TEMPLATE" "$work/boot.xdf"
|
||||
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'
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local mem = cpu.spaces["program"]
|
||||
local frame = 0
|
||||
local t0 = 0
|
||||
local biased = false
|
||||
local done = false
|
||||
local checks = 0
|
||||
local fails = {}
|
||||
|
||||
-- Stage pixel (px, y) -> GVRAM word low byte.
|
||||
local function pix(px, y)
|
||||
local rowBase = 0xC00000 + ((156 + y) * 512 + 96) * 2
|
||||
return mem:read_u8(rowBase + 2 * px + 1)
|
||||
end
|
||||
|
||||
-- Decode the mouse verification row (stage y 199): 5A5A sentinel,
|
||||
-- present/left/right/middle nibbles, then x and y as 4 nibbles each.
|
||||
local function readMouse()
|
||||
local m = {}
|
||||
m.sentinel = pix(0, 199) == 0x5 and pix(1, 199) == 0xA and
|
||||
pix(2, 199) == 0x5 and pix(3, 199) == 0xA
|
||||
m.present = pix(4, 199)
|
||||
m.left = pix(5, 199)
|
||||
m.right = pix(6, 199)
|
||||
m.middle = pix(7, 199)
|
||||
m.x = (pix(8, 199) << 12) | (pix(9, 199) << 8) | (pix(10, 199) << 4) | pix(11, 199)
|
||||
m.y = (pix(12, 199) << 12) | (pix(13, 199) << 8) | (pix(14, 199) << 4) | pix(15, 199)
|
||||
return m
|
||||
end
|
||||
|
||||
-- The x68k mouse device (rs232 slot "mouse_port", option "x68k") owns
|
||||
-- three ports: X and Y are 12-bit absolute counters it diffs into
|
||||
-- delta packets, BTN holds left (mask 2) and right (mask 1).
|
||||
local ports = manager.machine.ioport.ports
|
||||
local axisX, axisY, btnLeft, btnRight
|
||||
local px_port = ports[":mouse_port:x68k:X"]
|
||||
local py_port = ports[":mouse_port:x68k:Y"]
|
||||
local pb_port = ports[":mouse_port:x68k:BTN"]
|
||||
if px_port then for _, f in pairs(px_port.fields) do axisX = f end end
|
||||
if py_port then for _, f in pairs(py_port.fields) do axisY = f end end
|
||||
if pb_port then
|
||||
for _, f in pairs(pb_port.fields) do
|
||||
if f.mask == 2 then btnLeft = f end
|
||||
if f.mask == 1 then btnRight = f end
|
||||
end
|
||||
end
|
||||
if not (axisX and axisY and btnLeft and btnRight) then
|
||||
io.write("VERIFY-X68K-MOUSE error: mouse ioports not found\n")
|
||||
io.flush()
|
||||
manager.machine:exit()
|
||||
end
|
||||
|
||||
local function expect(name, got, want)
|
||||
checks = checks + 1
|
||||
if got ~= want then
|
||||
fails[#fails + 1] = string.format("%s=%d(want %d)", name, got, want)
|
||||
end
|
||||
end
|
||||
|
||||
local function expectRow(step, m, x, y, l, r)
|
||||
checks = checks + 1
|
||||
if not m.sentinel then fails[#fails + 1] = step .. ":sentinel" end
|
||||
expect(step .. ":present", m.present, 1)
|
||||
expect(step .. ":x", m.x, x)
|
||||
expect(step .. ":y", m.y, y)
|
||||
expect(step .. ":left", m.left, l)
|
||||
expect(step .. ":right", m.right, r)
|
||||
expect(step .. ":middle", m.middle, 0)
|
||||
end
|
||||
|
||||
-- Steps run at fixed offsets from t0 (the frame the sentinel landed
|
||||
-- plus settle time). Slam sub-steps are 15 frames apart and every
|
||||
-- read sits 60+ frames after the last injection, so the 4800-baud
|
||||
-- packets, the IOCS solicitation, and the app's poll all settle.
|
||||
-- Down-slams walk the counter 2000 -> 1365, up-slams walk it back.
|
||||
local function walkDown(axis, at, steps)
|
||||
for i = 1, 5 do
|
||||
steps[#steps + 1] = { at = at + 15 * (i - 1), run = function() axis:set_value(2000 - 127 * i) end }
|
||||
end
|
||||
end
|
||||
|
||||
local function walkUp(axis, at, steps)
|
||||
for i = 1, 5 do
|
||||
steps[#steps + 1] = { at = at + 15 * (i - 1), run = function() axis:set_value(1365 + 127 * i) end }
|
||||
end
|
||||
end
|
||||
|
||||
local steps = {}
|
||||
-- Slam both axes to the (0, 0) corner: any possible starting position
|
||||
-- is absorbed by the clamps, making the state absolute from here on.
|
||||
walkDown(axisX, 0, steps)
|
||||
walkDown(axisY, 0, steps)
|
||||
steps[#steps + 1] = { at = 120, run = function() expectRow("slamOrigin", readMouse(), 0, 0, 0, 0) end }
|
||||
-- One axis at a time into each remaining edge: proves delta sign and
|
||||
-- axis attribution (the untouched axis must stay put) plus the clamp.
|
||||
walkUp(axisX, 130, steps)
|
||||
steps[#steps + 1] = { at = 250, run = function() expectRow("slamRight", readMouse(), 319, 0, 0, 0) end }
|
||||
walkUp(axisY, 260, steps)
|
||||
steps[#steps + 1] = { at = 380, run = function() expectRow("slamBottom", readMouse(), 319, 199, 0, 0) end }
|
||||
walkDown(axisX, 390, steps)
|
||||
steps[#steps + 1] = { at = 510, run = function() expectRow("slamLeft", readMouse(), 0, 199, 0, 0) end }
|
||||
walkDown(axisY, 520, steps)
|
||||
steps[#steps + 1] = { at = 640, run = function() expectRow("slamTop", readMouse(), 0, 0, 0, 0) end }
|
||||
-- Button choreography at a settled position.
|
||||
steps[#steps + 1] = { at = 650, run = function() btnLeft:set_value(1) end }
|
||||
steps[#steps + 1] = { at = 710, run = function() expectRow("leftPress", readMouse(), 0, 0, 1, 0) end }
|
||||
steps[#steps + 1] = { at = 740, run = function() expectRow("leftHold", readMouse(), 0, 0, 1, 0) end }
|
||||
steps[#steps + 1] = { at = 750, run = function() btnLeft:clear_value(); btnRight:set_value(1) end }
|
||||
steps[#steps + 1] = { at = 810, run = function() expectRow("rightPress", readMouse(), 0, 0, 0, 1) end }
|
||||
steps[#steps + 1] = { at = 820, run = function() btnRight:clear_value() end }
|
||||
steps[#steps + 1] = { at = 880, run = function()
|
||||
expectRow("released", readMouse(), 0, 0, 0, 0)
|
||||
done = true
|
||||
io.write(string.format("VERIFY-X68K-MOUSE checks=%d fails=%d%s\n",
|
||||
checks, #fails,
|
||||
#fails > 0 and (" detail=" .. table.concat(fails, ",")) or ""))
|
||||
io.flush()
|
||||
manager.machine:exit()
|
||||
end }
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frame = frame + 1
|
||||
if done then return end
|
||||
if not biased and frame == 300 then
|
||||
-- Mid-boot counter bias for two-directional headroom. However
|
||||
-- the boot-time solicitation history consumes this, the slam
|
||||
-- steps above make the position deterministic afterwards.
|
||||
biased = true
|
||||
axisX:set_value(2000)
|
||||
axisY:set_value(2000)
|
||||
end
|
||||
if t0 == 0 and frame > 600 and frame % 100 == 0 then
|
||||
-- KEYS is up once both verification sentinels are stamped.
|
||||
if pix(0, 198) == 0xA and pix(1, 198) == 0x5 and
|
||||
pix(0, 199) == 0x5 and pix(1, 199) == 0xA then
|
||||
t0 = frame + 60
|
||||
end
|
||||
end
|
||||
if t0 ~= 0 then
|
||||
for _, s in ipairs(steps) do
|
||||
if frame == t0 + s.at then s.run() end
|
||||
end
|
||||
end
|
||||
if t0 == 0 and frame > 30000 then
|
||||
done = true
|
||||
io.write("VERIFY-X68K-MOUSE timeout: sentinel never appeared\n")
|
||||
io.flush()
|
||||
manager.machine:exit()
|
||||
end
|
||||
end)
|
||||
LUA
|
||||
|
||||
# Run from the work dir so MAME's cfg/ and nvram/ droppings land there
|
||||
# instead of the caller's cwd.
|
||||
cd "$work"
|
||||
out=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
||||
-rompath "$SP/x68mame/roms" -flop1 "$work/boot.xdf" \
|
||||
-video none -sound none -nothrottle \
|
||||
-autoboot_script "$work/mouse.lua" </dev/null 2>&1) || true
|
||||
|
||||
line=$(echo "$out" | grep -E '^VERIFY-X68K-MOUSE ' | tail -1)
|
||||
echo "$line"
|
||||
if [ -z "$line" ] || echo "$line" | grep -q -e timeout -e error; then
|
||||
echo "verify-x68000-mouse: FAIL - KEYS never came up or ports missing" >&2
|
||||
echo "$out" | tail -10 >&2
|
||||
exit 1
|
||||
fi
|
||||
if echo "$line" | grep -q 'fails=0$'; then
|
||||
echo "verify-x68000-mouse: PASS (deltas, all four clamps, and both buttons verified through the IOCS ROM)"
|
||||
else
|
||||
echo "verify-x68000-mouse: FAIL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -332,6 +332,7 @@ void jlpInputInit(void) {
|
|||
TAG_DONE);
|
||||
|
||||
if (gWindow != NULL) {
|
||||
gMouseAttached = true; // Intuition delivers IDCMP mouse events on this window
|
||||
gMouseX = (int16_t)(gWindow->Width / 2);
|
||||
gMouseY = (int16_t)(gWindow->Height / 2);
|
||||
|
||||
|
|
|
|||
|
|
@ -105,35 +105,45 @@ void jlpSerialFlush(void) {
|
|||
}
|
||||
|
||||
|
||||
// Why the last jlpSerialOpen failed, for host-side diagnosis: a silent false was unattributable
|
||||
// through three swallowing layers (this file -> core gate -> the client's "non-fatal" open).
|
||||
// stage: 1=CreateMsgPort 2=CreateIORequest 3=OpenDevice 4=SDCMD_SETPARAMS 5=write IORequest.
|
||||
uint8_t gAmigaSerialFailStage = 0u;
|
||||
BYTE gAmigaSerialFailErr = 0;
|
||||
BYTE gAmigaSerialFailIoErr = 0;
|
||||
uint8_t gAmigaSerialFailDevice = 0u;
|
||||
|
||||
|
||||
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||
ULONG unit;
|
||||
UBYTE flags;
|
||||
BYTE err;
|
||||
|
||||
if (gOpen) {
|
||||
return false;
|
||||
}
|
||||
gAmigaSerialFailStage = 0u;
|
||||
gAmigaSerialFailErr = 0;
|
||||
|
||||
gPort = CreateMsgPort();
|
||||
if (gPort == NULL) {
|
||||
gAmigaSerialFailStage = 1u;
|
||||
return false;
|
||||
}
|
||||
gReadReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
||||
if (gReadReq == NULL) {
|
||||
gAmigaSerialFailStage = 2u;
|
||||
serialTeardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (OpenDevice((CONST_STRPTR)"serial.device", unit, (struct IORequest *)gReadReq, 0ul) != 0) {
|
||||
serialTeardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Line parameters via SDCMD_SETPARAMS.
|
||||
// io_SerFlags participates in OPENDEVICE, not just SETPARAMS -- the autodoc contract is "the
|
||||
// request must be zeroed EXCEPT io_SerFlags, set before open". Setting the flags only after the
|
||||
// open (the old order) both violated that contract and left the unit opened EXCLUSIVE with
|
||||
// xon/xoff ENABLED until SETPARAMS ran.
|
||||
flags = 0u;
|
||||
if (config->flow != JL_SERIAL_FLOW_XONXOFF) {
|
||||
flags |= SERF_XDISABLED; // no software flow control
|
||||
flags |= SERF_XDISABLED; // no software flow control: 0x11/0x13 are DATA here
|
||||
}
|
||||
if (config->flow == JL_SERIAL_FLOW_RTSCTS) {
|
||||
flags |= SERF_7WIRE; // hardware RTS/CTS handshake
|
||||
|
|
@ -144,13 +154,42 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
flags |= SERF_PARTY_ODD;
|
||||
}
|
||||
}
|
||||
gReadReq->io_SerFlags = flags;
|
||||
|
||||
// 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;
|
||||
err = OpenDevice((CONST_STRPTR)"serial.device", unit, (struct IORequest *)gReadReq, 0ul);
|
||||
if (err != 0) {
|
||||
// Exclusive access is refused if ANYONE holds the unit; fall back to a shared open, which is
|
||||
// fine for this HAL (it is the only serial user in the program).
|
||||
gReadReq->io_SerFlags = (UBYTE)(flags | SERF_SHARED);
|
||||
err = OpenDevice((CONST_STRPTR)"serial.device", unit, (struct IORequest *)gReadReq, 0ul);
|
||||
}
|
||||
if (err != 0) {
|
||||
gAmigaSerialFailStage = 3u;
|
||||
gAmigaSerialFailErr = err;
|
||||
// Distinguish a REAL open failure from a lying return value: io_Error is the device's own
|
||||
// verdict and io_Device is non-NULL if the device actually attached.
|
||||
gAmigaSerialFailIoErr = gReadReq->IOSer.io_Error;
|
||||
gAmigaSerialFailDevice = (gReadReq->IOSer.io_Device != NULL) ? 1u : 0u;
|
||||
serialTeardown();
|
||||
return false;
|
||||
}
|
||||
gReadReq->io_Baud = config->baud;
|
||||
gReadReq->io_ReadLen = config->dataBits;
|
||||
gReadReq->io_WriteLen = config->dataBits;
|
||||
gReadReq->io_StopBits = config->stopBits;
|
||||
gReadReq->io_SerFlags = flags;
|
||||
// CreateIORequest zero-fills the request, and serial.device REJECTS a SETPARAMS whose io_RBufLen
|
||||
// is below the documented 64-byte minimum -- so leaving it at 0 made every open fail (silently,
|
||||
// three swallowed layers up). Ask for a real ring while at it: the X68000 port just proved a
|
||||
// ~64-byte receive window loses mid-frame bytes whenever the client blits or polls input.
|
||||
gReadReq->io_RBufLen = 4096ul;
|
||||
// io_SerFlags already holds the value the successful open used (possibly including the
|
||||
// SERF_SHARED fallback) -- do not strip it here.
|
||||
gReadReq->IOSer.io_Command = SDCMD_SETPARAMS;
|
||||
if (DoIO((struct IORequest *)gReadReq) != 0) {
|
||||
gAmigaSerialFailStage = 4u;
|
||||
gAmigaSerialFailErr = gReadReq->IOSer.io_Error;
|
||||
serialTeardown();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -158,6 +197,7 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
// Second request sharing the same open device (copy carries io_Device/Unit).
|
||||
gWriteReq = (struct IOExtSer *)CreateIORequest(gPort, sizeof(struct IOExtSer));
|
||||
if (gWriteReq == NULL) {
|
||||
gAmigaSerialFailStage = 5u;
|
||||
serialTeardown();
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@ void jlpJoystickReset(jlJoystickE js) {
|
|||
void jlpInputInit(void) {
|
||||
_KEYTAB *keyTab;
|
||||
|
||||
gMouseAttached = true; // the IKBD mouse line is always wired on an ST
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
|
|
|
|||
|
|
@ -28,10 +28,29 @@
|
|||
// bit 1 parity even (1) / odd (0)
|
||||
#define ST_UCR_CLK_DIV16 0x80
|
||||
|
||||
// Rsconf sentinels. -1 in any slot means "leave that field alone" (TOS writes a
|
||||
// field only when the value it is handed is in range). -2 in the SPEED slot is
|
||||
// the separate, documented baud inquire (mint/ostruct.h BAUD_INQUIRE): EmuTOS's
|
||||
// rsconf_mfp returns iorec->baudrate and returns BEFORE touching any register,
|
||||
// so it is side-effect free. Do NOT use -1 to inquire; -1 is "no change".
|
||||
#define ST_RSCONF_NO_CHANGE ((int16_t)-1)
|
||||
#define ST_RSCONF_INQUIRE ((int16_t)-2)
|
||||
#define ST_BAUD_CODE_MAX 15
|
||||
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
// What jlpSerialOpen took away, so jlpSerialClose can hand it back. Both sit at
|
||||
// ST_RSCONF_NO_CHANGE when there is nothing to restore, so a close with no
|
||||
// matching open -- or a second close -- lowers to an Rsconf that writes nothing.
|
||||
static int16_t gSavedBaudCode = ST_RSCONF_NO_CHANGE;
|
||||
static int16_t gSavedUcr = ST_RSCONF_NO_CHANGE;
|
||||
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static int16_t serialBaudCode(uint32_t baud);
|
||||
static int32_t serialRsconf(int16_t baud, int16_t flow, int16_t ucr);
|
||||
static int16_t serialUcr(const jlSerialConfigT *cfg);
|
||||
|
||||
|
||||
|
|
@ -61,6 +80,46 @@ static int16_t serialBaudCode(uint32_t baud) {
|
|||
}
|
||||
|
||||
|
||||
// XBIOS 15 (Rsconf) done by hand, because mint's Rsconf() macro CANNOT be
|
||||
// trusted with computed arguments.
|
||||
//
|
||||
// osbind.h's own change log admits it: the seven-word trap macro relaxed its
|
||||
// operand constraints from "r" to "g" on the grounds that "these args will
|
||||
// never be expressions". With "g", gcc is free to leave an argument in a stack
|
||||
// temp -- and the macro then pushes seven words with movew ...,sp@-, moving sp
|
||||
// out from under that temp, so the push reads the wrong offset. The disassembly
|
||||
// showed exactly that: the baud code was parked at sp@(14) and pushed after sp
|
||||
// had already moved. MEASURED under EmuTOS/Hatari: every requested rate (19200,
|
||||
// 9600, 4800, 2400, 1200, 300) programmed 9600, and shuffling the C around only
|
||||
// changed WHICH wrong code came out. The UCR happened to survive in a register.
|
||||
//
|
||||
// Pushing the words here, with the three variable ones bound to registers,
|
||||
// removes the sp-relative temp entirely. rsr/tsr/scr are immediates: no caller
|
||||
// wants them, and leaving them "no change" means there is nothing to undo.
|
||||
static int32_t serialRsconf(int16_t baud, int16_t flow, int16_t ucr) {
|
||||
// Bound to d0 the way osbind.h's own trap_14_* helpers bind theirs: XBIOS
|
||||
// returns in d0, and an unbound output operand would leave gcc free to pick
|
||||
// another register that the asm never writes (which silently returned
|
||||
// garbage here until the probe caught it).
|
||||
register int32_t ret __asm__("d0");
|
||||
|
||||
__asm__ volatile (
|
||||
"movew #-1,%%sp@-\n\t" /* scr: no change */
|
||||
"movew #-1,%%sp@-\n\t" /* tsr: no change */
|
||||
"movew #-1,%%sp@-\n\t" /* rsr: no change */
|
||||
"movew %3,%%sp@-\n\t" /* ucr */
|
||||
"movew %2,%%sp@-\n\t" /* flow */
|
||||
"movew %1,%%sp@-\n\t" /* baud */
|
||||
"movew #15,%%sp@-\n\t" /* XBIOS 15 = Rsconf */
|
||||
"trap #14\n\t"
|
||||
"lea %%sp@(14),%%sp"
|
||||
: "=d" (ret)
|
||||
: "d" (baud), "d" (flow), "d" (ucr)
|
||||
: "d1", "d2", "a0", "a1", "cc", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int16_t serialUcr(const jlSerialConfigT *cfg) {
|
||||
int16_t ucr;
|
||||
|
||||
|
|
@ -85,8 +144,23 @@ uint16_t jlpSerialAvailable(void) {
|
|||
}
|
||||
|
||||
|
||||
// The trap path hijacks no vector, but jlpSerialOpen did change the line
|
||||
// settings, and those live in the MFP and TOS's iorec -- both of which outlive
|
||||
// the program. So hand back the two fields TOS lets us recover: the baud code
|
||||
// (from the -2 inquire) and the UCR (the high byte of what Rsconf returned when
|
||||
// it installed ours). rsr/tsr/scr were never written -- open passes -1 for them
|
||||
// -- so they need no undo, and scr is not in the return value anyway.
|
||||
//
|
||||
// Flow control is deliberately NOT restored: Rsconf has no inquire for it (TOS
|
||||
// keeps it in iorec->flowctrl with no way back out), so the only options are
|
||||
// leaving it or guessing, and guessing a value onto a working port is the
|
||||
// mistake the X68000 _SET232C note in that port's serial.c warns about. A user
|
||||
// who had hardware handshaking on keeps the app's setting until something sets
|
||||
// it again; that is a documented limitation, not a silent corruption.
|
||||
void jlpSerialClose(void) {
|
||||
// Trap path hijacks no vector -- nothing to tear down.
|
||||
(void)serialRsconf(gSavedBaudCode, ST_RSCONF_NO_CHANGE, gSavedUcr);
|
||||
gSavedBaudCode = ST_RSCONF_NO_CHANGE;
|
||||
gSavedUcr = ST_RSCONF_NO_CHANGE;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -99,6 +173,10 @@ void jlpSerialFlush(void) {
|
|||
|
||||
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||
int16_t flow;
|
||||
int16_t baudCode;
|
||||
int16_t ucr;
|
||||
int32_t inquired;
|
||||
uint32_t previous;
|
||||
|
||||
(void)device; // the base ST has one RS-232 line: the Modem port.
|
||||
switch (config->flow) {
|
||||
|
|
@ -106,8 +184,33 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
case JL_SERIAL_FLOW_XONXOFF: flow = 1; break;
|
||||
default: flow = 0; break;
|
||||
}
|
||||
// Rsconf(speed, flow, ucr, rsr, tsr, scr); -1 leaves a field unchanged.
|
||||
Rsconf(serialBaudCode(config->baud), flow, serialUcr(config), -1, -1, -1);
|
||||
|
||||
// EVALUATE THE HELPERS INTO LOCALS FIRST -- never inline a call into the
|
||||
// Rsconf() argument list. mint/osbind.h's own change log says so: the
|
||||
// seven-word trap macro relaxed its operand constraints from "r" to "g"
|
||||
// and notes that is "ok since these args will never be expressions", so
|
||||
// gcc may materialise an argument from a stack slot AFTER the macro has
|
||||
// moved sp out from under it. Passing serialBaudCode(...) inline was
|
||||
// exactly that, and it lost the baud code: MEASURED under EmuTOS/Hatari,
|
||||
// every requested rate -- 19200, 9600, 4800, 2400, 1200, 300 -- programmed
|
||||
// code 1 (9600). serialUcr's result survived; the baud one did not.
|
||||
baudCode = serialBaudCode(config->baud);
|
||||
ucr = serialUcr(config);
|
||||
|
||||
// Snapshot the baud code BEFORE installing ours -- the inquire reports the
|
||||
// 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
|
||||
// nothing (every field is out of range), and hand back the packed
|
||||
// ucr/rsr/tsr instead, which is >= 0x01000000 for any non-zero UCR.
|
||||
inquired = serialRsconf(ST_RSCONF_INQUIRE, ST_RSCONF_NO_CHANGE, ST_RSCONF_NO_CHANGE);
|
||||
if (inquired >= 0 && inquired <= ST_BAUD_CODE_MAX) {
|
||||
gSavedBaudCode = (int16_t)inquired;
|
||||
}
|
||||
|
||||
// The return is the PREVIOUS (ucr << 24) | (rsr << 16) | (tsr << 8), so the
|
||||
// same call that installs our line settings also reports the UCR we displaced.
|
||||
previous = (uint32_t)serialRsconf(baudCode, flow, ucr);
|
||||
gSavedUcr = (int16_t)((previous >> 24) & 0xFFu);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ uint8_t gKeyState [KEY_COUNT];
|
|||
uint8_t gKeyPrev [KEY_COUNT];
|
||||
|
||||
int16_t gMouseX = 0;
|
||||
bool gMouseAttached = false; // set by jlpInputInit where the HAL confirms a mouse
|
||||
int16_t gMouseY = 0;
|
||||
uint8_t gMouseButtonState[MOUSE_BUTTON_COUNT];
|
||||
uint8_t gMouseButtonPrev [MOUSE_BUTTON_COUNT];
|
||||
|
|
@ -205,6 +206,11 @@ bool jlMouseReleased(jlMouseButtonE button) {
|
|||
}
|
||||
|
||||
|
||||
bool jlMousePresent(void) {
|
||||
return gMouseAttached;
|
||||
}
|
||||
|
||||
|
||||
int16_t jlMouseX(void) {
|
||||
return gMouseX;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
extern uint8_t gKeyState[KEY_COUNT];
|
||||
extern uint8_t gKeyPrev [KEY_COUNT];
|
||||
|
||||
extern bool gMouseAttached;
|
||||
extern int16_t gMouseX;
|
||||
extern int16_t gMouseY;
|
||||
extern uint8_t gMouseButtonState[MOUSE_BUTTON_COUNT];
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@
|
|||
|
||||
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// Root the save tree at the APPLICATION directory via the GS/OS "1/" prefix
|
||||
// designator (the Loader sets prefix 1 to the app's directory at launch).
|
||||
// Unrooted paths resolve against prefix 0, which a Finder launch leaves
|
||||
// pointing away from (or nowhere near) the app's volume - measured: every
|
||||
// plain "SAVES/x" open failed under MAME GS/OS 6 while the same file opened
|
||||
// fine through "1/SAVES/x". libc's fopen passes the string verbatim to
|
||||
// GS/OS Open, which accepts prefix-designator syntax.
|
||||
#define JL_SAVE_DIR "1/SAVES"
|
||||
#define JL_SAVE_DIR_PREFIX "1/SAVES/"
|
||||
|
||||
// =====================================================================
|
||||
// IIgs direct-dispatch macros.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -14,8 +14,16 @@
|
|||
#include "port.h"
|
||||
|
||||
|
||||
#define SAVE_DIR "SAVES"
|
||||
#define SAVE_DIR_PREFIX "SAVES/"
|
||||
// A port that must root the save tree elsewhere overrides these in port.h
|
||||
// (IIgs: GS/OS prefix-designator "1/" = the application directory - unrooted
|
||||
// paths resolve against prefix 0, which a Finder launch does not point at the
|
||||
// app's volume, so plain "SAVES/x" opens fail there).
|
||||
#ifndef JL_SAVE_DIR
|
||||
#define JL_SAVE_DIR "SAVES"
|
||||
#define JL_SAVE_DIR_PREFIX "SAVES/"
|
||||
#endif
|
||||
#define SAVE_DIR JL_SAVE_DIR
|
||||
#define SAVE_DIR_PREFIX JL_SAVE_DIR_PREFIX
|
||||
#define SAVE_PATH_MAX 256
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -580,6 +580,7 @@ void jlpInputInit(void) {
|
|||
gHooked = true;
|
||||
|
||||
gMousePresent = mouseInit();
|
||||
gMouseAttached = gMousePresent;
|
||||
gJoystickPresent = joystickInit();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,8 @@ extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y,
|
|||
// bytes), iigsTilePasteMonoInner's mono-byte -> opacity-pair index
|
||||
// table (PERF-AUDIT #3).
|
||||
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
|
||||
// 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);
|
||||
|
|
@ -318,8 +320,10 @@ void jlpPresent(const jlSurfaceT *src) {
|
|||
// rows, which seed all 16 palettes, present, then do pixel-only presents
|
||||
// over rows 190-199 (where a descending-push overrun would land first)
|
||||
// and re-compare $E1 against the stage.
|
||||
iigsSerialPump();
|
||||
uploadScbAndPaletteIfNeeded(src);
|
||||
iigsBlitStageToShr();
|
||||
iigsSerialPump();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -380,16 +384,19 @@ void jlpShutdown(void) {
|
|||
// would already be too late to keep that struct out of the display.
|
||||
static void *gStagePixelsHandle = NULL;
|
||||
|
||||
// Bank-0 region the linker uses without telling the Memory Manager:
|
||||
// the C heap ($BC67-$BF00 in the current layout) AND the bank-0
|
||||
// ALIASES of joeyDraw.s's D-relative pointer-scratch symbols
|
||||
// (dpxlScratch/dlnScratch/dcPixPtr/fcPixPtr, ~$BBB8-$BC66) -- the
|
||||
// [pix],y pattern reads/writes bank 0 at the symbol's OFFSET. If an MM
|
||||
// handle (codegen arena, jlpBigAlloc scratch) lands there, every draw
|
||||
// primitive plots through stomped pointers (PERF-AUDIT.md #88, the
|
||||
// codegen-on showcase hang). Claim generously: $00:B900-$BEFF.
|
||||
#define IIGS_BANK0_CLAIM_BASE ((void *)0x00B900L)
|
||||
#define IIGS_BANK0_CLAIM_BYTES 0x0600ul
|
||||
// Bank-0 region the linker uses without telling the Memory Manager: the
|
||||
// link-time BSS (which holds the bank-0 ALIASES of joeyDraw.s's D-relative
|
||||
// pointer-scratch symbols -- the [pix],y pattern reads/writes bank 0 at the
|
||||
// symbol's OFFSET) plus the link-time C heap window. If an MM handle
|
||||
// (codegen arena, jlpBigAlloc scratch, a GS/OS file buffer) lands there,
|
||||
// every draw primitive plots through stomped pointers (PERF-AUDIT.md #88,
|
||||
// the codegen-on showcase hang).
|
||||
// The span is COMPUTED from the link symbols, not hard-coded: the old
|
||||
// $B900-$BEFF constant was the DEMO layout's top slice, and the bigger
|
||||
// RetroNet client linked its scratch at ~$84CC -- entirely unprotected,
|
||||
// with the claim failing against whatever GS/OS had placed at $B900.
|
||||
extern char __bss_start[];
|
||||
extern char __heap_end[];
|
||||
|
||||
static void *gBank0ScratchHandle = NULL;
|
||||
|
||||
|
|
@ -408,12 +415,17 @@ static void claimStagePixels(void) {
|
|||
}
|
||||
}
|
||||
if (gBank0ScratchHandle == NULL) {
|
||||
gBank0ScratchHandle = NewHandle(IIGS_BANK0_CLAIM_BYTES,
|
||||
_ownerid,
|
||||
attrAddr | attrFixed | attrLocked,
|
||||
IIGS_BANK0_CLAIM_BASE);
|
||||
unsigned long base = (unsigned long)(void *)__bss_start;
|
||||
unsigned long end = (unsigned long)(void *)__heap_end;
|
||||
|
||||
if (end > base) {
|
||||
gBank0ScratchHandle = NewHandle(end - base,
|
||||
_ownerid,
|
||||
attrAddr | attrFixed | attrLocked,
|
||||
(void *)base);
|
||||
}
|
||||
if (gBank0ScratchHandle == NULL) {
|
||||
coreSetError("bank-0 heap/scratch MM reservation failed");
|
||||
coreSetError("bank-0 bss/heap MM reservation failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -502,7 +514,10 @@ void jlpWaitVBL(void) {
|
|||
|
||||
tick = iigsGetTickWord();
|
||||
while (iigsGetTickWord() == tick) {
|
||||
/* wait for the VBL interrupt to advance the tick */;
|
||||
// The VBL spin is the client loop's longest deaf window (~16ms); drain the 3-byte SCC FIFO
|
||||
// into the serial soft ring while waiting or a 9600-baud downlink loses mid-frame bytes
|
||||
// (the X68000 lesson, same Z8530).
|
||||
iigsSerialPump();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -421,6 +421,7 @@ void jlpInputInit(void) {
|
|||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
buildAsciiTable();
|
||||
|
||||
gMouseAttached = true; // the ADB mouse is part of every IIgs
|
||||
gMouseAbsX = SURFACE_WIDTH / 2;
|
||||
gMouseAbsY = SURFACE_HEIGHT / 2;
|
||||
gMouseX = gMouseAbsX;
|
||||
|
|
@ -432,6 +433,12 @@ void jlpInputInit(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 ascii;
|
||||
uint8_t key;
|
||||
|
|
@ -450,6 +457,14 @@ void jlpInputPoll(void) {
|
|||
// ever fails to clear.
|
||||
strobeObserved = false;
|
||||
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;
|
||||
if ((kbd & KBD_STROBE_BIT) == 0) {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
// Apple IIgs serial HAL -- direct, polled register access.
|
||||
//
|
||||
// Three endpoints behind one API, selected at open:
|
||||
// JL_SERIAL_MODEM -- modem port = Zilog 8530 SCC channel A
|
||||
// JL_SERIAL_PRINTER -- printer port = Zilog 8530 SCC channel B
|
||||
// JL_SERIAL_MODEM -- modem port = Zilog 8530 SCC channel B
|
||||
// JL_SERIAL_PRINTER -- printer port = Zilog 8530 SCC channel A
|
||||
// JL_SERIAL_SLOT -- 6551 ACIA on a Super Serial Card in cfg.unit's slot
|
||||
// JL_SERIAL_DEFAULT -- the modem port (SCC channel A)
|
||||
// JL_SERIAL_DEFAULT -- the modem port (SCC channel B)
|
||||
//
|
||||
// Channel-to-port assignment: MEASURED against MAME 0.264's apple2gs (its
|
||||
// machine config wires out_txda -> "printer" and out_txdb -> "modem"); with
|
||||
// the old modem=A mapping every TX byte left the unconnected printer port and
|
||||
// the modem-port peer saw silence. If real silicon turns out to disagree,
|
||||
// pick the other port at open (MODEM <-> PRINTER) rather than editing this
|
||||
// table.
|
||||
//
|
||||
// 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
|
||||
|
|
@ -29,10 +36,17 @@
|
|||
|
||||
// ----- SCC (modem/printer) registers -- 24-bit long pointers -----
|
||||
|
||||
#define SCC_B_CTRL ((volatile uint8_t *)0x00C038L) // printer port command/control (ch B)
|
||||
#define SCC_A_CTRL ((volatile uint8_t *)0x00C039L) // modem port command/control (ch A)
|
||||
#define SCC_B_DATA ((volatile uint8_t *)0x00C03AL) // printer port data (ch B)
|
||||
#define SCC_A_DATA ((volatile uint8_t *)0x00C03BL) // modem port data (ch A)
|
||||
#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_B_DATA ((volatile uint8_t *)0x00C03AL) // modem port data (ch B)
|
||||
#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,
|
||||
// 11 FORCE HARDWARE RESET. Only ever issue the one for the channel being
|
||||
// opened: 0xC0 also wipes the OTHER channel's registers, FIFOs, baud generator
|
||||
// and DTR/RTS, and on a IIgs that is the port AppleTalk runs on.
|
||||
#define SCC_WR9_RESET_A 0x80u
|
||||
#define SCC_WR9_RESET_B 0x40u
|
||||
|
||||
// RR0 (status) bits, read directly from the command register.
|
||||
#define SCC_RR0_RX_AVAIL 0x01
|
||||
|
|
@ -57,6 +71,11 @@
|
|||
// ----- Module state -----
|
||||
|
||||
static uint8_t gRxRing[RX_RING_SIZE];
|
||||
|
||||
// Flow counters for the RetroNet RXSTAT diagnostic (read via extern): bytes moved SCC->ring and
|
||||
// ring->caller since open. Cheap enough to keep unconditionally.
|
||||
uint16_t gIigsRxPumped = 0u;
|
||||
uint16_t gIigsRxRead = 0u;
|
||||
static uint16_t gRxHead;
|
||||
static uint16_t gRxTail;
|
||||
static bool gOpen;
|
||||
|
|
@ -68,7 +87,7 @@ static volatile uint8_t *gData; // data register (both)
|
|||
// ----- Prototypes -----
|
||||
|
||||
static void serialInitAcia(volatile uint8_t *base, const jlSerialConfigT *cfg);
|
||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, const jlSerialConfigT *cfg);
|
||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, uint8_t reset, const jlSerialConfigT *cfg);
|
||||
static bool serialRxReady(void);
|
||||
static bool serialTxReady(void);
|
||||
static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val);
|
||||
|
|
@ -85,7 +104,7 @@ static void sccWrite(volatile uint8_t *ctrl, uint8_t reg, uint8_t val) {
|
|||
}
|
||||
|
||||
|
||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, const jlSerialConfigT *cfg) {
|
||||
static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, uint8_t reset, const jlSerialConfigT *cfg) {
|
||||
uint8_t wr4;
|
||||
uint8_t wr3;
|
||||
uint8_t wr5;
|
||||
|
|
@ -110,12 +129,32 @@ static void serialInitScc(volatile uint8_t *ctrl, volatile uint8_t *data, const
|
|||
default: wr3 = 0xC0u; wr5 = 0x60u; break; // 8
|
||||
}
|
||||
|
||||
// baud = PCLK(3.6864MHz) / (2 * (TC+2) * 16) with the WR4 /16 clock mode -- the real-hardware
|
||||
// formula, which MAME's z80scc implements faithfully. Stock MAME 0.264 apple2gs feeds the SCC
|
||||
// the WRONG clock (14.318MHz/2 instead of the 3.6864MHz PCLK) and pins RX at rxc/16 = 230400 via
|
||||
// configure_channels; that is fixed in the DRIVER (patches/mame-0.264-apple2gs-scc-clock.patch),
|
||||
// not here.
|
||||
tc = SCC_TC_NUM / (int32_t)cfg->baud - 2;
|
||||
if (tc < 0) {
|
||||
tc = 0;
|
||||
}
|
||||
|
||||
sccWrite(ctrl, 9u, 0x00u); // reset pointer / no interrupts (polled)
|
||||
// Reset THIS CHANNEL ONLY, first: on real silicon the firmware has already reset the chip at boot,
|
||||
// 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).
|
||||
// This used to be WR9 = 0xC0, a force-hardware-reset that took the OTHER channel down with it.
|
||||
//
|
||||
// Not fully solved, and it cannot be from here: WR9 is a single chip-wide register AND is
|
||||
// write-only, so the reset command unavoidably rewrites the chip's interrupt-control bits
|
||||
// (MIE/VIS/NV) with our zeroes, and there is no way to read the previous value back to preserve
|
||||
// them. A driver on the other channel that takes interrupts -- AppleTalk -- is therefore still
|
||||
// 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
|
||||
// encoding bits the old chip-wide reset cleared, so a
|
||||
// channel left in FM0 by LocalTalk would stay there.
|
||||
sccWrite(ctrl, 4u, wr4);
|
||||
sccWrite(ctrl, 3u, wr3); // Rx bits, not yet enabled
|
||||
sccWrite(ctrl, 5u, (uint8_t)(wr5 | 0x82u)); // Tx bits + DTR + RTS, not yet enabled
|
||||
|
|
@ -224,9 +263,9 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
switch (device) {
|
||||
case JL_SERIAL_PRINTER:
|
||||
gIsScc = true;
|
||||
gStatus = SCC_B_CTRL;
|
||||
gData = SCC_B_DATA;
|
||||
serialInitScc(SCC_B_CTRL, SCC_B_DATA, config);
|
||||
gStatus = SCC_A_CTRL;
|
||||
gData = SCC_A_DATA;
|
||||
serialInitScc(SCC_A_CTRL, SCC_A_DATA, SCC_WR9_RESET_A, config);
|
||||
break;
|
||||
case JL_SERIAL_SLOT: {
|
||||
volatile uint8_t *base;
|
||||
|
|
@ -245,9 +284,9 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
case JL_SERIAL_DEFAULT:
|
||||
default:
|
||||
gIsScc = true;
|
||||
gStatus = SCC_A_CTRL;
|
||||
gData = SCC_A_DATA;
|
||||
serialInitScc(SCC_A_CTRL, SCC_A_DATA, config);
|
||||
gStatus = SCC_B_CTRL;
|
||||
gData = SCC_B_DATA;
|
||||
serialInitScc(SCC_B_CTRL, SCC_B_DATA, SCC_WR9_RESET_B, config);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +295,15 @@ bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
|||
}
|
||||
|
||||
|
||||
// Drain the SCC hardware FIFO into the soft ring from ANY wait/blit/input site (the X68000 lesson:
|
||||
// a polled port with a tiny hardware FIFO loses mid-frame bytes during every deaf window - the Z8530
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
void jlpSerialPoll(void) {
|
||||
uint16_t tail;
|
||||
uint16_t guard;
|
||||
|
|
@ -275,6 +323,7 @@ void jlpSerialPoll(void) {
|
|||
if (next != gRxHead) {
|
||||
gRxRing[tail] = b;
|
||||
tail = next;
|
||||
gIigsRxPumped++;
|
||||
}
|
||||
guard--;
|
||||
}
|
||||
|
|
@ -292,6 +341,7 @@ uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
|||
head = (uint16_t)((head + 1u) & RX_RING_MASK);
|
||||
}
|
||||
gRxHead = head;
|
||||
gIigsRxRead = (uint16_t)(gIigsRxRead + n);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +353,10 @@ uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
|||
uint16_t guard = 0u;
|
||||
|
||||
while (!serialTxReady()) {
|
||||
// Full-duplex: drain the RX FIFO while waiting for TX-ready. Each TX byte at 9600 is a
|
||||
// ~1ms wait and the Z8530 RX FIFO is 3 bytes - an unpumped uplink mid-downlink-burst
|
||||
// measurably ate 4-byte clumps out of arriving frames.
|
||||
jlpSerialPoll();
|
||||
guard++;
|
||||
if (guard == 0u) { // wrapped 65536 spins -- link stalled
|
||||
return n;
|
||||
|
|
@ -310,5 +364,6 @@ uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
|||
}
|
||||
*gData = buf[n];
|
||||
}
|
||||
jlpSerialPoll();
|
||||
return n;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,9 +211,31 @@ void jlpShutdown(void) {
|
|||
if (!gModeSet) {
|
||||
return;
|
||||
}
|
||||
// Restore Human68k's Timer C vector BEFORE dropping supervisor mode: the chained x68kTickIsr
|
||||
// lives in this program's RAM, and leaving $114 pointing at it after exit sends the OS clock's
|
||||
// next tick into freed memory (the dangling-ISR trap this port already fixed for the SCC RX
|
||||
// vectors in jlpSerialClose).
|
||||
if (gTickHooked) {
|
||||
static volatile uintptr_t addr = X68K_TICK_VEC_ADDR;
|
||||
volatile uint32_t *vec = (volatile uint32_t *)addr;
|
||||
uint16_t sr;
|
||||
|
||||
__asm__ volatile ("move.w %%sr,%0\n\tori.w #0x0700,%%sr" : "=d" (sr));
|
||||
*vec = x68kTickChainAddr;
|
||||
__asm__ volatile ("move.w %0,%%sr" :: "d" (sr));
|
||||
gTickHooked = false;
|
||||
}
|
||||
if (gPrevCrtMode >= 0) {
|
||||
_iocs_crtmod(gPrevCrtMode);
|
||||
}
|
||||
// Hand the mouse back the way the lines above hand back crtmod, the Timer
|
||||
// C vector and user mode. jlpInputInit boxed the IOCS cursor tracker into
|
||||
// the 320x200 stage with _MS_LIMIT, and that box lives in the IOCS work
|
||||
// area, which outlives the program -- leaving it set would confine the
|
||||
// next Human68k program's pointer to a corner of the screen. _MS_INIT
|
||||
// re-establishes IOCS's own defaults, and it runs AFTER the crtmod restore
|
||||
// above so those defaults match the screen the user is going back to.
|
||||
_iocs_ms_init();
|
||||
gModeSet = false;
|
||||
if (gPrevSsp >= 0) {
|
||||
(void)_dos_super(gPrevSsp); // back to user mode for Human68k
|
||||
|
|
@ -457,10 +479,38 @@ void jlpInputInit(void) {
|
|||
}
|
||||
(void)_iocs_b_keyinp();
|
||||
}
|
||||
|
||||
// The mouse plugs into the keyboard and reaches the machine over
|
||||
// SCC channel B, which IOCS owns end to end -- _MS_INIT (re)arms
|
||||
// that path and zeroes its work area so boot-time motion never
|
||||
// leaks into the first poll. This runs before jlSerialOpen can
|
||||
// steal channel A's RX vectors, so the two SCC clients never touch
|
||||
// the chip at the same time.
|
||||
//
|
||||
// Position comes from the IOCS cursor tracker (_MS_CURGT), not
|
||||
// from _MS_GETDT deltas: the IPL 1.0 ROM's _MS_GETDT work area
|
||||
// holds the LAST packet's displacement and is not cleared by
|
||||
// reading (verified against the real ROM under MAME by
|
||||
// verify-x68000-mouse.sh), so re-reading it between packets
|
||||
// re-counts stale motion. The tracker integrates each packet
|
||||
// exactly once at receive time and clamps to the _MS_LIMIT box,
|
||||
// which is set to stage coordinates here -- no cursor is ever
|
||||
// shown (_MS_CURON is never called), the tracker just does the
|
||||
// bookkeeping.
|
||||
_iocs_ms_init();
|
||||
_iocs_ms_limit(0, 0, SURFACE_WIDTH - 1, SURFACE_HEIGHT - 1);
|
||||
_iocs_ms_curst(SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2);
|
||||
gMouseAttached = true;
|
||||
gMouseX = SURFACE_WIDTH / 2;
|
||||
gMouseY = SURFACE_HEIGHT / 2;
|
||||
}
|
||||
|
||||
|
||||
// The _MS_LIMIT box is handed back in jlpShutdown instead of here: it has to
|
||||
// be re-established AFTER the CRT mode is restored, and core's jlShutdown
|
||||
// calls this first.
|
||||
void jlpInputShutdown(void) {
|
||||
gMouseAttached = false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -514,6 +564,8 @@ void jlpInputPoll(void) {
|
|||
uint16_t key;
|
||||
uint16_t drain;
|
||||
uint8_t scan;
|
||||
uint32_t mousePos;
|
||||
uint32_t mouseButtons;
|
||||
|
||||
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
|
||||
|
|
@ -546,6 +598,25 @@ void jlpInputPoll(void) {
|
|||
}
|
||||
jlInputCharPush((uint8_t)(_iocs_b_keyinp() & 0xFFu));
|
||||
}
|
||||
|
||||
// Mouse position: the IOCS cursor tracker already integrated and
|
||||
// clamped every SCC channel B packet into the _MS_LIMIT box set at
|
||||
// init (stage coordinates), so _MS_CURGT's packed (x << 16) | y IS
|
||||
// the pointer position -- no delta math and no second clamp here.
|
||||
// Buttons ride _MS_GETDT's low word, which holds live levels:
|
||||
// right in bits 15-8, left in bits 7-0, 0xFF held / 0x00 up. That
|
||||
// byte order is the one the real IPL 1.0 ROM produces (verified
|
||||
// under MAME by verify-x68000-mouse.sh); published summaries that
|
||||
// list left first are wrong. Neither trap blocks on the 4800-baud
|
||||
// wire -- both just read the IOCS work area.
|
||||
mousePos = (uint32_t)_iocs_ms_curgt();
|
||||
gMouseX = (int16_t)((mousePos >> 16) & 0xFFFFu);
|
||||
gMouseY = (int16_t)(mousePos & 0xFFFFu);
|
||||
|
||||
mouseButtons = (uint32_t)_iocs_ms_getdt();
|
||||
gMouseButtonState[MOUSE_BUTTON_LEFT] = (mouseButtons & 0xFFu) != 0u;
|
||||
gMouseButtonState[MOUSE_BUTTON_RIGHT] = ((mouseButtons >> 8) & 0xFFu) != 0u;
|
||||
gMouseButtonState[MOUSE_BUTTON_MIDDLE] = false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -134,8 +134,37 @@ class Xdf:
|
|||
|
||||
# ----- File operations -----
|
||||
|
||||
def find_in_subdir(self, dirname, leaf):
|
||||
# Resolve SUB/LEAF: a subdirectory's data clusters are themselves a directory table. xdftool
|
||||
# only ever creates one-cluster subdirs, but the guest may have grown one, so follow the chain.
|
||||
_, _, de = self.find(dirname)
|
||||
if de is None or not (de[11] & ATTR_DIR):
|
||||
return None
|
||||
want = self.encode_name(leaf).upper()
|
||||
cluster = struct.unpack("<H", de[26:28])[0]
|
||||
guard = 0
|
||||
while 2 <= cluster < EOC_MIN:
|
||||
base = self.cluster_offset(cluster)
|
||||
for i in range(self.cluster_bytes // DIR_ENTRY_SIZE):
|
||||
e = self.data[base + i * DIR_ENTRY_SIZE:base + (i + 1) * DIR_ENTRY_SIZE]
|
||||
if e[0] == FREE_MARKER:
|
||||
return None
|
||||
if e[0] == DELETED_MARKER or (e[11] & (ATTR_VOLUME | ATTR_DIR)):
|
||||
continue
|
||||
if bytes(e[0:11]).upper() == want:
|
||||
return e
|
||||
cluster = self.fat_get(cluster)
|
||||
guard += 1
|
||||
if guard > self.max_cluster:
|
||||
return None
|
||||
return None
|
||||
|
||||
def read_file(self, name):
|
||||
_, _, e = self.find(name)
|
||||
if "/" in name: # SAVES/RN.CFG: resolve through the subdirectory
|
||||
sub, _, leaf = name.partition("/")
|
||||
e = self.find_in_subdir(sub, leaf)
|
||||
else:
|
||||
_, _, e = self.find(name)
|
||||
if e is None:
|
||||
raise FileNotFoundError(f"{name} not in {self.path}")
|
||||
size = struct.unpack("<I", e[28:32])[0]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue