Case Study: Z3660 ethernet driver (zen0)¶
This is the network analogue of the A4091 / 53C710 SCSI driver: a
brand-new native driver for a board Amix was never meant to support, written, built, deployed and
validated end-to-end on real hardware. Where the A4091 work got Amix to boot with root on a
Zorro III SCSI controller, this work gives Amix full bidirectional TCP/IP over the Z3660
accelerator's onboard ethernet, as the interface zen0. It pulls together the
STREAMS driver model, the kernel build and the
networking stack — applied at once to a real combo board.
The work is first-party and reproduced on real hardware — written, built, deployed and run on a
physical Amiga 4000 + Z3660 during 2026-06. Unless noted, every claim here is ✅ Verified
(driver source we wrote, firmware source we read, or a result reproduced live on the hardware);
inferences and unconfirmed items are tagged 🟡 inline, carried verbatim from the source brief per the
repo's confidence-tag policy. The
source is the amix-z3660net project — src/z3660eth.{c,h}, driver.conf, userland/S99zen — the
sibling amix-z3660 SCSI project's ETHERNET-SCOPING.md, and the Z3660 firmware source.
One-line summary. Amix now has full TCP/IP over a native SVR4 STREAMS/DLPI driver (
zen0) for the Z3660's onboard ethernet — not NIC emulation. The only real-hardware blocker turned out to be an interrupt storm, fixed driver-side with no firmware change. ✅
What the Z3660 and z3660eth are¶
The Z3660 is a 68030/68060-class Zorro III accelerator built around a Xilinx Zynq-7000
SoC ✅. The Zynq PS runs firmware (Z3660.bin, loaded by BOOT.bin) that emulates the 68k and
drives the card's real peripherals — including the card's Zynq Gigabit Ethernet MAC (GEM) via the
Xilinx XEmacPs driver. Ethernet, audio and USB all hang off a piscsi-style register/mailbox
interface in the card's RTG register page ✅.
z3660eth is a native Commodore Amiga Unix (Amix, SVR4.0, m68k 68030) STREAMS/DLPI ethernet
driver for that onboard GEM ✅. It is the network counterpart of the amix-z3660 SCSI driver
(z3660.c) for the same combo board.
It is not NIC emulation. The firmware already moves whole ethernet frames between the 68k guest
and the Zynq GEM — it does this for AmigaOS through the SANA-II Z3660Net.device. z3660eth speaks
that same firmware mailbox (a small MMIO register protocol over the Zorro III window) and presents
it to Amix as a DLPI Style-1 connectionless ethernet provider, brought up with the stock slink +
ifconfig STREAMS plumbing as interface zen0 ✅. Contrast the hydra-amix driver,
which programs a real DP8390/NE2000 chip directly; here the chip-level work lives in the card's
firmware and the driver talks to it over a register protocol.
| Property | Value | Tag |
|---|---|---|
| Driver tag / interface | zen → zen0 |
✅ |
cdevsw major |
51 — originally 48 (the free nostr slot on the stock / A4091 kernel), renumbered 2026-07-30; 51 verified in a shipped kernel on real hardware 2026-09-09 |
✅ |
| Board AutoConfig id | 0x144B0001 |
✅ |
| Fixed combo base (AGA fallback) | 0x10000000 |
✅ |
| Station MAC observed on the wire | 00:80:51:01:02:03 |
✅ |
| Example IP | 192.168.2.39 (the build box keeps .38) |
✅ |
| DLPI style / limits | Style-1; Z3660ETH_MAXDEV = 5 SAP streams, Z3660ETH_MAXBOARDS = 1, MTU 1500 |
✅ |
The firmware mailbox: the protocol contract¶
All register offsets are board-base-relative, 32-bit big-endian MMIO, living in the RTG register
page at board_base + 0x000 (piscsi is at +0x2000) ✅. Every offset was verified against the Z3660
driver headers (z3660_regs.h) and the firmware register enum/dispatcher (rtg/zzregs.h,
rtg/rtg.c); the in-repo source of truth is src/z3660eth.h ✅.
Registers¶
| Name | Offset | Dir | Meaning |
|---|---|---|---|
ZZ_CONFIG |
0x104 |
W | 1 = enable INT6 delivery, 0 = disable, 24 (8\|16) = ack/clear ETH |
ZZ_ETH_TX |
0x190 |
W / R | W byte-length = trigger TX (synchronous, ~1 ms); R = TX result |
ZZ_ETH_RX |
0x194 |
W | write (any value) = advance / free the current RX slot |
ZZ_ETH_MAC_HI |
0x198 |
R/W | MAC bytes [0..1] = (b0<<8)\|b1 |
ZZ_ETH_MAC_LO |
0x19C |
R/W | MAC bytes [2..5]; a write triggers the GEM SetMac |
ZZ_ETH_RX_ADDR |
0x1A4 |
R | board-relative byte offset of the current RX slot |
ZZ_INT_STATUS |
0x1A8 |
R | pending bits: ETH = 1, AUDIO = 2, USB = 4 |
On the firmware side (rtg/rtg.c) these dispatch to REG_ZZ_CONFIG (~line 1150), REG_ZZ_ETH_TX →
ethernet_send_frame() (~1609), and REG_ZZ_ETH_RX → ethernet_receive_frame() (~1613) ✅.
Frame windows¶
The frame buffers are real DDR reached through the Zorro III window, at fixed board-relative offsets
(from the firmware memorymap.h) ✅. Note the board base is 0x10000000, so these windows land
inside the identity-mapped low 1 GB (< 0x40000000) and
are directly reachable — this is not the A4091's above-the-identity-map
case; sptalloc here was simply a clean way to map the full 128 KB region (and, on a large-memory
machine, the wrong trade — see the >80 MB sptmap exhaustion below):
- RX backlog ring —
0x07ED0000, 32 slots × 2048 bytes = 64 KB. - TX staging frame —
0x07EE0000, one frame. - GEM RX frame —
0x07EF0000. - Per-slot stride — 2048 bytes; per-slot header — 4 bytes:
[0..1] = length,[2..3] = serial(a monotonic frame serial used as the drain sentinel). The ethernet payload follows the 4-byte header.
🟡 Latent firmware bug (documented, mitigated, not the cause of any observed failure). The firmware constant
FRAME_MAX_BACKLOGis 64 while only 32 real slots exist before the TX window — a 64-deep backlog would overrun the ring. The driver mitigates by eager-draining every poll tick and by mapping the full 64-slot / 128 KB range, so anyZZ_ETH_RX_ADDRvalue the firmware returns is always mapped memory. No overrun was observed in practice.
TX / RX mechanics and the FCS boundary¶
- TX ✅ — copy the frame (min-padded to 60 bytes) into the TX window, then write the byte length to
ZZ_ETH_TX. The trigger is synchronous (~1 ms) with no completion IRQ; readingZZ_ETH_TXback returns the result code (res = 0= OK, observed on the wire). The single shared TX window is serialized byinfo->tx_busy; the ~1 ms trigger is not held at raised spl. - RX ✅ — drained by re-reading
ZZ_ETH_RX_ADDR, comparing each slot's serial header againstinfo->last_serialto find fresh frames, delivering each up the stream, and strobingZZ_ETH_RXto free the slot. The drain loop is bounded (≤ 256 iterations per call) so a runaway firmware can't wedge the kernel. - FCS / minimum-length boundary ✅ — the firmware delivers the raw
XEmacPs_BdGetLengthand does not strip the FCS (there is noFCS_STRIPoption). So the driver pads TX up to the 60-byte minimum and does not subtractETH_CRC_LENfrom the RX length — it must not inherit the stockaendriver's-4. (Same 60-vs-64 CRC boundary as the hydra RX min-frame gotcha, handled the other way round: hydra's DP8390 reports a length that includes the CRC, so it accounts for the 4 bytes; the Z3660 firmware hands back the raw length, so the driver must not subtractETH_CRC_LEN. Both honour the 60-byte post-CRC minimum.)
Driver architecture¶
src/z3660eth.c is a ~1034-line STREAMS/DLPI skeleton adapted from the stock Amix aen (LANCE) and
hydra drivers, with the chip layer replaced by the Z3660 mailbox ✅.
Function map (all in src/z3660eth.c)¶
| Function | Role |
|---|---|
z3660eth_map() |
sptalloc + map the register page and the 64-slot frame region; fills board_base/regs/frame/txwin/paddress. Returns ENXIO when no Z3660 GEM is present |
z3660eth_autoconfig() |
discovery: autocon(0x144B0001), else (AGA only) the fixed combo base 0x10000000, verified via the firmware MAC register |
z3660eth_initialize() |
writes ZZ_CONFIG, primes last_serial, starts the RX poll callout |
z3660ethopen / z3660ethclose |
lazy-open: autoconfig on first open (like aen) |
z3660ethwput / z3660ethwsrv |
STREAMS write put / service |
z3660ethxmit() |
TX path: pad, copy to txwin, optional cache push, write ZZ_ETH_TX |
z3660eth_drain() |
the bounded RX drain (serial sentinel + ZZ_ETH_RX strobe) |
z3660eth_poll() |
the timeout() callout that calls z3660eth_drain each tick |
z3660eth_tx_drain() |
TX-side completion bookkeeping |
z3660ethintr() |
future interrupt entry point (not wired into int2_tbl yet) |
z3660ethproto() |
DLPI primitive dispatch (bind / unbind / attach / unitdata / …) |
toss_packet_up_stream() |
deliver a received frame up the matching SAP stream |
z3660ethioctl() |
the zen.c presence / status ioctls |
z3660ethinit() |
driver init |
Key design decisions¶
- Lazy-open + polled RX → no
int2_tbl/init_tbledit. ✅ The driver autoconfigs on the firstopenand services RX from a clock-leveltimeout()poll callout (periodZ3660ETH_POLL_TICKS = 1). This is why a GEM-less build box boots cleanly andopen()simply returnsENXIO— there is no boot-time probe that can panic. It is a deliberately different choice from hydra's "probe at boot (init_tbl/hydrainit), full-init on open" split — see Writing a STREAMS driver. - No
spl6()/splx(). ✅ Those primitives do not exist in Amix SVR4.0 — they are a BSD/hydra idiom. The stockaendriver andz3660.cdo zero explicit interrupt masking, so inz3660eth.hthey are no-ops:#define splz3660eth() 0and#define splx(s) ((void)(s)). Referencing the nonexistent symbols had left them undefined in the kernel link, which thenm -uclean-gate (below) rejects — a real build-breaker we hit and fixed. - DLPI Style-1, up to
Z3660ETH_MAXDEV = 5SAP streams per board,Z3660ETH_MAXBOARDS = 1, MTU 1500 ✅.
Kernel integration¶
A STREAMS network driver is not registered in sd.c's scsicard[] table (that is the SCSI-stack
path the A4091 driver uses) — it is wired directly
into master.d/kernel.c's cdevsw[] and built in its own amiga/driver/<dir>/ subtree. Three edits
plus the subtree ✅:
/usr/sys/amiga/driver/z3660eth/—z3660eth.c,z3660eth.h,z3660ethuser.h,Makefile.master.d/kernel.c: addextern struct streamtab z3660ethinfo;after the stockaeninfo, then claim cdevsw slot 51 (verify it is still the empty/*51*/ … notty,nostr,…row) by replacingnostrwith&z3660ethinfo.amiga/driver/Makefile: addz3660eth/exptoOBJand az3660eth/exp:build rule.
The machine-readable form is driver.conf, consumed by the build-net-kernel.sh tool in the
amix-kerntools project ✅:
Create the node and bring the interface up. As on hydra, Amix is SVR4.0 so
there is no ifconfig plumb — you link the stream in with slink, then ifconfig ✅:
mknod /dev/zen0 c 51 0 # major 51 since 2026-07-30 (was 48)
slink # ensure the base inet streams are linked
slink addaen /dev/zen0 zen0 # STREAMS multiplexor link
ifconfig zen0 192.168.2.39 netmask 255.255.255.0 up -trailers
route add default 192.168.2.1 1 # trailing 1 = the (required) metric
★ The INT6 interrupt storm — the real-hardware blocker¶
The single most important lesson of the project ✅. On the WinUAE build box (no Z3660 GEM) the
whole integration and build path worked — the kernel boots, z3660eth registers at its cdevsw slot (48 at the time; 51 today), and
open(/dev/zen0) returns ENXIO with no panic. But on the real A4000 + Z3660:
Symptom. The kernel booted with the driver and reached login:, but the moment the interface was
brought up the box hard-locked (caps-lock LED dead = the 68k is hung hard, not merely busy). ✅
Root cause (diagnosed from the firmware serial [PC] heartbeat + nm of relocunix): the
firmware raises Amiga INT6 (EXTER) on every received frame — rtg/rtg.c sets
interrupt_enabled_ethernet from ZZ_CONFIG bit 0 (~line 1166), and the RX path calls
amiga_interrupt_set(AMIGA_INTERRUPT_ETH) (~line 336). Amix has no level-6 ethernet interrupt handler — its
p6int / aciabintr chain runs but never acks the eth source, so the LAN's normal ARP broadcast
traffic storms INT6 and instantly hard-locks the machine ✅. (Exactly the INT6 risk the scoping doc
had flagged in Phase 0.)
Fix (commit b06cf45): the driver writes ZZ_CONFIG_DISABLE (0) instead of
ZZ_CONFIG_ENABLE (1), so the firmware never raises INT6. RX is unaffected — the firmware still fills
the backlog ring regardless of the interrupt-enable flag (ethernet.c only gates a debug print on
it), so the bounded poll/drain callout keeps working. No firmware change was needed ✅.
The lesson generalizes: the firmware's interrupt model assumed an AmigaOS handler that Amix does not provide; the safe path is polled RX with the firmware interrupt explicitly disabled. This is recorded as a gotcha on the quirks checklist.
Other bugs that mattered¶
spl6/splx+rico.h-isms (Phase-1 build-breakers, commit84c9a4f). ✅ Besides the nonexistentspl6/splxsymbols (above), a STREAMS driver must spell outunsigned char/long/short— notuchar/ulong/ushort, which arerico.h-only (included by the SCSI driver, not this one). Both showed up only at kernel-link time, as undefined symbols.- The 030 data-cache flush — turned out not to be needed. ✅ The eth DDR windows are cacheable
(they sit inside the identity map), so a stale-cache TX/RX coherency hazard was the scoping's stated top risk. The driver
has an optional line-granular flush (
Z3660ETH_CACHE_FLUSH,cpushl %dcover the TX/RX windows), default OFF. On real hardware it was not required — TX and RX are byte-correct with the flush off, confirming the 030 data cache is effectively transparent to these windows here. Left OFF.
🟡 Caveat for a future maintainer: the flush helper uses GNU-style
asm volatile("cpushl …"). The Amix licensed K&Rccmay not accept GNU inline-asm syntax; if the flush is ever needed it may have to move to a separate.sfile. Untested, because it was never needed. - Auto-bring-up race (the boot script, not the driver). 🟡 On a fast clean boot the bring-upslink addaencan race ahead of the inet base STREAMS plumbing and fail to openzen0(observed once: the box reachedlogin:butzen0never came up; cause inferred from the working manual bring-up, which always ran a bareslinkfirst — not independently traced). The boot script now runs a bareslinkfirst, then retriesslink addaenin a loop (below).
The ">80 MB board not found" — sptmap exhaustion, not a map clash ✅¶
On a machine with more than 80 MB of RAM the driver stopped finding its board, and the obvious suspect was the firmware memory map: a clash between the board's Zorro III banks and the enlarged guest memory. That was falsified directly — the board's banks read byte-identical at every window, and the host DDR behind them was empty ✅.
The cause is on the kernel side, and it is the same arena the RAM ceiling
is about. page[], sptalloc() and the u-areas are all handed out of one fixed 4 MiB kernel arena
through the sptmap resource map. With ~80 MB of RAM described, page[] has taken that arena, and
this driver's 65 pages of sptalloc() mappings have nothing left to allocate from — the mapping
fails and the board simply is not there ✅.
The fix was to stop mapping. The board's fixed base 0x10000000 sits below VSECT1 — already
inside the identity-mapped low 1 GB — so the register window
and the frame windows are directly usable. Direct-mapping whenever base < VSECT1 takes the driver
from 65 sptmap pages to 0 ✅: the >80 MB failure disappears, and the driver stops competing with
the kernel for the frame array's arena.
Verified live on metal. On a running real A4000 + Z3660 the driver's z3660eth_direct_map flag
and its SCSI sibling's z3660_direct_map both read set in the live kernel, so the 98 arena pages
the pair would otherwise have consumed are a measured reclaim, not a build-time intention ✅. Reading
those flags has its own trap — they are one-byte objects and adb's int format prints a set byte as
16777216, not 1 (quirk 46).
The law worth carrying to any Amix driver: a driver's mapping budget and the machine's RAM ceiling are the same budget ✅.
sptalloc()is not free space — page-map only what you cannot reach directly.
Build & the nm -u clean-gate¶
The kernel is built on a networked Amix box (the WinUAE/Amiberry build box at 192.168.2.38).
On-box ld corrupts a high fraction of kernel links (the "D245" boot-breaker), so the link must be
repeated until it produces a clean kernel ✅ — the same hazard the A4091 build
and the kernel-build page describe.
★ The correct clean-kernel bar is nm -u, not sum -r recurrence or checkunix. ✅ This was
learned the hard way (2026-06-21) and supersedes older notes:
relocunixis literallyln unix relocunix, gated by the kernel Makefile's own test:nm -h -u unix | egrep -v '(etext|edata|end)'must be empty (no undefined symbols).- A
unixcan passcheckunix(which only validates the.symtabshape) while still having undefined symbols — socheckunixalone is insufficient. The working gate relinks until the kernel is bothnm -u-clean andcheckunix-clean, thenlns it torelocunix. (A recurringsum -ramong clean builds is gold confirmation but does not converge on this larger kernel and is not required.) - Implemented in the amix-kerntools
tools/build-clean-net-kernel.sh.
⚠️ Never delete the 22 generic SVR4
expblobs (os, io, netinet, rpc, vm, …): they ship pre-linked with no source on the box and are not regenerable. Only theamiga/tree +master.d+fs/exp+local/expare re-rolled. (Deleting them once cost a painful restore.) ✅
Other on-box build realities (✅, validated on the build box): Makefiles have no header-dependency
tracking — after editing a .h, force-recompile the dependent .o (or touch the .c) and verify
the md5; FTP is asymmetric on the a2065 build box — push (PUT) is reliable but large pull
(GET) is flaky, so patch kernel.c on the box with ed and use NFS (nfsvers=2) for large
pulls; and a fresh /usr/sys rebuild faults at $40000000 unless the amix-base DDR memory fix
(the wip-emulated-ram work) is in the image — the config that boots is the xfer-memory .uae.
Deploy & bring-up to the real box¶
The real A4000 + Z3660 has no network until zen0 is up (chicken-and-egg), so deployment is
out-of-band ✅. Two paths:
- Kernel-only shuttle — copy the clean
relocunixto the box viatransfer.hdf(a small scratch Amiga hardfile on SCSIc5d0), stage it,cd /stand; make, reboot. Never re-image the root. - Full-image / firmware deploy (no-console) — the Z3660 ARM firmware console has a TFTP
server. Power-cycle into the ARM console (spam
Cat power-on),Pto start TFTP at192.168.2.29,PUTthe artifact,Rto reboot: path0:(FAT32) holdsZ3660.bin(the firmware, ~12.3 MB, ~21 s TFTP; built withcd <your Z3660 checkout> && ./docker/run.sh make bootbin→…/Alfa/sd_card/BOOT.BIN, incremental control-core builds in seconds); path1:(exFAT) holdshdf/Amix.hdf(the full root image, ~629 MB, ~20 min TFTP). The host↔box TFTP host is the laptop wired directly to the Z3660.
⚠️ Operational gotchas (✅, all hit this session). Whatever switches power to the box, verify the serial has actually gone silent before powering back on — a remote switch reporting success is not proof the machine went down, and a cut on a still-running box costs you an
fsckon the next boot. Do not rely on remote video to read the Amiga console either: a video-capture path can show a solid black frame while the machine is perfectly alive. Detect state from the firmware serial[PC]heartbeat (pinned PC = hung; varying = alive) and, oncezen0is up, over telnet/ftp. Keep the serial stream logged to a file — a bring-up is reconstructed from the log, not from the screen.
★ Why the stock aen0 driver cannot substitute for zen0 — and why that depends on the firmware build¶
Amix ships a stock ethernet driver, aen, and on a Z3660 box it is tempting to reach for it instead
of installing zen0. Under an Amix-interop Z3660 firmware build it can never work, and the
reason is a board-ID mismatch that no configuration key can bridge ✅:
- The Amix kernel's
aenautoconfigprobes the Zorro chain for manufacturer 514 (Commodore), product 112 — the A2065 — pushing the packed id0x02020070toautocon()✅ (read from the linked kernel's disassembly). - The Amix-interop firmware presents every emulated autoconfig board under
MANUF_ID 0x144B(Double H Tech), and has A2065 emulation commented out of its embedded UAE core ✅ (Z3660_emu/src/cpu_emulator.cpp:263;uae/include/sysconfig.h:79,/* #define A2065 */). ⚠ Note the tree: there are twocpu_emulatorfiles in the firmware —Z3660/src/cpu_emulator.candZ3660_emu/src/cpu_emulator.cpp— and the autoconfig code lives only in the.cpp. A grep in the wrong tree returns nothing and makes the claim look false.
So the probe hunts an id the firmware never offers. A manufacturer id is not settable by any
autoconfig_* option, which is why the probe fails identically with autoconfig_rtg NO and YES —
measured on real hardware, both values, 2026-09-09 ✅. zen0/z3660eth instead binds the Z3660's
own product id 0x144B0001 — the same id the sibling z3660scsi driver uses to find its board, so
if root mounted, the board is demonstrably in the chain ✅.
🟡 The corollary that costs the most time: this is a property of the firmware build, not of the hardware. Other Z3660 firmware builds do present an A2065 façade, and a box running one of them networks perfectly on
aen0— observed on a second SD carrying a different build, same machine, same card, 2026-09-09. An observation that "that box networks onaen0" therefore never transfers across a firmware boundary. Establish which build is running before concluding anything about which interface can work; the runtime proof is the firmware's own[SD Init] Done (…)line, not a checksum of the files on the card.
Diagnosing it takes one command. /etc/inet/network-config brings the stock path up as a single
&& chain — aen -S && slink addaen /dev/aen0 aen0 && ifconfig aen0 … — so a failed probe
short-circuits at step one and prints nothing at all ✅. A box whose network is dead this way
produces no console output during boot, which reads convincingly like "the startup script never
ran". Run /usr/amiga/bin/aen -S; echo $? by hand: a return of 1 is the board probe failing, and
netstat -in will show aen0 with address and network none.
Boot auto-bring-up — /etc/rc2.d/S99zen¶
The amix-z3660net repo ships userland/S99zen (installed as /etc/rc2.d/S99zen). It is
backgrounded so a datapath problem can never hang the boot (the box still reaches login:), and
hardened for the bring-up race above ✅:
#!/bin/sh
# S99zen -- bring up zen0 at boot, backgrounded; cdevsw major 51 (was 48 before 2026-07-30).
[ -c /dev/zen0 ] || mknod /dev/zen0 c 51 0
(
sleep 5
/usr/sbin/slink # ensure the base inet streams are linked
i=0
while [ $i -lt 20 ]; do
/usr/sbin/slink addaen /dev/zen0 zen0 && break
sleep 3
i=`expr $i + 1`
done
/usr/sbin/ifconfig zen0 192.168.2.39 netmask 255.255.255.0 up -trailers
/usr/sbin/route add default 192.168.2.1 1
/usr/sbin/ifconfig zen0
) > /tmp/zen-bringup.log 2>&1 &
⚠️
zen0only comes up after the full boot completes (including anfsck+ reboot if the SD image is dirty). Do not conclude "broken" from an early serial check — give it the whole boot. ✅
Status — ✅ works on real hardware (2026-06-21)¶
Validated on a real A4000 + Z3660, all reproduced live ✅:
| Test | Result |
|---|---|
ifconfig zen0 |
flags=23<UP,BROADCAST,NOTRAILERS>, inet 192.168.2.39, mask ffffff00 |
Inbound flood ping (laptop → .39, 40 pkts @ 50 ms) |
40/40 received, 0% loss (~3–19 ms RTT) |
Outbound: box → laptop (ping 192.168.2.66) |
192.168.2.66 is alive |
Outbound: box → gateway (ping 192.168.2.1) |
192.168.2.1 is alive |
netstat -in for zen0 |
Ierrs = 0 Oerrs = 0 Collis = 0 (e.g. 895 Ipkts / 376 Opkts) |
Remote login (telnet / ftp over zen0) |
works (drove the box entirely over the network) |
| Sustained FTP throughput | ~185 KiB/s, stable across 3 consecutive ~3.4 MB transfers |
🟡 Throughput note. ~185 KiB/s (≈1.5 Mbit/s) is the measured FTP rate — a working baseline, not a tuned result; the performance side is considered still flaky and not worth chasing yet. The interface itself reports zero errors/collisions even under flood load.
Known issues & open questions¶
- 🟡 Boot reliability is gated by unrelated boot panics, not the driver. The Amix root is a
non-journaled UFS; a cold power-cut can corrupt it and boot-time
fsckis effectively broken (a separate known issue), so a dirty root can panic before reaching the bring-up. When the boot is clean, networking comes up. Always prefer clean shutdowns (shutdown -i0; verify the network drops, i.e. the FS unmounted) before power-cycling. - 🟡 Auto-bring-up race — mitigated by the hardened
S99zen, not yet stress-tested across many cold boots. - ✅ Interrupt-driven RX is future work. RX is polled today. A true ISR path (
z3660ethintr()wired intoint2_tbl[]) needs a level-6 hook the firmware's INT6 model doesn't safely give Amix; it is gated on a board-mod. The polled callout is correct and measured to work; interrupt RX would mainly help latency/CPU. - ✅ Cache flush left OFF; revisit only if a future board/firmware revision changes the cacheability of the eth windows.
- 🟡 The in-repo amix-z3660net docs lag the real-hardware state. The lower
## Statusblock ofREADME.mdand the## Real-world build notesinBUILD.mdstill describe the pre-real-hardware state and the supersededsum -rclean-gate; the authoritative facts are the ones on this page (thenm -ugate and the real-hardware status).
See also¶
- Z3660 piscsi SCSI driver — the SCSI sibling on the same Z3660 combo board: the same firmware-mailbox style of register protocol, applied to disk I/O — it boots Amix multiuser with root on real hardware.
- A4091 / 53C710 SCSI driver — a separate Zorro III SCSI card (a real 53C710); the D245 clean-gate and Zorro III bring-up context this builds on.
- Writing a STREAMS driver — the third driver kind;
zen0is a second worked example (polled RX, firmware mailbox) alongside hydra (INT2, DP8390). - Hydra DLPI case study — the other modern Amix net driver; the mirror-image 60-vs-64 CRC boundary lives there.
- Networking — the SVR4 STREAMS TCP/IP stack
zen0joins; static IP, DNS off by default, theroutemetric. - Emulation Fidelity — the 68030 bus-error-frame and SCSI INT2-latency requirements of the same Z3660 UAE-derived emulator this driver runs on.
- Building and installing a kernel — the relink →
relocunixcycle and the D245 clean-gate this driver's build depends on. - Device & card list — the
cdevswmajor 51 /zenassignment (renumbered from 48 on 2026-07-30). - Quirks — the firmware-INT6/polled-RX gotcha in the checklist.
Sources¶
- Driver source we wrote (primary, ✅): the amix-z3660net project (git, branch
master) —src/z3660eth.c(~1034 lines),src/z3660eth.h(register map, frame-window layout, thesplno-ops),src/z3660ethuser.h,src/Makefile,driver.conf(thenet z3660eth … 51 zenstanza; 48 until the 2026-07-30 renumber),userland/S99zen(boot auto-bring-up),userland/{bringup.sh,network-config.zen,zen.c},README.md,BUILD.md; commitsf04d283(initial STREAMS/DLPI driver),2d26315(on-box integration scripts),84c9a4f(fixspl6/splx+rico.h-isms — Phase-1 gate),b06cf45(do not enable firmware INT6 — the storm fix),029ee9b(validated on real HW; hardenedS99zen+ README real-HW status). - Firmware source we read (primary, ✅): the Z3660 firmware
Z-TURN/vitis_ide/Z3660/src/—rtg/rtg.c(REG_ZZ_CONFIG/REG_ZZ_ETH_TX/REG_ZZ_ETH_RXdispatch, the INT6 raise),ethernet.c,memorymap.h,rtg/zzregs.h. - Design / scoping (primary, ✅): the amix-z3660 SCSI project's
ETHERNET-SCOPING.md(the protocol contract, cache-coherency analysis, the INT6 risk, the phased plan). - Real-hardware results reproduced live (2026-06-21, A4000 + Z3660, ✅):
ifconfig zen0,netstat -in, laptop↔boxping/ftp, and firmware serial[ETHCFG]/[ETHTX]debug breadcrumbs (a temporaryrtg.cbuild, since reverted). - Kernel build tooling (✅): the amix-kerntools project —
tools/build-clean-net-kernel.sh(thenm -uclean-gate),tools/build-net-kernel.sh(consumesdriver.conf). - The 2026-08-12/14 RAM campaign (workspace record): the ">80 MB board not found" root cause —
sptmapexhaustion against the fixed 4 MiB kernel arena, with the firmware-map-clash hypothesis falsified (banks byte-identical at every window, host DDR empty) — and thebase < VSECT1direct-map fix taking the driver from 65sptmappages to 0 ✅. Kernel-side context on the RAM ceiling. - The 2026-09-09
aen0-vs-zen0determination (workspace record) ✅ — the board-ID mismatch read from both ends of the interface: the Amix kernel'saenautoconfigpushing the packed A2065 id0x02020070toautocon()(disassembled from the linked kernel), against the Amix-interop firmware'sMANUF_ID 0x144Bfor every emulated board with A2065 emulation commented out of the embedded UAE core (Z3660_emu/src/cpu_emulator.cpp:263;uae/include/sysconfig.h:79) — line references re-verified against the firmware source 2026-09-09. Confirmed on real hardware the same day:aen -Sreturning 1 underautoconfig_rtgboth NO and YES, and the five-commandzen0bring-up succeeding first try on a real 68LC060 — while a second SD carrying a different firmware build networked onaen0on the same machine and card, which is the evidence for the firmware-build dependency (🟡 for that build's façade, which we did not read). - The 2026-08-14/15 golden-regeneration event (workspace record) ✅ — both direct-map flags read
set in the live kernel on a real A4000 + Z3660 (98 arena pages reclaimed across the ethernet and
SCSI drivers), read with
adb -kagainst the on-box native-linked kernel; and the byte-vs-intadbformat trap that makes a set flag print as16777216. - Magic numbers (✅): cdevsw major 51 (48 until 2026-07-30); board autocon
0x144B0001; fixed combo base0x10000000; ifacezen0@192.168.2.39; build box192.168.2.38; TFTP192.168.2.29; station MAC00:80:51:01:02:03.