Skip to content

Add Macintosh IIfx machine + SCC/SWIM IOP + OSS subsystems#31

Merged
pappadf merged 13 commits into
mainfrom
iifx
May 17, 2026
Merged

Add Macintosh IIfx machine + SCC/SWIM IOP + OSS subsystems#31
pappadf merged 13 commits into
mainfrom
iifx

Conversation

@pappadf
Copy link
Copy Markdown
Owner

@pappadf pappadf commented May 17, 2026

Summary

New top-level Macintosh IIfx machine target, plus the two cross-cutting subsystems it needs that no other machine in the tree had:

  • IOP (Apple 343S1021 PIC) frameworkiop.c shared core + iop_scc.c and iop_swim.c per-IOP behavioural models. Implements the full host-visible mailbox protocol over shared RAM: PIC register window, iopRamAddr / iopRamData / iopStatCtl semantics, alive-handshake, code-load, soft-restart via PatchReqAddr, and Int0/Int1 dispatch into the host.

    • SWIM IOP slot-2 SonyIOP — all 17 xmtReq floppy commands (Initialize / DriveStatus / Read / Write / Format / Eject / StartPolling / …), async rcvReq events (DiskInserted/Ejected/StatusChanged), and the DriveKinds qDrive-numbering convention.
    • SWIM IOP slot-3 ADBMsg — explicit-command and implicit-autopoll Flags paths, DevMap latching via SetPollEnables, reply ADBCmd echo of the actually-issued Talk command so the OS's pollCmd/pollAddr globals stay coherent.
    • SCC IOP — bypass-mode passthrough at \$50F04020-3F + firmware-driven mailbox mode.
  • OSS (Operational System Switch) — IIfx-only interrupt controller. Per-source pending bitmap + per-source priority bytes, autovector arbitration, and source-6/source-7 wiring for the two IOPs' hint lines.

  • IIfx machinesrc/machines/iifx.c ties everything together: 68030 + 16 MB RAM + SWIM IOP + SCC IOP + OSS + ADB-via-IOP + JMFB video as slot $9 NuBus card + RTC + VIA1. Boots System 7.0.1 from a 1.44 MB MFM floppy through POST + ADB scan + .Sony driver init + boot blocks + System loader to the Finder desktop, and brings up the "About This Macintosh" dialog via Apple-menu interaction.

  • Optional RPU bus-error model at `$50F1E000` — POST phase $93 probes the optional RAM Parity Unit with ~32 BSET/BCLR/MOVE.L cycles all expected to bus-error. Returning anything but a bus-error sets AddrMapFlags bit 20 (RPUExists) and trips System 7's `ParityINIT` modal at Finder time. The model bus-errors every access in \$50F1E000..\$50F1E01F, matching the real BIU30's behaviour on unequipped IIfxs.

Incidental supporting changes

  • ADB type-00 sub-command fix: \$X1 is Flush, not broadcast SendReset. The old fallthrough wiped the device-address remap and made the OS loop probing the same devices forever after any Flush.
  • NULL-via tolerance in ADB: adb_init / set_adb_int short-circuit when via == NULL (IOP-based machines have no direct VIA wiring for vADBInt).
  • adb_iop_transact public API: lets the SWIM IOP issue a single ADB command through the existing ADB device model and read back the reply.
  • NuBus IRQ routing through machine->update_ipl for machines without VIA2 (IIfx).
  • floppy_drive_image(floppy, drive) accessor.
  • ROM table entry for the IIfx ROM (4147DD77).

Documentation

  • docs/iifx.md (803 lines, new): full IOP reference — register window, iopStatCtl per-bit semantics, shared-RAM layout, mailbox protocol (both directions), slot-2 SonyIOP byte protocol with all 17 commands + 3 events, slot-3 ADBMsg byte protocol with all five Flags bits, the RPU bus-error probe, and per-IOP specifics for SCC vs SWIM.

Test plan

  • make -f Makefile.headless builds clean (no warnings beyond the pre-existing strncpy truncation).
  • make -C tests/integration test-iifx-boot PASSes — boots System 7.0.1 to Finder, matches finder.png, brings up the Apple-menu "About This Macintosh" dialog, matches about.png.
  • Deterministic: same instruction count (269,238,974) across repeated runs.
  • Tight budget: full test runs in ~20 s wall-clock (~294 M instructions total).
  • Per-commit-builds: verified that the originating "Add Macintosh IIfx machine" commit (ed36e07) compiles standalone after the rebase onto current main.
  • Existing SE/30, IIcx, Plus integration tests unaffected (worth re-running on CI).

pappadf and others added 13 commits May 17, 2026 21:33
Implements the Macintosh IIfx (model=iifx) and the supporting chip
models, 68030 PMMU fix, and integration hooks needed for the System ROM
to boot from POST through the ADB device-table scan to the "insert disk"
prompt screen.
Implements the Macintosh IIfx (model=iifx) and the supporting chip
models, 68030 PMMU fix, and integration hooks needed for the System ROM
to boot from POST through the ADB device-table scan to the "insert disk"
prompt screen.
Replaces the previous "ack and ignore" stub for XmtMsg[2] traffic with a
complete C reimplementation of the .Sony driver protocol exposed by the
SWIM IOP firmware:

- All 17 xmtReq* host→IOP commands (Initialize, ShutDown,
  Start/StopPolling, SetHFSTagAddr, DriveStatus, Eject, Format,
  FormatVerify, Read, Write, ReadVerify, CacheControl, TagBufferControl,
  GetIcon, DiskDupInfo, GetRawData).  Read/Write transfer through the
  image_t via disk_read_data/disk_write_data and DMA into host RAM via
  the global memory_map.
- Async rcvReq* IOP→host events (DiskInserted, DiskEjected,
  DiskStatusChanged) emitted from a 20 ms drive-poll scheduler tick
  whenever floppy_drive_image() state changes.
- DriveStatus and ExtDriveStatus synthesised from floppy module state
  (track, write-protect, in-place, sides, format, controller kind).

Adds floppy_drive_image() to expose the disk image_t* for sector I/O
without leaking the floppy internals to the IOP layer.

Trims two stale private-docs path references from iop.c comments.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The previous swim_read_blocks/swim_write_blocks used memory_write_uint8 /
memory_read_uint8, which route through the host CPU's current MMU SoA
tables.  When iop_swim_on_host_kick runs mid-instruction, those tables
reflect whatever supervisor/user context the host happens to be in —
and on the IIfx (MMU on with explicit TT0/TT1 setup) the write fast
path was silently dropping the bytes for the .Sony driver's boot-block
read buffer at $00400E6C, so the host saw the boot-time RAM scrub
pattern instead of "LK ...".

Replace with a direct write into the memory_map's flat RAM image via
ram_native_pointer, which is what the PIC's DMA controller does on
real hardware (DMA bypasses the CPU MMU and targets physical RAM).
Bounds-check against ram_size to guard against bogus BufferAddr from
mis-formed requests.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The .Sony driver's @AddDriveLoop iterates over DriveKinds[0..N] passing
the index `d1` as the drive-number argument to _AddDrive.  With our
previous DriveKinds = {04, 04, 00, 00} the first physical floppy was
registered at d1=0, giving qDrive=0 in the drive queue.  The boot
drive search loop at $40801360 then walked head-first, found that
qDrive=0 entry, wrote BootDrive ($0210) := 0 via $408015E8, and read
the boot block successfully — but the boot block code itself ($00400F9C)
copied BootDrive into ioVRefNum and called _MountVol, which rejects
ioVRefNum=0 with paramErr (-50).  The ROM's failure fallback at
$4080203E then ejected the disk and reset the IOP, looping forever.

Fix: advertise DriveKinds = {00, 04, 04, 00} so .Sony skips d1=0 and
adds drives starting at d1=1 (= qDrive=1).  Add a thin floppy_idx ↔
.Sony-drive-number mapping (swim_floppy_to_drvnum /
swim_drvnum_to_floppy) and route all per-drive handlers
(DriveStatus / Eject / Read / Write / Format) through it so the
physical floppy module index stays 0-based while the .Sony driver
sees 1-based numbers.

With this in place, the IIfx boots from the System 7.0.1 floppy:
trace shows xmtReqRead progressing through the HFS Master Directory
Block (blk=2), volume bitmap, catalog B-tree, and many subsequent
block reads as the OS loads the System file.  Final screen at
~580M instructions: System 7 parity-circuitry warning dialog
("Shut Down / Continue"), confirming we're now in user-mode OS code.

See local/gs-docs/asm/IIfx-ROM.asm §20-§23 for the instruction-level
trace that led to this fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The IIfx ships with a RAM Parity Unit (RPU) chip whose self-test fires
a level-7 NMI on bad-parity reads.  Our emulator doesn't model the
chip's register surface or NMI path, so the boot's POST self-test
fails and either disables parity or leaves it in an indeterminate
state.  System 7's ParityINIT then sees AddrMapFlags advertising
RPUExists (bit 20) but finds parity "not enabled" via _Gestalt — that
combination is exactly the @parityOff path which posts the
"Parity has been disabled because the parity circuitry is not
functioning" alert and stalls the boot at a modal dialog.

Minimal fake: after POST has installed the real AddrMapFlags
(signature: low three bits set = ROM/DiagROM/VIA1 exist), strip
RPUExists from the long.  The OS's gestaltParityAttr handler then
takes the @parityExit branch and returns zero — "no parity
capability" — which makes ParityINIT skip the dialog entirely.

Implementation runs from the existing VBL tick; uses ram_native_pointer
to write the physical-RAM image directly (bypasses MMU like the SWIM
IOP DMA path does).  Guarded on the post-POST AddrMapFlags signature
so the patch doesn't fire while POST is still scrubbing RAM with
test patterns.

Boot now reaches the System 7 Finder desktop from the floppy:
"Disk Tools" volume mounted, menu bar visible, Trash icon present
— no parity dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The IIfx has an optional RAM Parity Unit (RPU) chip whose base lives
at $50F1E000 (DecoderInfo.RPUAddr).  On real hardware the chip is
only present when parity SIMMs are installed; without it, the BIU30
leaves the address undecoded and every access bus-errors.

POST phase $93 ($408430DC) probes $50F1E000 with 32 BSET/BCLR/MOVE.L
cycles and expects every one to bus-error.  A successful probe (= "no
RPU") leaves AddrMapFlags bit 20 (RPUExists) clear; an installed RPU
sets the bit.  System 7's gestaltParityAttr handler then either takes
@parityExit (returns 0 = "no parity capability") or @checkRPU /
@parityOff (= "capability present but not enabled" → ParityINIT
dialog).

Our prototype previously had a generic R/W backing store covering the
entire OSS-extension window ($50F1C000-$50F1FFFF), which absorbed
POST's probe writes silently — POST falsely concluded "RPU installed"
and AddrMapFlags carried bit 20 into System 7, where the gestalt
check found parity disabled and posted the warning dialog.

Fix: in the I/O dispatch, route $50F1E000-$50F1E01F through
iifx_bus_error() on both read and write, leaving the rest of the
OSS-extension window alone.  POST now sees the bus errors phase $93
needs, leaves RPUExists clear, and System 7 boots straight to the
Finder desktop with no parity dialog.

Replaces the earlier b589fa9 software patch (reverted in b91832d)
that cleared AddrMapFlags bit 20 from a VBL hook — that was a
lowmem patch real hardware would never do.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Wire the existing ADB device-state machine (adb_t) into the IIfx via
the SWIM IOP's slot-3 mailbox.  The IIfx ADB bus is bit-banged by the
PIC's 65C02 firmware ($F032 in iop-swim.bin), not by VIA1's shift
register; the OS posts each ADB command to XmtMsg[3] and reads the
reply from RcvMsg[3].

  - adb_iop_transact(cmd, in_data, in_len, out_data, out_len_ptr):
    new public API on adb.c that dispatches Talk / Listen / Reset /
    Flush through the same prepare_talk_reply / apply_listen_data
    helpers the VIA-shift path uses, but returns the Talk reply by
    value instead of clocking it out via_input_sr().

  - adb_init() now tolerates NULL via: set_adb_int / adb_autopoll
    short-circuit when the machine is IOP-based.  Autopoll is the
    SWIM IOP firmware's job in this configuration, not adb.c's.

  - iifx_init() constructs adb_t with via=NULL and exposes it via
    cfg->adb, so system_mouse_update / adb_keyboard_event route to
    the SWIM IOP transparently.  Teardown + checkpoint plumbing
    added to match.

  - swim_handle_xmt_slot / swim_adb_response / swim_handle_rcv_slot
    snapshot the Listen-side ADBData into SWIM_MODEL_ADB_DATA before
    the host's XmtMsg buffer can be overwritten, then call
    adb_iop_transact when the deferred response fires.  The reply's
    Flags byte clears NoReply when a device acknowledged (including
    zero-data Flush / Listen acks) and sets it otherwise.

  - adb_decode_command's type-00 case now distinguishes SendReset
    (\$X0) from Flush (\$X1) per Inside Mac V section 6.2.  Earlier
    code treated every type-00 command as a broadcast reset, which
    would wipe an address remap set up by Listen-R3 immediately
    before.

With this in place, the IIfx boot's ADB device-discovery scan now
sees real Talk-R3 replies from the keyboard at addr 2 and the mouse
at addr 3 — same data that the SE/30 and IIcx tests get through the
VIA-shift path.  Apple-menu interaction from mouse.click doesn't yet
pull the menu down: the OS's ADB Mgr enters a Flush-keyboard (\$21)
loop instead of the expected Talk-R0 autopoll cycle, so host-side
mouse-button changes aren't sampled.  Filed for follow-up.

iifx-boot / iix-floppy / se30-floppy-hd / iicx-mactest all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Updates the iifx-boot integration test from "boot to ? disk prompt"
to "boot from System 7.0.1 floppy and verify the Finder desktop"
(the IIfx analogue of se30-floppy-hd).

Replaces question-mark-640x480.png with finder.png (640x480 grey
desktop with menu bar, "Disk Tools" volume icon, Trash icon, mouse
cursor at top-left) — the same scenario se30-floppy-hd / iix-floppy
verify on their respective machines.

Adds two new asserts beyond the rebaselined screen.match:

  - fDBInit clear at $5405 (carried over: confirms the ADB device-
    table scan finished).
  - AddrMapFlags bit 20 (RPUExists) clear: confirms POST phase $93's
    bus-error probe of $50F1E000 (commit a2f2e34) saw the bus
    errors it expected and didn't advertise the optional RPU
    parity unit, so System 7's ParityINIT skips its dialog.

Exercises end-to-end through:
  - Full IIfx ROM POST (PMOVEFD, FMC ROM-mirror invert, OSS, IOPs,
    RPU bus-error probe).
  - SCC + SWIM IOP firmware load and behavioural-model start.
  - SWIM IOP slot-3 ADB protocol (Talk-R3 device probe + Listen-R3
    address remap + Flush + autopoll via adb_iop_transact).
  - SWIM IOP slot-2 .Sony driver protocol (Initialize / SetHFSTagAddr
    / StartPolling / DiskInserted async event / DriveStatus / Read).
  - Boot block code at $00400EF6, ROM-side System loader at
    $4080203E + region.

Note: this test verifies the boot reaches Finder; it does NOT yet
exercise the Apple-menu / About-dialog interaction.  Host-side
mouse.click currently doesn't pull the menu down on IIfx because
the OS's ADB Mgr enters a Flush-keyboard ($21) retry loop instead
of the expected Talk-R0 autopoll cycle, so host-side button
changes aren't sampled.  Tracked for follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The IIfx's ADB Manager (Sys 7 source: OS/IoPrimitives/ADBPrimitives.a,
IopADB branch) splits slot-3 traffic into two distinct flavours:

  - Explicit (Flags has ExplicitCmd bit 7): host fills ADBCmd with a
    specific Talk / Listen / Reset / Flush byte and expects the
    firmware to issue it on the bus.

  - Implicit autopoll (Flags has PollEnable bit 6 — set by IOPStartReq
    when fDBExpActive is already 1 or when no explicit cmd is queued):
    the ADBCmd field is meaningless (just whatever stale byte the
    XmtMsg buffer happened to hold), and the firmware is expected to
    pick the next enabled device from its DevMap and Talk-R0 it on
    its own.  When SetPollEnables bit 5 is also set, ADBData carries
    a fresh 2-byte DevMap that should replace the current poll mask.

Previously swim_adb_response always dispatched the host's ADBCmd byte
through adb_iop_transact, which on autopoll messages produced a
nonsensical Flush of whatever device happened to share the byte's
high nibble (typically the keyboard).  The OS's IOPReqDone then took
the ImplicitRequestDone branch — fDBExpActive stayed set, the cmd
queue head never advanced, ADBOpTrap returned qErr, sendFlush retried
forever, and host-side mouse clicks were never sampled because no
Talk-R0 ever ran for the mouse address.

Fix: in swim_adb_response, branch on the request's Flags:

  - ExplicitCmd path: keep the existing adb_iop_transact dispatch on
    ADBCmd.  Echo ExplicitCmd back in the reply so IOPReqDone takes
    ExplicitRequestDone and advances the cmd queue.

  - Autopoll path: cycle through ADB addresses 1..15 (round-robin
    pointer in SWIM_MODEL_AUTOPOLL_ADDR, biased by SWIM_MODEL_DEVMAP
    if the OS has installed a poll bitmap), Talk-R0 each via
    adb_iop_transact, return the first non-NoReply reply.  Set the
    reply ADBCmd to the actual Talk command issued so the OS's
    pollCmd / pollAddr globals reflect which device responded.

  - SetPollEnables: latch the 2-byte DevMap bitmap from ADBData into
    SWIM_MODEL_DEVMAP and clear the SetPollEnables bit on the reply
    (it's a one-shot bit per IOPStartReq's @implicit / @explicit
    layout).

With this in place, the IIfx host-side mouse.click + Apple-menu
sequence now drops the menu and selects "About This Macintosh..."
— the dialog renders "Macintosh IIfx" / "System Software 7.0.1" /
"Total Memory: 16,384K" exactly as the IIfx ROM advertises itself.

iifx-boot now matches both finder.png and about.png; iix-floppy /
se30-floppy-hd / iicx-mactest still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The existing §16 only said "host posts XmtMsg requests describing the
floppy operation" / "Talk and listen transactions are issued as XmtMsg
requests".  That hand-wave is now backed by three new sections:

  §17  SwimIopMsg payload layout, all 17 xmtReq codes, the 3 rcvReq
       event codes, and the DriveKinds qDrive-numbering convention
       (why DriveKinds[0] must be noDriveKind on the IIfx — Mac OS
       rejects ioVRefNum=0 in _MountVol).

  §18  ADBMsg payload layout, the Flags bit semantics (ExplicitCmd /
       PollEnable / SetPollEnables / SRQ / NoReply), and the two
       transport flavors (explicit vs implicit autopoll).  Documents
       that the reply ADBCmd must echo the actually-issued Talk
       command (autopoll round-robin) so the OS's pollCmd / pollAddr
       globals stay coherent.

  §19  RPU bus-error probe at \$50F1E000.  POST phase \$93 runs ~32
       BSET/BCLR/MOVE.L cycles all expected to bus-error; success
       sets AddrMapFlags bit 20 (RPUExists) and triggers
       ParityINIT's "Parity has been disabled" modal at Finder time.

§16's slot table is updated to make the channel/protocol assignment
explicit (1 = IOPMgr kernel, 2 = SonyIOP, 3 = ADBMsg).
Finder paints stable by ~225M instructions on this boot path, and
the post-click About-dialog handshake settles much faster than the
previous "leave plenty of headroom" budgets suggested.  Verified
deterministic (269,238,974 instructions at the final about-match)
across three fresh-boot runs.

  scheduler.run 700000000  →  260000000    (boot to Finder)
  scheduler.run  50000000  →   10000000    (after click true; menu drop)
  scheduler.run  40000000  →   10000000    (between Apple-menu moves)
  scheduler.run 200000000  →   10000000    (after click false; About paint)

Wall-clock on a Codespaces sandbox drops from ~50s+ to ~20s.
@pappadf pappadf merged commit 09fde01 into main May 17, 2026
2 checks passed
@pappadf pappadf deleted the iifx branch May 17, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant