Quirks & Gotchas¶
Amix is a "quick and dirty" 1990–1992 SVR4 port ✅, and it shows: the kernel hard-codes SCSI IDs and a fixed RAM-description arena, finds the A3000 SCSI by where the kernel was loaded, the bootstrap predates the 68040 and Zorro III, the stock setclk breaks in 2000, and the userland is pre-POSIX. This page is the checklist of port-specific surprises — the things that are technically correct, technically broken, or just unexpected, that cost people hours before they realize "it's just Amix."
Each item is one line: what it is, the ✅/🟡 confidence tag carried from the research brief §12 (and §2/§9/§11, plus §17/§18 for the Z3660 driver-authoring items), and a cross-link to the page that covers it in depth.
TL;DR checklist¶
| # | Quirk | Tag | Deeper coverage |
|---|---|---|---|
| 1 | Tape must be ID 4 (hard-coded /dev/rmt/4h); disk ID 6 by convention — installer prompts for the target, but the ID is baked into device names, so don't change it post-install |
✅/🟡 | Hardware, Filesystems & disks |
| 2 | ~~16 MB Fast RAM ceiling~~ wrong mechanism (corrected 2026-09-29) — not a RAM ceiling: the stock kernel's A3000-SCSI guess (row 57) needs the kernel loaded above 0x07000000, i.e. a 16 MB A3000 motherboard bank. Probing kernels run 64 MB, the 040/060 kernels 128 MB. The real limit — total describable RAM, ≈64 MB on a stock 030 kernel, set by a fixed 4 MiB kernel arena — fails silently (no output at all) |
✅ | Hardware, RAM ceiling |
| 3 | Stock Amix is Zorro II only — stock drivers can't address Zorro III; but a purpose-written driver that maps the board (sptalloc, or a window inside the identity-mapped low 1 GB) can — see the A4091 & Z3660 drivers |
✅ | Hardware, Zorro autoconfig |
| 4 | No 68040/68060 in stock Amix → a stock kernel can't run on an A4000; the public 68040/68060 port boots to login on a real 68LC060 (Z3660 in an A4000T, 2026-09-25) | ✅ | Hardware, 040/060 status |
| 5 | Superkickstart dual-boot by holding the right mouse button at power-on | ✅ | Boot process |
| 6 | DNS resolution is OFF by default (/etc/hosts only) |
✅/🟡 | Networking |
| 7 | Y2K is a setclk bug, not a kernel cap — the stock setclk writes the year %02d (126 for 2026), date rejects it, so a stock box never sets its clock from the battery clock, even when the RTC is right. Fix: the community-patched setclk plus /etc/clkzone naming the zone the RTC holds; the stock 1991 zone files carry outdated DST rules |
✅/🟡 | Quirks §7, Versions |
| 8 | SLIP is buggy — reboot between sessions; no PPP at all | 🟡 | Networking |
| 9 | X keymap is wrong: y/z swapped, / is SHIFT-8 |
🟡 | X11 & desktop |
| 10 | amixpkg is install-media-only, not an installed tool — it drives the root.adf install; its "broken" reputation is partly the item below (the raw pkgadd/pkgmk tools are installed and work) |
✅/🟡 | Package management, Install walkthrough |
| 11 | Clock drift via SCSI interaction | 🟡 | Networking |
| 12 | /bin/sh is pre-POSIX: no $(...), no grep -q |
✅ | Driver model, Writing a char driver |
| 13 | Enabling DNS adds ~3 min to every boot unless the boot-time ifconfigs (S69inet lo0, network-config aen0) use literal IPs instead of hostnames |
✅ | Networking, Networking on the LAN |
| 14 | A board interrupt Amix has no handler for will storm the kernel — the Z3660 firmware raised level-6 (INT6) on every RX frame; with no Amix eth-INT6 handler, ordinary ARP broadcasts hard-locked the box. Fix: disable the firmware IRQ and poll | ✅ | Z3660 ethernet driver |
| 15 | A board the 2.1 bootinfo.autocon[] table misses, registered via autocon() alone, silently never runs — kernel banner, then a dead box (no panic, no I/O). Fix: multi-method detect (autocon → a chipset-gated fixed-base probe → a probe= fallback) |
✅ | Z3660 piscsi SCSI driver, A4091 |
| 16 | On an emulated 68k, "boots then hangs" may be the emulator, not your driver — the Z3660's 68k emulator faulted demand-paging the driver's own pages, while the driver carried every transfer byte-perfectly. Triage with serial instrumentation + core-dump analysis before blaming the driver | ✅ | Z3660 piscsi SCSI driver, Emulation Fidelity |
| 17 | An emulator that batches instructions between interrupt polls can hang the A3000-SCSI bootstrap — that bootstrap is INT2-driven; too-coarse interrupt-service cadence starves it and Amix hangs before the banner. Keep INT2 latency low through boot (on the Z3660, service_cadence ≤ 4) |
🟡 | Emulation Fidelity |
| 18 | pkgrm and pkginfo -l fail out of the box on the stock 2.1c image — one malformed contents record (a filename containing a space) aborts every full parse of the package DB (bad read of contents file … unknown ftype). Delete that one line to fix both |
✅ | Package management |
| 19 | mount(2) silently stacks a second mount on an already-mounted directory — the second mount returns 0, umount then pops one layer and returns 0 while the media looks "still mounted": a perfect fake broken-umount. An in-kernel filesystem must refuse it itself. Smoking gun: duplicate fstype lines in /etc/mnttab |
✅ | Driver model |
| 20 | "Password expired" at login is the future-lastchg check, not max-age — a dead RTC battery boots the box in 1978; if a previous session set the clock forward, /etc/shadow's lastchg is now in the future and SVR4 login forces a password change every boot. Never set the box RTC forward |
✅ | Time & Y2K |
| 21 | A colon-less zoneinfo TZ crashes every tzset() caller — TZ=US/Eastern gives User BUS ERROR at 474D5400 (= ASCII "GMT\0" dereferenced as a pointer) in date, who -r, cron, ls -l…; use the colon form TZ=:US/Eastern |
✅ | Quirks §21 |
| 22 | A fresh miniroot ships no /etc/mnttab — its bare mount(2) writes none, yet pkgadd needs the file, so an installer must seed it before it calls pkgadd; a reused root floppy can carry a stale one and hide the gap |
✅ | Quirks §22, Package management |
| 23 | inetd disables a service after 40 connections in 60 s — that port answers Connection refused while every other port, ICMP and the console stay healthy; it re-enables itself within 10 min. No config knob exists (nowait.N is silently ignored). Diagnosis: port 21 open + port 23 refused ⇒ throttle |
✅ | Networking |
| 24 | syslogd ships deliberately disabled — a vendor exit on line 8 of /etc/init.d/syslogd, so every daemon.*/auth.* message on the box is discarded. One commented-out line enables it; this SVR4 logs to /var/log/*, not /var/adm/messages. ⚠ that file is one inode with 3 hard links — edit with cp in place, never mv. Enabled can still mean not started: a stale /etc/syslog.pid fools the start test |
✅ | Networking, stale pid |
| 25 | A hand-written UAE-family .uae selects the cycle-exact 68030 core and Amix dies at PID 1 — omitting cycle_exact is not neutral, the default is true (and host-dependent). Pin cycle_exact=false; it also silently overrides cpu_compatible=false |
✅ | Emulation fidelity, Amix on Amiberry |
| 26 | Three diagnostic tools lie or don't work: netstat -a prints corrupt control block chain on a perfectly healthy box; sar -v fails sadc: Not enough space; ipcs -a fails read error: No such device or address. Don't chase any of them, and don't budget on them for a capture |
✅ | Quirks §26 |
| 27 | dd skip= on a cdfs file is not an effective seek — the skipped bytes are really transferred, through the one-sector in-kernel SCSI path, so a read at the 1 GiB mark needs ~20 minutes. Never score that timeout as a read failure |
✅ | Quirks §27, Filesystems & disks |
| 28 | The partitioning tools' units are all 512-byte blocks, and rdb's usage text omits the flags that matter — rdb -c requires a -d disksize its usage block never mentions; rdb -p wants a 1-based partition number (a device path atoi()s to 0 and is refused); mkfs_s5's size operand is 512-byte blocks (pass a pre-converted 1 KB count and the filesystem covers half the slice) |
✅ | Filesystems & disks |
| 29 | The stock kernel writes motherboard register 0xde0002 at every boot and at reboot/halt — on the A3000 that is the SuperKickstart re-kick bit; on an A4000 the same address is Fat Gary's coldreboot latch, which nothing consumes, so the write is benign. Don't chase it in bus traces or emulator logs; decode-and-ignore is the correct model for an A4000 target |
✅ | Quirks §29 |
| 30 | A scripted ed edit that misses its address search still writes the file — earlier inserts in the same body stay applied, and a name-wide grep guard then reports "already patched" forever: a permanent, invisible half-patch. Pre-probe the target row, one ed run per edit, post-verify the row — never trust ed's exit status alone |
✅ | Quirks §30, Kernel build |
| 31 | On an FPU-less part, floating point dies as SIGSYS ("Bad System Call", exit 140), not SIGILL/SIGFPE — and it dies in libc: _doprnt carries FP on its %e/%f/%g paths, so any program that prints a float dies even if its own code is FP-free (df -k, uptime). __fpstart is FP-safe, so everything else runs. Triage rule: does it print a float? On an FPU-less 68030 the stock mkfs -F ufs and gcc14-built GNU cp/gtar die too (🟡, emulation) |
✅/🟡 | Quirks §31, 040/060 status |
| 32 | awk is always a floating-point program — it holds every number as a C double, so on an FPU-less part it dies even on 1+1. An awk failure on integer arithmetic is not evidence of a second defect |
✅ | Quirks §32 |
| 33 | 68060: the FP-disabled frame's stacked "Next PC" is software-maintained — Motorola's own support package recomputes it from its own decode and treats the faulted-PC slot as the authority. A handler must resume on a PC it computed itself | 🟡 | Quirks §33 |
| 34 | 68060: immediate #<data> is the one F-line operand the hardware cannot size — its length lives in the FP command word a disabled FPU has no reason to decode, so the stacked next-PC can be short by the operand's width |
🟡 | Quirks §34 |
| 35 | Withdrawn 2026-10-04 — see §35 | — | — |
| 36 | Withdrawn 2026-10-04 — see §36 | — | — |
| 37 | Withdrawn 2026-10-04 — see §37 | — | — |
| 38 | uptime and w do not report the machine's load — the reader resolves a COMMON kernel symbol to a non-address without an error (flat 0.00 and absurd millions are the SAME defect), and even the kernel's own count reads exactly N−1 (the on-CPU process is never counted). Read the truth with adb -k <running-kernel> /dev/mem, avenrun/3X, ÷256 |
✅ | Load averages |
| 39 | <cmd> &; is a syntax error in the SVR4 Bourne shell — a command line ending in a bare & that then gets ; … appended (such as a completion marker ; echo DONE_$?) never parses, so the background job never starts. Parenthesise the background launch: (… &) |
✅ | Quirks §39 |
| 40 | Amix keeps no persistent boot log — no dmesg at all, wtmp has never recorded a reboot, and boot fsck writes only to the console (a delta to quirk 24, independent of syslogd). The console is the only record — and it still holds the whole boot transcript at a fresh login: prompt. Plan an HDMI capture; there is no log to read |
✅ | Quirks §40 |
| 41 | Stock autocon() decides whether the A3000 internal SCSI exists from where the loader put the kernel, not from the hardware (end > 0x07000000, end being the kernel's own end address — once read as a RAM-size test) — wrong in both directions: it fabricates a phantom card 0 where there is no controller, and it silently drops the real one (root disk included) on a machine where the kernel loads lower (a bigger A3000 motherboard bank), renumbering every remaining SCSI card |
✅ | Quirks §41, Zorro autoconfig |
| 42 | Withdrawn 2026-10-04 — see §42 | — | — |
| 43 | Withdrawn 2026-10-04 — see §43 | — | — |
| 44 | On-box adb -k needs a namelist that still has a symbol table — elf2brel discards symtabs, so /stand/unix is useless as a namelist and adb reports nonsense instead of failing |
✅ | Quirks §44 |
| 45 | Heavy raw dd to the boot slice rebooted one machine mid-write — a single-unit observation with no known mechanism. The session dies and looks like a failure; trust the verify line the job wrote to disk, not the session that vanished |
🟡 | Quirks §45 |
| 46 | adb -k prints a one-byte kernel flag as 16777216, not 1 — /D reads four bytes as a big-endian int, so a set byte at the object's address reads as 0x01000000. The value is right and the format is wrong; do not conclude the flag holds garbage |
✅ | Quirks §46 |
| 47 | A successful remote halt looks like a transport failure — uadmin 2 0 kills the session that issues it, so a remote runner reports a transport failure; that signature is the success case. A halt is proven clean only by a fsck-silent next boot — image a disk only after one |
✅ | Quirks §47 |
| 48 | Withdrawn 2026-10-04 — see §48 | — | — |
| 49 | A warm reset is not a clean-state reset — on one card (stock firmware, emulated-CPU mode) the accelerator's warm-reset command gave a valid reset vector 0/3 where a power cycle gave it 5/5; a single-unit observation. Power-cycle with ≥20 s off for anything that needs a known state | 🟡 | Quirks §49 |
| 50 | Withdrawn 2026-10-04 — see §50 | — | — |
| 51 | Memory detection that reads back immediately is fooled by capacitive echo — an unpopulated bus holds the value it was just driven with and passes. Treat any "Detected NNN MB" as unverified until a whole-block address check confirms it | ✅ | Quirks §51 |
| 52 | Withdrawn 2026-10-04 — see §52 | — | — |
| 53 | 68060: the unimplemented 64-bit integer instructions trap to a vector the stock SVR4 trap table never populated — the 64-bit MULU.L/MULS.L/DIVU.L/DIVS.L forms are left to software by the CPU, the stock trap-dispatch table has no entry for them, and where nothing serves that vector the process is killed with no diagnostic at all (a bare Killed, rc 137 — no SIGILL, no SIGFPE, no core). 68060 kernels before amigaux 1.0 emulated at most some 64-bit multiply forms; the 1.0 kernels emulate the 64-bit multiply and divide forms, MOVEP and CMP2/CHK2, so only CAS2 and a misaligned CAS still die. Integer ops, so it is not an FPU matter; a 68060 toolchain should still emit the 32-bit MULU.L/DIVU.L/DIVUL.L forms (emulation is slow, and older kernels kill) |
✅ | Quirks §53, 040/060 status |
| 54 | On the Z3660, amix_mode YES under bootmode CPU silently disables SCSI boot — the firmware forces cpu_ram = 0, and the boot device then disappears with one line of serial as the only warning. A real-CPU Amix preset must set amix_mode NO |
✅ | Quirks §54 |
| 55 | A Kickstart proven under emulation is not proven on silicon, and the file matters more than the generation — a ROM that boots under emulation may still fail on the real CPU. Identify a ROM by CRC-32, never by filename or slot | ✅ | Quirks §55 |
| 56 | A firmware CRC manifest does not tell you which image ran — the loader picks at boot time and only its own serial line records the choice; and the boot menu is a single-keystroke consumer where six keys copy firmware images over each other | ✅ | Quirks §56 |
| 57 | The stock kernel finds the A3000's internal SCSI only when the loader placed the kernel above 0x07000000 — with RAM laid out so the kernel loads lower, no card 0 exists and root fails to mount (VOP_OPEN error 6). The "16 MB ceiling" folklore is this guess's one working layout |
✅ | Quirks §57, RAM ceiling |
| 58 | A run-level change requested over telnet while nobody is logged in on the console stalls at INIT: New run level: 6 — stock init (reproduced on the stock kernel); rc6 never runs and only a power cycle recovers. Keep a root shell logged in on the console, or issue reboots from it |
✅ | Quirks §58 |
| 60 | On the Z3660, an isoN CD image works only when N equals the SCSI target id — a mismatch fails silently in both directions (the ISO becomes a read-write disk, an unrelated image becomes a CD); only iso0–iso6 exist |
✅ | Quirks §60 |
| 61 | VPOSR: AGA is the Agnus/Alice id's low nibble, not id >= 0x22 — the range test misreads an NTSC ECS Agnus (0x30) as AGA and skips an NTSC A3000's internal SCSI (its root disk included) |
✅ | Quirks §61, Zorro autoconfig |
| 62 | On-box mkfs fails above a ~603 MB root, and a raw read past that point sleeps forever — stock sddevsize() answers a fixed 1 234 567 blocks for a slice whose table was not read, and dd.c clamps to it. Fix: default 0x3FFFFF + an offset check in dd.c |
✅ | Quirks §62 |
| 63 | Stock dd.c counts every unit in 512-byte blocks — a 2048-byte CD driver that multiplies by its own block size overruns the buffer 4×; refuse any request larger than nbyte (the Z3660 raw-dd CD hang) |
✅ | Quirks §63, Z3660 SCSI driver |
| 64 | Stock in.ftpd silently truncates PASSIVE downloads and still replies 226 — a byte-exact prefix, no SIZE command to catch it. Use active (PORT) mode and length-gate every GET against ls -l |
✅ | Quirks §64 |
| 65 | Stock autocon() can never match a manufacturer id ≥ 0x8000 — it compares against a signed id >> 16; the board is handed over in bootinfo.autocon[] but no driver ever attaches (the A4092's 0xC0DE was the first such id). Fix: an unsigned shift (asr.l → lsr.l) |
✅ | Quirks §65, Zorro autoconfig |
| 66 | Under the Z3660, a CPU read of an undecoded local-I/O address ($D80000–$DFFFFF) hangs forever — the motherboard raises no timeout bus error there and the card's CPLD has no cycle timeout, so not even a fault handler runs. Never probe $DD00xx speculatively |
✅ | Quirks §66 |
| 67 | On the Z3660 the real 68060 cannot boot from the motherboard ROM sockets (kickstart 0) — the firmware clears the MAPROM bit after the FPGA has already latched it, so the CPU fetches an empty DDR region (black screen). On stock firmware (upstream 1.03 beta 21 included), always MAPROM from a Kickstart file; the project's fork firmware 1.0 (C96EAD03) carries the fix |
✅ | Quirks §67 |
| 68 | Real-060 MAPROM boots take the reset vector from the socket ROM — a stock Kickstart in the sockets (00F800D2) needs nothing; DiagROM ($F800D6) needs the mapped image .d6-patched (0xD6: 00414D49 → 6000007A, checksum at 0x7FFE8) |
✅ | Quirks §68 |
| 69 | A 68040/68060 kernel writes anything over 4 KiB to disk as 2 KiB requests, and flushes a UFS inode block twice per sync (~70 ms apart) — only a fault on a block's last write persists; the disk layer's error path never misplaces data; mask dinode time fields when comparing inode copies |
✅ | Quirks §69 |
| 70 | 68060 kernel, emulator run: the stock 1992 installf crashes (User BUS ERROR at 4AFC0002), leaving a core in / and a half-registered package entry; APKG's installf did the same job cleanly in the same run. Route runtime package operations through the APKG tools on a 68060, with the engine amigaux 1.0 ships (APKGENG 1.12.3): the July 2026 1.12.0 build's own installf -f is killed by 64-bit divides on a 68060 kernel before 1.0 |
🟡 | Quirks §70, Package management |
| 71 | A boot node's fssm_Device BSTR length counts the terminating NUL — MakeDosNode() adds 1 to the device name's length byte (not to dn_Name's) on every Kickstart from 2.0 to 3.2.3. A loader that matches its boot node against an exec ln_Name must compare as a C string, as the strap does |
✅ | Quirks §71, Boot process |
| 72 | A local AF_UNIX stream socket can deliver stale data under memory pressure — on Amix 2.1c with about 8 MB of fast RAM and a non-blocking writer, a read can return whole 4 KB pages from earlier in the stream; X clients (Xlib writes non-blocking) die on misparsed requests. Third-party report, not reproduced here; none seen with 12 MB or more |
🟡 | Quirks §72, X11 & desktop |
| 73 | Amiberry builds before upstream commit 845af02 corrupt Amix data in the 68030 MMU's "more compatible" mode (cpu_compatible=true) — a misaligned MOVES was not split across a page boundary, and Amix copies between kernel and user space with MOVES. On an older build set cpu_compatible=false. Third-party report, not reproduced here |
🟡 | Quirks §73, Amix on Amiberry |
| 74 | Without an FPU the stock 68030 kernel boots, then panics at the first signal — the boot probe prints no fpu detected and carries on, but sendsig is the one caller of the FP-state routines that never checks fpu_present: its FRESTORE (kernel text 0x12A) takes a Line-F fault in supervisor mode, in init, seconds after boot. Triage rule: no fpu detected, then a kernel fault in pid 1 at load base + 0x12A, vector 0xB = the card has no FPU — not the disk driver, Kickstart or RAM. Fixed on the 68030 line in emulation |
✅ | Quirks §74, Hardware |
| 75 | Inbound TCP bursts lose every segment past the third, and nothing counts it — the stock kernel's ipintr frees a datagram when TCP's lower read queue is full, and that queue's high-water mark is 4096 bytes, so the 4th full segment of any batch IP processes in one pass is dropped. Signature: push ≪ pull, hundreds of sender retransmissions, netstat -s IP total a few hundred above TCP total, every error counter 0. Fix: tcpm_info[3].mi_hiwat 0x1000 → 0x4000 (one long word) |
✅ | Quirks §75, Networking |
| 76 | Stock diff has no unified format — /usr/bin/diff -u answers diff: invalid option -u; only -c exists. The stock public package's /usr/public/bin/patch (patch level 12u4) does apply unified diffs |
✅ | Quirks §76, Cheat sheet |
| 77 | syslogd pipes /etc/syslog.conf through /usr/ccs/bin/m4, which only the C development set ships — without it every file action is UNUSED and nothing is logged, while the log driver works. Install the development set (or m4 alone) before trusting an empty log |
✅ | Quirks §77, Networking |
| 78 | After boot, kernel printf lines are kept in the message buffer only when a console logger is registered (syslogd's I_CONSLOG) — otherwise they go to the console device and are kept nowhere |
✅ | Quirks §78 |
| 79 | Stock bcheckrc always reboots after fsck repairs the root (*** REBOOTING SYSTEM ***) — a root that never stays clean reboots for ever; boot counters and "try once" logic must allow for it |
✅ | Quirks §79 |
| 80 | netstat, ifconfig and crash read kernel symbols from /stand/unix, never from /unix, and there is no /dev/ksyms — a box whose /stand/unix is not the running kernel prints wrong addresses or no namelist |
✅ | Quirks §80 |
| 81 | The 2.1 Bourne shell's read keeps its own buffer across exec <file — a second exec <file; read continues where the first stopped; and a while read loop strips trailing blanks. Use one redirection per pass (while read …; done < file) |
✅ | Quirks §81 |
| 82 | The 4 KiB-page (68040/68060) kernels and small-block filesystems: stock mkfs refuses ufs blocks under 4 KiB on both lines, a crafted 2 KiB-block ufs panics the 040/060 kernel at first file access (ufs_getapage bad bsize; the 030 kernel handles it), and s5 is broken on 4 KiB pages (bus errors in ls) |
✅ | Quirks §82 |
| 83 | Kickstart leaves the CIA-A timer running when Amix starts and nothing in Amix stops it: it raises an ignored interrupt about every 92 ms, and a driver that wants that timer must stop and reprogram it rather than treat "running" as "owned" | ✅ | Quirks §83 |
| 84 | The stock /dev/audio open spins at level 4 with no timeout waiting for the sound chip's interrupt — a silent chip (an emulator with sound output off) freezes the whole box |
✅ | Quirks §84 |
| 85 | The 1992 console programs only OCS/ECS display registers — AGA state left behind by whatever ran before the kernel (FMODE, BPLCON3, BPLCON4, the copper list) garbles the Amix console on an AGA machine |
✅ | Quirks §85, Boot process |
| 86 | Swap is not optional in the stock kernel — with no swap device the first fork panics, and swapconf panics (lookupname failed - error 2) when the configured swap node does not exist; a RAM-rooted medium points swap at /dev/fakeswap (block 19,0, the stock dummy disk) |
✅ | Quirks §86 |
| 87 | On the Z3660, the second of two back-to-back register accesses can land at (previous address AND this address) — a mailbox write becomes a different command (a WRITE doorbell to the previous block; a READ turned WRITE that reports GOOD), an interrupt acknowledge is lost. Precede every register write with a covering read, in one block at IPL 7 | 🟡 | Quirks §87, Z3660 SCSI driver |
| 88 | Stock sed silently drops a final input line that has no newline — printf 'a b c ' \| sed 's/ $//' prints nothing at all. Terminated lines are fine. Make sure the input ends with a newline ({ …; echo; } \| sed …) |
✅ | Quirks §88 |
| 89 | libc.so.1's TZ parser writes into the TZ environment string in place — a program that putenv()s a string literal (read-only under modern compilers) dies with SIGSEGV at its next time conversion; date -u of a GCC 14 build did, every run. putenv() writable storage |
✅ | Quirks §89, Toolchain |
| 90 | ftruncate that shrinks a file past its 12 direct blocks to an unaligned EOF in the old last block can panic the kernel (KERNEL FAULT, bus error), on stock 2.1c and on the 040/060 line, when that block ends past the page holding the old EOF — e.g. 110017 → 110000 bytes on an 8 KiB-block ufs panics both. Cut at the block boundary below the new EOF, then write the tail back. The 040/060 port fixes it in kzero (verified in emulation and on a real 68060); the 68030 line has the same fix, verified in emulation |
✅ | Quirks §90, Toolchain |
| 91 | A shrink to an earlier block, then a sparse regrow, reads the old bytes back — from disk — on stock 2.1c and both kernel lines, F_FREESP from N to an unaligned M in an earlier 8 KiB block (both past the direct blocks) returns cleanly, but after writing one byte at N−1 the file holds its old data in [roundup(M, PAGESIZE), roundup(M, 8192)), or in all of [M, roundup(M, 8192)) if the file was not cached. No crash. Fixed on both lines in emulation. Write real zeros through the gap, or cut block-aligned |
✅ | Quirks §91 |
| 92 | A failed STREAMS open can leave a freed queue on the run list, and stock 2.1c then panics in queuerun — qattach undoes the failed open with qdetach(q, 0), whose short path frees an already-enabled queue without taking it off qhead; the next queuerun calls qi_srvp through freed memory. Signature: KERNEL FAULT, vector 0x4 or 0xB, pc in the kernel heap, backtrace ureturn → queuerun. Found with prngd exhausting a small swap's reservation. Fixed on the 68030 line in emulation; the 040/060 line has the same code and carries the same guard, its save not yet seen there |
✅ | Quirks §92 |
| 93 | netstat -rn fails with read error on routehash, netstat -in lists no interfaces and netstat -s counts nothing, on an image whose /stand/unix is not the booted kernel (on the bench image it is the retail 2.1 kernel ✅). Likely the namelist mismatch of §80 (🟡). Check /stand/unix before trusting netstat; set routes with route and record them yourself |
✅/🟡 | Quirks §93, Networking |
| 94 | The 2.1 /bin/sh and background jobs: wait <pid> returns 0 for a child that already ended; two long-lived >> writers of one file overwrite each other; a shell waiting on a foreground child ignores SIGTERM until the child ends; sh -c 'cmd' a b sets $0=a. One output file per job, KILL after a grace period |
✅ | Quirks §94 |
| 95 | The A2065 aen DLPI traps: DL_ATTACH_REQ is never answered; an open can return a slot left bound with stale frames queued; received frames are 14 bytes longer than the datagram; a DL_UNITDATA_REQ with under 60 data bytes fails with ERANGE. Attach only on Style 2, check the state, trust the IP length, pad short frames to 60 |
✅ | Quirks §95, Networking |
| 96 | The stock tar (/usr/sbin/tar) crashes when root extracts a ustar or GNU archive whose owner or group name the box lacks (an empty name counts, as GNU --numeric-owner writes): it prints problem reading passwd entry / owner not changed, then dereferences the NULL lookup and dies with SIGSEGV after the first file. Plain tar is this binary even with GNU tar installed. Same on a 68060 kernel with the TT fix. It also reads no GNU long names or pax headers. Install heirtar, or use tar xof, a non-root user, /opt/amix/bin/tar, or v7 archives |
✅/🟡 | Quirks §96 |
| 97 | A NULL read returns 0 in 193 stock binaries and faults in gcc14-built ones: SVR4 ld gave those stock executables a read-only zero page at vaddr 0, while gcc14 binaries have none. Code that passes as one kind of binary can crash as the other. Before an unreleased fix, the 040/060 kernels returned chip-RAM bytes instead |
✅ | Quirks §97, 040/060 status |
| 98 | A vfork child that calls signal, sigset or sigaction breaks the parent: libc keeps the handlers in a table in its own data, which a vfork child shares, so the parent's next delivery of that signal jumps to 0 or 1 and dies with SIGBUS. xinit built with vfork never starts X. Use fork; in a vfork child call only exec* and _exit |
✅ | Quirks §98 |
| 99 | A telnet command line over the 256-byte canonical limit runs nothing and wedges the login: a 229-byte line answered and a 269-byte line did not, and nothing later in that login answered until a reconnect. Usually there is no echo or error, only silence. Check the length on the host and split long lines | ✅/🟡 | Quirks §99 |
The rest of this page expands each item.
Hardware & addressing quirks¶
54. On the Z3660, amix_mode YES under bootmode CPU silently disables SCSI boot ✅¶
On a Z3660 preset that releases the real CPU (bootmode CPU), setting amix_mode YES makes the
firmware force cpu_ram = 0 ✅. SCSI boot is then enabled only when scsiboot && cpu_ram holds, so
the boot device silently disappears and the machine never finds its disk. The only warning is one
line on the serial console — CPURAM disabled -> SCSI boot disabled — with nothing on screen.
It reads backwards: the Amix preset is the one that must set amix_mode NO. The rule is
amix_mode NO on any real-CPU Amix preset. An emulated preset (UAE_040_MMU) keeps
amix_mode YES safely, because CPU RAM and autoconfig are emulated in the CPU emulator there, so
the forcing costs nothing — which is why the two preset shapes legitimately differ and should not be
"made consistent" ✅. A companion trap: amix_ram is a construct of the emulator core and is inert
under bootmode CPU; the firmware says so at boot (amix_ram 128 ignored: amix_mode is NO), so
leave the line alone rather than "fixing" it ✅.
55. A Kickstart proven under emulation is not proven on silicon ✅¶
Success under a UAE core is not evidence for the same ROM on silicon ✅: a ROM that "works" may only have been shown to work on the easier, emulated path. Prove the ROM on the real CPU before relying on it.
What it is not, checked rather than assumed ✅:
- Not a generation requirement. The Unix dostype constant
UNI\1(0x554E4901) appears exactly twice in every ROM from 2.04 through 3.2.3 — the code moves address between generations, it does not disappear. The boot chain is generic RDB autoboot; the ROM only has to recognise the dostype and load the boot block.
Identify a Kickstart by CRC-32, never by filename or by slot number ✅. Slot numbers are per-card config indices and mean nothing across machines, and a file name proves nothing about the ROM inside.
56. A firmware CRC manifest does not tell you which image ran ✅¶
The Z3660 carries three firmware images on its boot volume. BOOT.BIN is the loader: it loads
Z3660.bin when that file is present and valid, falling back to FAILSAFE.bin and then to itself
✅. So a checksum inventory of the three files records what exists, not what the loader chose
— the only record of the actual choice is the firmware's own [SD Init] Done (…) serial line. A
causal timeline built from the manifest instead of the log will be confidently wrong.
The same page of the story: the firmware boot menu is a single-keystroke consumer ✅. There is no
Enter; every character acts immediately, several are destructive, and typing a word executes it one
letter at a time. Six keys (M, B, N, J, K, L) each copy one of the three boot images over
another, and D reflashes the CPLD ✅. Send keys one at a time; never send a line.
60. On the Z3660, an isoN CD image works only when N equals its SCSI target ✅¶
The firmware's isoN <file> key (upstream's, since beta 21) fills image slot N like hdfN and
marks it optical: the target is then presented as a read-only, 2048-byte-block CD-ROM (SCSI
device type 0x05) with no RDB scan, which is what Amix's cdfs mounts ✅. But the firmware records
the CD flag by slot and consumes it by SCSI target: target t is a CD if and only if slot t
was declared with isoN. The scsiN line that actually assigns the image is never consulted for
the flag ✅. The only working shape is one where the numbers agree:
A mismatch fails silently in both directions ✅: the ISO is attached as an ordinary read-write
RDB disk (its ISO filesystem is never found), and whatever image sits on the target matching the
slot number is presented as a read-only CD. Only iso0–iso6 exist (the CD table is seven entries
wide). Beta 21's own shipped example config pairs iso6 with scsi4 6 and produces no working CD —
read it as a demonstration of the trap, not a template ✅. The firmware also skips the RDB scan on an
isoN unit, so a CD unit cannot be a boot device.
61. VPOSR: AGA is the Agnus/Alice id's low nibble, not id >= 0x22 ✅¶
Kernel code that must tell an A3000-class machine (OCS/ECS) from an A4000 (AGA) without touching
hardware that may not decode reads VPOSR (0xDFF004), whose bits 8–14 are the Agnus/Alice id.
The ids are 0x00/0x10 OCS (NTSC/PAL), 0x20/0x30 ECS (PAL/NTSC), and 0x22/0x23/0x32/0x33
AGA Alice ✅. The tempting range test id >= 0x22 is wrong: it classifies an NTSC ECS Agnus,
0x30, as AGA. On an NTSC A3000 that skips the probe for the internal WD33C93 SCSI, and the root
disk on it is lost ✅. The correct test is the low nibble: AGA iff (id & 0x0F) >= 2.
Found in 2026-09 in a review of a contributed Gayle-IDE driver (Emre Bastuz's amix-gayle-ide),
whose own gate was right; the same defect was then fixed in two places that had copied the range
test — the A4091 project's support.c.patch and the Z3660 SCSI driver's machine gate — and a bench
run on an emulated ECS-NTSC machine (id 0x30) confirms
the probe now runs and finds the WD33C93 as card 0 ✅. The corrected gate and its probe are on
Zorro autoconfig.
66. Under the Z3660, a read of an undecoded local-I/O address hangs the machine forever ✅¶
A CPU read of an address in the A4000's local I/O space ($D80000–$DFFFFF) that nothing
decodes never terminates on an A4000D with a Z3660 ✅ (measured 2026-10-02 with a
supervisor-mode, fault-catching peek tool under AmigaOS on the card's 68060). No bus error arrives, so
not even a fault handler gets a chance: the first read of $DD0000 froze the machine ✅. Two facts combine: the motherboard generates no
timeout bus error for local I/O space, and the Z3660's CPLD has no bus-cycle timeout of its
own — its termination logic waits for DSACK, AVEC or BERR indefinitely ✅ (read from the
card's CPLD sources). The CPLD does forward a bus error that something else raises: Buster's Zorro
III timeout (~280 ms) arrives at the CPU as an ordinary access fault ✅.
An empty Zorro III address, by CPU path ✅ — an undecoded address below VSECT1
(0x20000000) read through the kernel's bprobe, at interrupt levels 0, 4 and 7, on an A4000D with
a Z3660 and under Amiberry (2026-11-02):
| CPU path | What the read does |
|---|---|
| real 68LC060 (the Z3660's real-CPU mode) | bus error after 283–295 ms (Buster's timeout); bprobe catches it at every level, level 7 included, and the box carries on |
| the Z3660's emulated 68030 | returns 0xFF within 64 µs, no fault |
| Amiberry (A4000, either CPU) | returns 0x00, no fault |
So a bus-error guard tells an empty window from a present one only on the real-CPU path. On the
emulated 030 and under Amiberry the read succeeds, and only the identity check that follows can
refuse a phantom board: reject all-zeros and all-ones as well. A real 68030 has not been run 🔴. And a
bprobe at or above VSECT1 says nothing about the bus: there the read faults in the MMU first,
outside the identity map, so a bus-error test must use an undecoded address below 0x40000000.
Rules for driver and probe authors: never probe $DD00xx (or anything else in $D80000–
$DFFFFF) speculatively — gate any such access on a positive identification of the machine, the way
the VPOSR gate does; a fault-catching probe does not help, because there is
no fault to catch. And read a freeze after a c0 access on an A4000-class machine with the stock
kernel's phantom A3000 card (§41) as a hung bus cycle, not a driver
stall. Setting the bit of Gary's $DE0001
that emulators treat as the bus-timeout enable does not help: on the A4000D the bit reads back
set and the $DD0000 read still hangs ✅ (§29),
so the board logic most likely never raises a bus error for local-I/O non-range addresses 🟡 (not yet
confirmed on a scope). The card's CPLD would pass one on. On the Z3660's emulated 68030/040 the
same read behaves differently: the emulator times the cycle out itself and its UAE-derived Gary
model can turn it into a bus error ✅ — which is why older emulated-CPU runs saw a fault where the
real 68060 hangs (see the dated note on Zorro autoconfig).
67. On the Z3660, the real 68060 cannot boot from the motherboard ROM sockets (kickstart 0) ✅¶
With the Z3660 running its real 68060, kickstart 0 — "use the Kickstart in the motherboard's
ROM sockets, no MAPROM" — never boots: black screen, no SCSI activity at all ✅ (A4000D with
Kickstart 3.2.3 in the sockets, 2/2, 2026-10-03). The cause is an ordering problem in the firmware ✅
(read from the FPGA and firmware sources): the FPGA latches its MAPROM-enable bit only while it is
held in reset, and whenever that bit is set it serves $F80000–$FFFFFF from the card's own DDR.
The firmware enables MAPROM at start-up, pulses the FPGA reset, starts the CPU side, and only then
clears the bit for kickstart 0 — with no further reset, so the cleared bit never takes effect and
the 68060 fetches its Kickstart from a DDR region nothing was loaded into. The upstream firmware
(1.03 beta 21) has the same order ✅. How to recognise it: the screen stays black and the
firmware's serial console floods with descending write SCSI add 0x001FFFFD… lines — the 68060
executing garbage and sweeping the piscsi register window (about 85 000 such lines in three minutes on
the A4000D, versus none with the fixed firmware; first-party record, 2026-10-03) ✅.
Rule: on stock firmware a real-060 Z3660 boots via MAPROM from a Kickstart file, always — dump your ROM chips
to a file if that is all you have. The fix is small — take the Kickstart/MAPROM decision before the
final FPGA reset pulse, so the FPGA latches the real state — and it works ✅: built on the project's
firmware fork and proven on one A4000D with Kickstart 3.2.3 in the sockets (2026-10-03), kickstart 0
then boots the socket Kickstart and SCSI-boots Amix on the real 68060, and kickstart 1 boots
unchanged. A second box confirmed it on 2026-10-03: on an A4000T the fixed firmware booted
DiagROM 2.0 from the motherboard sockets on the real 68060 ✅ (first-party record, 2026-10-03). The fix ships in
the project's fork firmware 1.0 (Z3660.bin, CRC-32 C96EAD03; source on branch release-1.0 of
jusii/Z3660-amix) ✅. Stock firmware, upstream 1.03 beta 21
included, still black-screens, so on stock firmware the rule above stands. The emulated-CPU modes are
not affected by this ordering.
68. Real-060 MAPROM boots take the reset vector from the socket ROM — the .d6 patch ✅¶
When the Z3660's real 68060 boots a MAPROM'd Kickstart, the reset vector (the initial stack
pointer and PC at $0/$4) still comes from the ROM chips in the motherboard sockets, read
through the overlay; only the code at $F80000 onwards comes from the mapped file ✅. So what is in
the sockets matters even though they are "not used":
- A socket ROM whose bytes 4–7 read
00F800D2(a stock Kickstart's entry point) sends the CPU to$F800D2of the mapped image, which is the mapped Kickstart's own entry — no patch needed ✅. - A socket ROM with another entry point — DiagROM enters at
$F800D6— sends the CPU four bytes further into the mapped image, into the middle of the Kickstart's ID string. The mapped image then needs a branch there: the.d6patch replaces the long at offset0xD6(00414D49→6000007A, aBRAto the real start) and recomputes the Kickstart checksum at0x7FFE8✅.
Known-good pairs (CRC-32 of the original → the .d6 image): A4000 Kickstart 3.2.3 (A4kOS323)
B6A4698E → FA5F933B; A4000T Kickstart 47.115 588A5E6D → 75B807FE ✅. Identify ROMs by CRC, never by file
name (§55); and keep real Kickstart chips (or a DiagROM with the
.d6 image) in the sockets for any real-060 MAPROM boot.
1. SCSI IDs: tape fixed at 4, disk 6 by convention ✅/🟡¶
The tape drive must sit at ID 4 — the root-floppy install scripts hard-reference the literal /dev/rmt/4h and look nowhere else for it ✅. The hard disk is ID 6 by convention, not by kernel mandate: the installer prompts for the disk target and computes the boot partition from a $SCSI variable (BPART=/dev/dsk/c${SCSI}d0s${BOOTPART}), reserving only ID 4 for the tape — so Amix will install on a disk at another target 🟡. The catch is that the chosen ID is baked into the device names (c6d0s…) the installed system records in /etc/vfstab (the SVR4 mount table) and the boot partition, so you can't change the disk's SCSI ID after install without editing /etc/vfstab to match 🟡 (amix_21_root.adf analysis via tools/inspect-adf.sh; see hardware).
Consequence for emulation: attach your tape image at SCSI ID 4 (the installer looks only there) and your hardfile (RDB) at SCSI ID 6 — any target works for the disk, but 6 is the convention and saves you re-pointing /etc/vfstab later. See the mapping in the WinUAE setup and the FS-UAE setup. Disk geometry and partitioning are covered in filesystems & disks.
2. The "16 MB Fast RAM ceiling" — a layout requirement, not a ceiling ✅¶
The folklore says the kernel hard-codes a 16 MB Fast RAM ceiling and that more "mis-maps the SCSI drive". The symptom was real; the mechanism was wrong (corrected 2026-09-29) ✅. The stock kernel registers the A3000's internal SCSI only when the loader placed the kernel above 0x07000000 (§57); a motherboard bank bigger than 16 MB starts lower, so the kernel loads lower, no card 0 exists and root is not mounted — measured on the bench with a 64 MB bank, 2026-09-29. Kernels that probe for the controller (they boot with a 64 MB bank) and the 040/060 kernels (128 MB, measured on a real 68060) are not affected. Separately, the describable-RAM arena (≈64 MB stock 030, ≈128 MB on the 4 KB-page 040/060 kernels — measured on a 68060, the 68040 not yet) on the RAM-ceiling page fails silently, not as a disk error. For the stock kernel on an (emulated) A3000, give it a 16 MB motherboard bank. Background in hardware.
3. Zorro III — stock drivers are Zorro II only ✅¶
Stock Amix is Zorro II only ✅: its stock drivers simply dereference the address autocon() returns, which only reaches Zorro II boards (≤ 24-bit, inside the identity-mapped low 1 GB), and that kernel source was never shipped so the stock card set can't be patched in place. This is not absolute for a purpose-written driver, though: one that maps the board explicitly — the kernel's sptalloc() primitive, or (when the board's window happens to fall inside the identity map) a direct mapping — can reach a Zorro III board. The A4091 SCSI driver and the Z3660 ethernet driver (✅ real hardware) both do exactly this. The A4091 driver's 68040/68060 arm reaches a Zorro III board on real hardware (root on an A4092 in an A4000D with a Z3660 ✅); the A4091 board itself and the 68030 line are proven in emulation only 🟡. For ordinary expansion, still assume a card must present a Zorro II window; see Zorro autoconfig for how boards are discovered via the AUTOCONFIG / autocon() path, and hardware for the supported-board list.
4. No 68040/68060 in stock Amix — the A4000 is out for a stock kernel ✅ (the 040/060 port changes this: see 040/060 status)¶
The kernel predates the 68040 MMU ✅, so it runs only on a 68020 or 68030 with a real MMU plus a 68881/68882 FPU ✅. There is no soft-float and the MMU-less 68EC020/030 variants cannot run Amix ✅. Practical upshot: an A4000 cannot officially run Amix ✅ — and that includes the A4000/030 as shipped, whose stock CPU card is a 68EC030 with no MMU ✅; it needs a full-68030 card. Targets are the A3000(UX) and the A2500UX. See hardware; for what 040/060 support would actually require and the current state of work toward it, see the 040/060 status page.
5. Superkickstart dual-boot via the right mouse button ✅¶
A3000UX machines ship a "Superkickstart 1.4" bootstrap ROM ✅. At power-on:
- Default (do nothing) → boot Amix from the SCSI disk.
- Hold the right mouse button at power-on → load an AmigaOS Kickstart instead.
So the "gotcha" is the inverse of what AmigaOS users expect: the machine boots Unix by default, and you reach AmigaOS only by holding a mouse button. Details in the boot process.
29. The kernel pokes the A3000 re-kick register 0xde0002 at every boot and reboot ✅¶
The stock Amix kernel writes motherboard address 0xde0002 unconditionally — once in its boot
path and again in its reboot/halt path ✅. What that write means depends on the machine:
- A3000 — the SuperKickstart re-kick control. Bit 7 of
0xde0002(a Gary register, flanked by the bus-timeout controls at0xde0000/0xde0001on the A3000) arms a hard re-kick: the next reset re-enters the bootstrap ROM and reloads the soft Kickstart from disk instead of warm-starting the RAM-resident image ✅. This is public, non-Amix hardware knowledge — Linux/m68k performs the identical write (0xde0002 |= 0x80) on A3000-class machines and names the mechanism "Magic Hard Rekick". On a SuperKickstart machine that is the right bit for a Unix to arm — see Superkickstart dual-boot and the boot process. - A4000 — an unconsumed latch. The same address decodes to Fat Gary's coldreboot/re-kick latch (the same bit-7 convention), but an A4000 boots Kickstart from real ROM — there is no soft-Kickstart machinery, and nothing on the machine consumes the latch ✅. The write terminates normally and has no effect. UAE-family emulators model it accordingly: a settable, readable bit-7 "coldreboot flag" with no consumer ✅.
- A4000D —
0xde0000/0xde0001decode too. Reads of both terminate normally (first read0xFFthen0x7F, then0x7F/0x7F; measured 2026-10-02) ✅. Writing0x80to$DE0001reads back0xFF, yet a following read of the undecoded$DD0000still hangs ✅ (measured 2026-10-02). The only source for the bit names is the UAE Gary model, which calls bit 7 of$DE0000the timeout, bit 7 of$DE0001TOENB (non-existent memory → bus error) and bit 7 of$DE0002coldboot 🟡; on real A4000 hardware the effect of TOENB, if any, is unsourced 🔴 — it does not produce a bus error for local I/O space (§66).
The gotcha is diagnostic: the poke surfaces as an unexplained motherboard-register write in bus
traces, emulator logs and disassemblies — on exactly the machine family (A4000-based rigs) where
the register does nothing, which invites chasing it as the cause of whatever else is wrong. Don't:
on a real A4000 + Z3660 the write is exercised on every boot — the accelerator forwards
it to the physical bus, where the real Fat Gary latches it — with no ill effect ✅. For emulator
and bus-bridge authors the only requirement is to tolerate the access (decode-and-ignore is
correct for an A4000 target; only an A3000 SuperKickstart model needs to honour the bit). And don't
confuse this motherboard address with the similar-looking AutoConfig product code 0xc0de0002 in
the major-number registry — they are unrelated.
41. The stock kernel infers the A3000 SCSI from where the kernel was loaded ✅¶
Stock autocon() (amiga/kernel/support.c) decides whether the A3000 internal SCSI controller
exists by testing whether the kernel's own end address end lies above 0x07000000 — i.e. where
boot2 placed the kernel — and not by touching the hardware ✅. The A3000 controller is not an
AutoConfig board, so there is nothing in bootinfo.autocon[] to look at; the load address was used
as a proxy for "this is an A3000-class machine" (the A3000's classic 16 MB motherboard bank sits at
0x07000000). This page long described it as a RAM-size test; it is a load-address test
(corrected 2026-09-29, §57). It is a guess, and it is wrong in
both directions:
- False positive. On a machine with no internal SCSI — a real A4000, or an A3000 whose controller
is absent — a kernel loaded high enough fabricates a phantom card 0 at
0xDD0000. It claimsqueue[0], shifts every real controller up by one, and the compiled-in root device (which decodes to card 0) sends the root read to hardware that does not exist ✅. This is the failure that produced thes5mountroot VOP_OPEN error 5→errno 89panic chain; full story on the boot process and Zorro autoconfig. - False negative. On a machine where the loader puts the kernel below
0x07000000— an A3000 with a motherboard bank bigger than 16 MB (64 MB starts at0x04000000), or fast RAM placed lower — the same test fails and the real internal SCSI is silently dropped — root disk included — and every remaining SCSI card renumbers ✅. Nothing is logged; the machine simply cannot find its disk. - The A4000T: a phantom that is not empty. On an A4000 the
0xDD0000window is empty; on an A4000T it holds the onboard NCR 53C710 — real hardware, the wrong driver ✅ (read from the shipped kernel'sautocon()and the stock sources, 2026-09-24). Boot is safe, because the stocksdinitialises a card lazily on its first I/O; but the first open of anyc0device runs the A3000 WD33C93 initialisation against the 53C710's registers 🟡 (read from the source). Under the Z3660 an unanswered CPU read in local I/O space never terminates (§66), so if the uninitialised 53C710 does not answer, ac0open is expected to freeze the whole machine on a hung bus cycle, not just stall one process 🟡 (predicted; not run). The amigaux 1.0 68060 kernels carry the chipset gate below, so they register no phantom and the Z3660 is card 0 ✅. On an 040/060 kernel built without it (the public port's mainline does not address the guess), never open ac0device on an A4000T. Details on the Z3660 SCSI driver page.
Real A3000s with more than 16 MB of motherboard RAM are inside the affected envelope ✅ — this is not an emulator-only curiosity.
The law: probe, don't guess. The fix that replaced the special case is a chipset gate plus a WD33C93 write/readback probe — read a custom-chip register that is always present to tell an ECS/OCS (A3000-class) machine from an AGA one, then write two different values to two WD33C93 registers through the same data port and read the first back, so an open bus (which aliases the read to the last write) cannot pass ✅. Full source and rationale on Zorro autoconfig; the anti-alias readback is the reference design for any board probe. Decode the chipset id by its low nibble (§61).
87. Z3660: back-to-back register accesses can land at (previous AND this address) 🟡¶
When the 68060 makes two accesses to the Z3660's register window back to back, the card can deliver
the second at the bitwise AND of the two addresses 🟡 (phantom writes with exactly that signature,
counted by a tripwire firmware build on two A4000-class machines; the FPGA mechanism is not confirmed,
and a proving boot did not reproduce it 🔴). For the piscsi mailbox and the ethernet registers this
turns ordinary access pairs into other commands ✅ (read from the register map). The defensive rule
costs little: read an offset that covers the target immediately before each write, in one asm
block at IPL 7, and read descriptors back before a doorbell ✅. Details and the affected pairs: the
Z3660 SCSI driver.
Networking quirks¶
6. DNS resolution is off by default ✅/🟡¶
Out of the box, name resolution uses /etc/hosts only — DNS is disabled ✅/🟡. To turn DNS on you relink the socket library and add config:
ln -f /usr/lib/libsockdns.so /usr/lib/libsocket.so # swap in the DNS-capable socket lib
# then provide /etc/netconfig, start in.named (or point elsewhere), and write /etc/resolv.conf
Also note the routing gotcha: the default route needs an explicit metric — route add default <gw> 1 (the trailing 1 is required) ✅/🟡. Full procedure in networking.
8. SLIP is buggy; no PPP 🟡¶
SLIP works but is buggy: you must reboot between SLIP sessions 🟡, and there is no PPP at all 🟡. If you need a serial IP link, plan for a reboot cycle rather than re-dialing. See networking.
11. Clock drift via SCSI interaction 🟡¶
The system clock drifts, reportedly because of an interaction with SCSI activity 🟡. On a networked box you'll want a time-sync workaround; combined with the Y2K issues below, treat timekeeping as something to actively manage rather than trust. See networking.
13. Enabling DNS adds ~3 minutes to every boot ✅¶
Turning DNS on (quirk 6 above) drops a hidden tax on the boot path: with DNS enabled the box stalls ~3 minutes at The system is coming up. Please wait. on every boot ✅ (reproduced locally on Amix 2.1c under Amiberry). Stock Amix, with DNS off, does not.
The cause is two boot-time ifconfig calls that take hostnames, each forcing a gethostbyname() ✅:
ifconfig lo0 localhost up— in/etc/rc2.d/S69inetifconfig aen0 `uname -n` up -trailers— in/etc/inet/network-config
Both run before the default route is installed (route add default … happens later, in /etc/inet/rc.inet). With DNS on, gethostbyname() tries DNS first, but there is no route to the nameservers yet, so each lookup burns the full resolver retransmit schedule — ~90 s each, ~180 s total — before falling back to /etc/hosts ✅. With DNS off the same names resolve instantly from /etc/hosts, which is exactly why this trap appears only after you enable DNS.
This is independent of the /etc/domain weirdness ✅: a box with an already-empty /etc/domain still hangs the full 180 s — the domain-append bug and this no-route hang are two different problems.
Fix (boot 210 s → 29 s; runtime DNS unaffected): give both boot-time ifconfigs a literal IP so no resolver call happens before the network is up ✅:
# /etc/rc2.d/S69inet
- /usr/sbin/ifconfig lo0 localhost up
+ /usr/sbin/ifconfig lo0 127.0.0.1 up
# /etc/inet/network-config
- /usr/sbin/ifconfig aen0 `uname -n` up -trailers
+ /usr/sbin/ifconfig aen0 <this-host-static-ip> up -trailers
lo0 is always 127.0.0.1; aen0 uses the host's own static address (the one already in /etc/hosts). Configuring your own interface should never depend on a name service, so this is the correct design regardless — and DNS for clients/mail/etc. is untouched. Full procedure in networking and networking on the LAN.
23. inetd disables a service after 40 connections in 60 seconds ✅¶
A port that answers Connection refused while ICMP, the console and every other port stay
healthy is almost never a wedge. SVR4 inetd counts invocations per service entry and, on the
40th inside a 60-second window, logs <service>/<proto> server failing (looping), service terminated,
closes the listening socket and arms a 600-second re-enable alarm ✅. All three constants are
compile-time immediates in the shipped /usr/sbin/inetd — there is no config knob: the
inetd.conf wait field is parsed as a boolean, so the 4.4BSD nowait.<max> syntax is silently
ignored (tested: nowait.100 + SIGHUP was accepted without complaint and still tripped) ✅.
Because the counter is per service, tripping telnet leaves FTP up and vice versa — which makes the diagnosis one command: port 21 answering while port 23 refuses ⇒ the throttle ✅. Nothing needs fixing; wait up to ten minutes. It only ever fires on automation: a harness that opens one telnet (or FTP) session per command reaches 40 in a minute trivially. Hold one session open for a batch, keep invocations under ~30 per 60 s per service, or drive bulk work from an on-box script and poll at a low rate. Measured: 29.9/min never tripped in 200 invocations; ~575/min tripped within seconds ✅. Full mechanism, constants and the syslog story on networking.
64. Stock in.ftpd silently truncates PASSIVE-mode downloads and still replies 226 ✅¶
A GET over a passive (PASV) data connection can arrive short, and the stock FTP server
still answers 226 Transfer complete ✅. Measured on the bench, 2026-10-02: passive transfers from a
cdfs mount came back short 18 times in 80 (always exactly 4 096 bytes, a correct prefix); passive
from UFS 0 in 80; active (PORT) mode 0 in 40; and a single 8 KiB read() of the same file was
whole every time ✅. A slow source (such as cdfs) is the timing trigger, not the cause. The server
appears to ignore a short socket write on the passive data socket and to set no linger or send-buffer
options on it 🟡 (diagnosis from the behaviour, not yet from the server's code). An earlier 68030
bench measurement saw 57–64 % of 1.4 MB GETs come back short ✅ (its FTP mode not recorded).
Because the truncation is always a byte-exact prefix, the file looks fine and only a checksum
disagrees. And because this ftpd implements no SIZE command, a client cannot ask for the length
up front. Workarounds: use active (PORT) mode, and length-gate every GET against the size
LIST/ls -l reports, retrying on a mismatch ✅.
72. A local AF_UNIX stream socket can deliver stale data under memory pressure 🟡¶
Reported by the amix-x11-aga project, a colour X server for
the native chipset, and not reproduced here 🟡. On Amix 2.1c, a read from a local AF_UNIX stream
socket can return data from whole 4 KB pages earlier in the stream when the system is short of memory
(about 8 MB of fast RAM) and the writer is non-blocking: 8 failures in 9 runs with a non-blocking
writer, 0 in 4 with a blocking one. The reporter saw it in emulation, also with the unmodified kernel
and no third-party driver, and on a real A4000 with the kernel limited to 8 MB; never with 12 MB or
more. X is where it bites: Xlib writes non-blocking and ships as binaries, so a large request (a
172 KB PutImage) is misparsed, the client dies with BadIDChoice or BadLength, and the server
cannot work around it.
Rule: give a box that runs X 12 MB or more of fast RAM. The reporter's reproducer,
test/sockstress.c, streams 32-bit counters through a local socket with Xlib's write pattern and
exits 2 at the first mismatch.
75. Inbound TCP bursts lose every segment past the third, and nothing counts it ✅¶
An FTP put into the box crawls at tens of KB/s while the get runs at full speed, the sender
retransmits hundreds of times per few MB, and the box's own counters show no error anywhere. The
cause is in the stock stack, not the card or the wire. ipintr passes each datagram up only if
canput() succeeds on TCP's lower read queue. Otherwise it frees the datagram, with no statistic
and its STRLOG compiled out. That queue holds 4096 bytes (tcpm_info[]'s hiwat), so in a
single ipintr pass the third full segment fills it and the fourth onward are discarded. The
per-connection receive window is a different queue and does not help.
Triage rule: compare netstat -s's IP "total packets received" with TCP's over one
transfer. A gap of hundreds, with every error counter zero, is this queue, not the driver. (The
honest gap on a quiet LAN is the broadcast floor of a few a second.) A polled or batching driver
triggers it most, because it hands IP several frames per pass.
Fix: raise that one module_info long word to 0x4000 (16384): in the stock 2.1c
/stand/unix at file offset 0xe6ba4, old value 0x1000. On a running kernel raise the live
queue's q_hiwat instead. Measured on a Z3660 box: push 24.6 → 334 KB/s, retransmissions 372 → 13.
Mechanism, offsets and numbers: networking.
14. An unhandled board interrupt storms the kernel ✅¶
A gotcha for driver authors, learned bringing up the Z3660 ethernet driver on real hardware: if a board (or its firmware) raises a CPU interrupt level for which Amix installs no handler, the unacknowledged source fires continuously and hard-locks the machine ✅. The Z3660 firmware raises Amiga INT6 (EXTER) on every received ethernet frame, but Amix has no level-6 ethernet handler — so the moment the interface came up, normal LAN ARP broadcast traffic stormed INT6 and instantly froze the box (caps-lock LED dead). The whole kernel had booted to login: fine; only bringing the interface up triggered it.
Fix: disable the board/firmware interrupt and poll the receive path instead — z3660eth writes the firmware's ZZ_CONFIG_DISABLE so INT6 is never raised, and drains RX from a clock-level timeout() callout ✅. Interrupt-driven RX would need a level-6 hook the firmware's model doesn't safely give Amix. The general lesson: when adding a driver, don't enable an interrupt source Amix can't service — confirm there's a handler (and an ack path) for that level, or run polled. Full story on the Z3660 ethernet driver case study.
FPU & floating-point quirks¶
31. On an FPU-less part, floating point dies as SIGSYS ("Bad System Call") ✅¶
The F-line trap's stock mapping is SIGSYS, so an FP instruction on a part with no FPU exits 140
with Bad System Call - core dumped — a label that points a debugger at the syscall interface,
which is not involved. Anything grepping crash reports for illegal-instruction traps misses every
one of these. The reach is wider than "programs that compute with floats": libc's _doprnt carries
FP instructions on its %e/%f/%g conversion paths, so any program that prints a float dies
inside libc — df -k and uptime die in the print call, not in their own arithmetic. __fpstart
does not touch FP hardware, which is why everything else runs normally and the failure set is
exactly the float-printers. Because the whole class funnels through one libc path, one fix covers
all of it. Triage rule: "program dies with SIGSYS in a print call" means FP-in-libc, not a syscall
bug — ask "does it print a float?" before investigating anything else.
On an FPU-less 68030 the set is wider than the float-printers 🟡. Once the 68030 line's signal
gate lets such a system run (§74), more programs die with Bad System
Call (exit 140) than awk and the float-printers. They include the stock mkfs -F ufs, so no new
ufs can be made on an FPU-less 68030, and GNU cp and gtar built with the gcc14 userland
toolchain. dd, cmp, sh, sleep and the stock cp run. The kernel stays up throughout. Which
FP instruction each of these reaches has not been traced yet 🔴, so for them "does it print a float?"
is not the test. Seen in emulation only (Amiberry, fpu_model=0, AttnFlags 0007); a real
FPU-less 68030 has not run them yet.
32. awk is always a floating-point program ✅¶
awk 'BEGIN{print 1+1}' dying on an FPU-less part looks like proof of a second, non-FP defect. It
is the opposite: awk holds every number as a C double, so it prints a float even for pure-integer
expressions. An awk failure on integer arithmetic is the same FP defect, not a new one.
74. Without an FPU the stock 68030 kernel boots, then panics at the first signal ✅¶
The stock kernel's boot probe copes with a missing 68881/68882: it prints no fpu detected, and
the boot carries on through the driver banners and the copyright block. Seconds after init starts,
the machine dies:
TRAP proc = … (pid 1, init) psw = 2000 pc = <kernel load base>+0x12A
PANIC: KERNEL FAULT psw=0x2000, pc=…12A, fmt=0x0, vector=0xB (Line-F Emulator)
Text offset 0x12A is the stock frestore wrapper, a bare FRESTORE. With no coprocessor that is
an F-line instruction, and here it runs in supervisor mode, so it is a kernel fault, not a user
process's SIGSYS (§31).
What reaches it is signal delivery: sendsig calls fpu_setup, and it is the one caller that
does not check fpu_present first. Of the eleven calls into the kernel's FP-state routines
(fpu_save, fpu_restore, fpu_setup), ten are gated, among them the boot probe fpuinit and
setregs on exec. So the first signal is fatal, and one comes at once: init's first child
exits and init takes SIGCLD. The probe covers boot, not signals: "boots without an FPU" is
true of the kernel and false of a running system. Seen on a real A1200 with an FPU-less 68030 card
(Kickstart 3.1, kernel loaded at 0x08000000, so pc = 0x0800012A) and reproduced in Amiberry
(cpu_model=68030, fpu_model=0) at the same text offset (pc = 0x0020012A, kernel loaded at
0x00200000).
On the stock 68030 line the 68881/68882 requirement is therefore a kernel-side wall: such a system
never runs long enough to show §31's userland failures. Triage rule: no fpu detected at boot,
then a kernel fault in pid 1 at the load base + 0x12A, vector 0xB — the card has no FPU. It is
not the disk driver, not Kickstart and not RAM.
The 68040/68060 port's 68060 kernels are not affected: src/fpuinit060.s gates exactly that
sendsig call (fpu_setup_gated), which is why the FPU-less 68LC060 lane boots and runs (it is
where §31 was measured; see the 040/060 page).
Fixed on the 68030 line, in emulation ✅. The project's 68030 kernels carry the same gate in its
68030 form: src/fpugate030.s provides fpu_setup_gated, which returns at once when fpu_present
is 0, and the build retargets sendsig's one fpu_setup call to it. Only that relocation changes,
and the build checks it after the link. The gate is on by default; --no-fpu-gate builds the
previous kernel byte for byte. On an emulated FPU-less 68030 with its MMU (Amiberry, fpu_model=0,
AttnFlags 0007), stock 2.1c panics as above at the first signal and boot-loops. The gated kernel
reaches multi-user and survives SIGCLD, a trapped SIGINT, kill -INT of a sleeping process and
SIGALRM; its skip count rises with each signal. Userland floating point still dies with SIGSYS
(awk printing a float), which is §31 and unchanged. With a 68882 the gated kernel behaves as
before (awk prints 1.50). A real FPU-less 68030 has not run the fixed kernel yet 🟡, and it is
not in a released 68030 kernel yet.
The gate saves the kernel, not the userland: on the gated kernel the stock mkfs -F ufs and
gcc14-built GNU cp and gtar still die with SIGSYS (§31, 🟡 emulation), so an
install on a 68030 still needs a 68881/68882.
Planned, not yet implemented 🟡: a software FPU on the 68030 line — NetBSD's FPE, the one the 68LC060 lane runs — which would carry the userland on FPU-less 68030 cards.
68060 CPU quirks¶
33. 68060: the FP-disabled frame's "Next PC" is software-maintained 🟡¶
In the 68060's eight-word FPU-disabled exception frame, the stacked "Next PC" is not an architectural guarantee that the CPU sized the faulting instruction: Motorola's own 68060 support package builds and rewrites that field itself, from its own decode, and treats the PC-of-faulted-instruction slot as the authority. Any handler for this frame should resume on a PC it computed from its own decode and treat the stacked Next PC as advisory. (Tag stays 🟡: read from the vendor package's source behaviour, not verified against the MC68060 User's Manual.)
34. 68060: immediate #<data> is the one F-line operand the hardware cannot size 🟡¶
For every F-line addressing mode except immediate data, instruction length follows from the
operation word alone. For #<data> the operand's byte count lives in the FP command word's
source-specifier field — precisely the field a disabled FPU has no reason to decode — so the
stacked next-PC can be short by the operand's width (an IEEE double immediate is 8 bytes the frame
never accounted for). This is the structural reason quirk 33's field cannot be trusted, and it names
the one instruction class where the discrepancy occurs.
53. 68060: the unimplemented integer instructions trap to a vector stock Amix never populated ✅¶
The 68060 does not implement every integer instruction form in silicon. The 64-bit
MULU.L/MULS.L/DIVU.L/DIVS.L forms — a 64-bit product, or a 64-bit dividend with its
quotient/remainder register pair — are trapped out to software through the CPU's
unimplemented-integer-instruction exception (vector 61), for an emulation layer to serve ✅. The
stock SVR4 trap-dispatch table on this platform stops at vector 55, so that vector has no
handler: on a 68060 kernel that adds none, the trap falls through to the kernel's default
unhandled-vector path and the process is SIGKILLed with no diagnostic at all ✅ — a bare Killed
and exit status 137, no SIGILL, no SIGFPE, no named fault, no core, and nothing in any log. Two
consequences:
- It is not an FPU problem, and nothing in the failure says so either way. The affected instructions are integer ops the core leaves to software, so the kill fires identically on a full-FPU 68060 and on an FPU-less 68LC060 ✅. Do not file it next to the SIGSYS float-printing class — that one is F-line and FPU-shaped, this one is not.
- The 32-bit forms are safe.
MULU.L/DIVU.L/DIVUL.Lin their 32-bit spellings are hardware-implemented and execute normally ✅.CMP2/CHK2/CAS2/MOVEPbelong to the same unimplemented-integer class architecturally and should be expected to behave the same way where nothing emulates them 🟡 (class membership per the CPU's own definition of the set, not each form separately exercised).
What the amigaux 1.0 kernels change ✅. The 1.0 68060 kernels carry Motorola's 68060 Integer
Support Package on that vector: the 64-bit multiply and divide forms, MOVEP and CMP2/CHK2 are
emulated and run, and only CAS2 and a misaligned CAS still kill the program. Earlier 68060
kernels emulated at most some 64-bit multiply forms, so a 64-bit divide was still killed there.
Practical rule for anyone emitting 68060 code — a compiler, an assembler macro, a hand-patch:
keep to the 32-bit integer forms anyway, because the emulated 64-bit forms are slow and an older
68060 kernel kills the program; never emit CAS2 or a misaligned CAS ✅. Because the trap is
silent, the diagnostic signature is a freshly rebuilt binary that runs a while and then dies
Killed with rc 137 where the previous build did not — check the instruction encodings before suspecting the program. This is distinct from the GNU as
2.8.1 size-bit defect on the toolchain page, which produces a 64-bit
divide form by accident on a 68030: same instruction family, different cause, different platform.
70. 68060 (emulator run): the stock 1992 installf crashes — use APKG's package tools 🟡¶
On a 68060 kernel the stock /usr/sbin/installf, run as installf -f core to finalise a
package entry, dies with User BUS ERROR at 4AFC0002 ✅. It leaves a core file in / and a
half-registered entry in the package database — a contents line whose size, checksum and date
fields are still ?. On the same
system the project's package engine's installf (/usr/apkg/bin/installf) finalises the same entry
cleanly: exit 0, with the checksum the kernel really has ✅. Observed in an emulator run of the 68060
kernel (2026-10-03); not yet on metal 🟡.
Rule: on a 68060 system (no 68040 was tested), route every runtime package operation through the
APKG tools (/usr/apkg/bin/…); treat the stock pkgadd, pkgrm, installf and removef as suspect
there. Use the engine amigaux 1.0 ships, APKGENG 1.12.3, built without 64-bit divides ✅: the July
2026 1.12.0 build published on the package repository has its own installf -f and pkgchk killed
by them on a 68060 kernel before 1.0, which does not emulate the divides
(§53) ✅.
Why the stock binary faults is not established 🔴 — the same kind of 1992-binary-meets-new-CPU
problem as §53 is a plausible family, not a diagnosis. Run such
tools from a scratch directory such as /tmp, so a crash cannot leave a core file in /. See
package management.
82. 4 KiB pages and small-block filesystems ✅¶
The 68040/68060 kernels use 4 KiB pages; the stock 68030 kernel 2 KiB ✅. Three consequences:
- Stock
mkfsrefuses ufs block sizes under 4 KiB on both lines ✅, so a normal ufs is safe on either kernel. - A ufs with 2 KiB blocks (only a modified
mkfscan make one) mounts on the 040/060 kernel and panics it at the first file access (ufs_getapage bad bsize) ✅; the 030 kernel handles the same disk fully. The panic is inherited (a 060 kernel with ufs built in does the same), not caused by any later change ✅ (emulation). A mount-time refusal is being added to the 040/060 kernel 🔴. - s5 is broken on 4 KiB pages: an s5 root on the 040/060 kernel bus-errors in ordinary commands
(
ls -l) ✅ (emulation). Use ufs on the 040/060 line.
Diagnostics & logging quirks¶
24. syslogd is present, correct, and deliberately switched off ✅¶
The stock image has a complete, working logging system that never starts. /usr/sbin/syslogd, a valid
/etc/syslog.conf, all eight /var/log/* targets and /dev/log are present — but
/etc/init.d/syslogd line 8 is a vendor-hardcoded exit ✅, so the daemon is never started and
every syslog(3) message on the box is written into /dev/log with nothing reading it. The comment
above the line says so out loud: #TO USE SYSLOGD, COMMENT OR REMOVE THE exit ON THE NEXT LINE:.
Consequence: every daemon.* and auth.* event this system generates is discarded, which is why
failures like quirk 23 look
inexplicable. Note also that this SVR4 logs to /var/log/*, not /var/adm/messages ✅ — looking
for the latter finds nothing and yields the wrong conclusion ("this image has no syslog").
Enabling it is one commented-out line and needs no /etc/syslog.conf change (the stock
*.notice;kern.none /var/log/notice line already selects daemon.err) ✅. Measured cost across
23 minutes of deliberate abuse: one daemon and 293 bytes total in /var/log ✅.
🔗 Edit it with
cpin place, nevermv./etc/init.d/syslogdand/etc/rc2.d/S70syslogdare the same inode (3 hard links).mvreplaces the file and silently breaks therc2.dhook, so the fix works once and never again after a reboot ✅ — the same hazard asS69inet/inetinit.
Enabled is not the same as running: the script skips the start whenever the previous boot's pid,
left in /etc/syslog.pid, belongs to a live process at S70 time, and a small change to the early boot
is enough to make that happen on every boot ✅. See
the stale pid file.
40. Amix keeps no persistent boot log — the console is the only record ✅¶
Quirk 24 is about syslogd shipping switched off. This is the broader, orthogonal fact it sits
inside: Amix has no persistent boot logging at all, whether or not syslogd is ever turned on ✅.
Three independent subsystems each drop their boot-time output on the floor, so "what happened when
this box last booted?" has no on-disk answer:
- There is no
dmesgon Amix ✅. Unlike Linux or BSD, there is no kernel ring buffer to dump after the fact — the command and the facility simply do not exist. (Confirmed on a real box, and by zerodmesghits anywhere in this corpus.) wtmphas never recorded a single reboot — on any boot, ever ✅. SVR4's login-accounting file, whichlast/last rebootread for boot history, holds zero reboot records across its whole life: its own header timestamp is Mar 24 1992 andlast rebootreturns nothing. This is independent of the syslogd question —wtmpis a separate, always-on accounting mechanism, and it is still empty of boot history.- The boot-time root
fsckleaves no on-disk trace by design ✅./sbin/bcheckrcdiscards its pre-check verdict to/dev/nulland sends the real repair pass's output to the console only — notee, no log file — so even a root-fsck repair, had one run, would leave zero evidence it happened.
The practical consequence: for any "what happened at boot" question — a hang, a fsck repair, a
kernel's boot banner — plan for a passive HDMI console capture, because there is no log to read ✅.
Turning syslogd on (quirk 24) does not fill this gap: it starts capturing future daemon/kernel
syslog(3) messages, a narrower channel than the raw console boot transcript. The one channel that
does survive is the physical console — and at a fresh login prompt the console has not scrolled
past the boot transcript: the visible frame still holds the first kernel line through to login:,
so a single passive HDMI capture at the prompt recovers the whole boot sequence with no login and no
keystroke ✅ (used exactly this way to answer a "did fsck run?" question with no other evidence
source). See quirk 24 for the
syslogd half and networking
for its config.
26. Three diagnostic tools that lie or do not work ✅¶
Recorded so nobody re-chases them, and so nobody plans a capture around them:
netstat -aprintscorrupt control block chain— on a fully healthy box. A control run during a known-good session prints the identical message, so it is an Amixnetstatlimitation, not evidence of anything ✅. (netstat -mlikewise answersMemory information not currently supported.)netstat -inandnetstat -rnworked on the boxes of that soak; on an image whose/stand/unixis not the booted kernel they fail (§93).sar -vfails withsadc: Not enough space✅. On an amigaux 1.1 module kernelsar -rfails too, withsadc cannot copy inode table: No such device or address✅.ipcs -afails withread error: No such device or address✅.
The last two are pre-existing tooling gaps on the stock image, not symptoms — don't budget on them when planning what to capture from a box in a bad state.
27. dd skip= on cdfs is not a seek ✅¶
SVR4 dd's skip= on a cdfs file really transfers the skipped bytes rather than seeking past
them, and the in-kernel cdfs read path is capped at one 2048-byte sector per SCSI transfer (see
the in-kernel DMA cap).
A read at the 1 GiB mark of a large disc therefore takes minutes of real transfer — budget a
command timeout of about 20 minutes, not the usual seconds ✅. Never score a timeout on a deep cdfs read as a read failure — re-run it with
a real timeout before concluding anything.
35. A zero behind a gate needs a pre-gate denominator ✅¶
Withdrawn 2026-10-04 (test methodology, not an Amix fact).
36. An optimised probe reporting "no fault" must be disassembled first ✅¶
Withdrawn 2026-10-04 (test methodology, not an Amix fact).
37. A one-byte binary diff is not understood until the byte is decoded ✅¶
Withdrawn 2026-10-04 (test methodology, not an Amix fact); the kernel fact (the controller registry's row-count bound must be widened with the table it names) lives on kernel composition.
38. uptime and w do not report the machine's load ✅¶
Two independent stock defects: nlist(3) cannot resolve avenrun (a COMMON symbol in the ET_REL
kernel) and succeeds anyway with the alignment as the value — so the printed number is whatever
sits at a bogus address, flat 0.00 and six-digit garbage being the same defect; and the kernel's
own load count omits the on-CPU process, reading exactly N−1 even when read correctly. Full story,
measurements and the working read recipe: load averages.
42. When you move a boundary, sweep the neighbours ✅¶
Withdrawn 2026-10-04 (method note); the kernel fact (segkmap is sized from physmem) lives on the RAM ceiling.
43. nm and relocation similarity are not enough to place a binary patch ✅¶
Withdrawn 2026-10-04 (method note); the kernel fact (segkmap is sized from physmem) lives on the RAM ceiling.
44. On-box adb -k needs a namelist that still has a symbol table ✅¶
adb -k reads symbols from a namelist file, and on Amix the obvious candidate is the wrong one:
/stand/unix has been through elf2brel, which discards the symbol table ✅. Point adb at it
and you do not get a clean failure — you get resolutions that are silently meaningless.
Keep an unstripped namelist beside the kernel (unix.namelist, the pre-elf2brel object) and read
against that ✅. The rule generalises: on this platform, the file you boot and the file you debug
against are two different files — verify the one you hand a debugger actually has a symtab before
believing anything it prints.
The one exception is a kernel linked natively on the box. A /stand/unix produced by an on-box
native link still carries its symbol table, so there it doubles as its own adb namelist ✅ —
adb -k /stand/unix /dev/mem resolves correctly, and no separate namelist file is needed. So the
invariant to check is not which file but does this particular file still have a symtab; the
answer depends on how that kernel was produced. On an amigaux 1.1 module kernel /stand/unix is a
namelist that kbootset writes at the booted generation's runtime addresses, so
adb -k /stand/unix /dev/kmem reads live values there too ✅
(kernel probing).
45. Heavy raw dd to the boot slice can reboot the box 🟡¶
On one machine, a large raw dd to the boot slice spontaneously rebooted it mid-write 🟡.
This is a single-unit observation with no known mechanism, so it may be a fault of that unit rather
than Amix behaviour. Because the reboot kills the session that issued the command, the operator
sees a transport failure — a dropped
connection or a timeout — and the natural conclusion is that the write never happened.
Trust the on-disk record, not the session. Have the job write its own verify line to a file on disk, and read that file on the next boot; the session's exit status is not evidence either way. The general shape is worth naming: an operation that destroys its own reporting channel always looks like a failure — and re-running it blindly is how a half-written boot slice becomes an unbootable one.
46. adb -k prints a one-byte kernel flag as a big-endian int ✅¶
Reading a driver's boolean flag out of a running kernel prints an absurd number:
The flag is set, and the read is correct. 16777216 is 0x01000000: the flag is a one-byte
object, /D reads four bytes and formats them as a big-endian signed int, so a set byte at the
object's address lands in the top byte of the word ✅. Nothing is wrong with the kernel — the format
letter is wrong for the object's width.
Read a byte object with a byte format, or learn the shape: 16777216, 65536 and 256 are "a byte
holding 1, read as a 4-byte int" at the first, second and third byte of the word. The trap is
expensive precisely because the wrong answer is plausible — a reader who has just patched a driver
sees a huge number and concludes the flag was never initialised.
47. A successful remote halt looks like a transport failure ✅¶
sync; sync; /sbin/uadmin 2 0 issued over the box's telnet transport kills the session executing
it, so a remote runner reports a transport failure — a no-route or a timeout ✅. That
signature is the success case. It is the same family as §45: an operation that
destroys its own reporting channel always looks like a failure, and re-running or power-cutting it
"because it failed" is how a completed clean halt becomes a dirty one.
The proof of a clean halt is the next boot, not the halt. Amix keeps no persistent boot log
(§40), so the evidence is a verification boot whose console shows
fsck repairing nothing ✅. Observed twice on a real A4000 + Z3660: runner "failure", fsck-silent
next boot, clean filesystem.
Image a disk only after a halt that a fsck-silent boot has proven clean, or every clone inherits a dirty filesystem.
58. A run-level change requested over telnet while the console sits at login: stalls in stock init ✅¶
shutdown -y -g0 -i6 (or init 6) issued from a telnet session while nobody is logged in on
the console — the console getty sitting at login: — stops at INIT: New run level: 6 and goes
no further ✅. init kills sac and the console getty, then waits; /etc/rc6 is never spawned, a
second init/HUP is ignored, who -r reports level 6, the disk keeps working and telnet still
answers. Only a power cycle brings the box back.
It is stock init, not a driver: discriminated in eight runs (2026-09-29), it reproduces
identically on the stock kernel on an A3000 with no third-party driver. With a root shell logged in
on the console the same command reboots normally (100–150 s) on every kernel tried ✅. The stall is
proven for level 6; a level-0 halt requested the same way completed in the one run that tried it 🟡.
The root cause inside init is open 🔴 (it needs init's symbols or a crash dump).
Rule: request run-level changes from a console root shell, or keep one logged in while you work
over the network. A box that has stalled this way still answers telnet: sync; sync, then
power-cycle (§49), and expect fsck on the next boot. As with
§47, a remote shutdown is proven only by the boot that follows it.
48. A USB-serial console is a single-reader channel ✅¶
Withdrawn 2026-10-04 (host-side bench practice, not an Amix fact).
49. A warm reset is not a clean-state reset — power-cycle, and leave it off 🟡¶
A single-unit observation — one Z3660 card running the stock accelerator firmware, in the emulated-CPU boot mode, not yet reproduced on a second card 🟡:
| reset path | correct reset vector read |
|---|---|
| power cycle / cold boot | 5 / 5 |
| the firmware's warm-reset command | 0 / 3 |
Every warm-reset failure read the reset vector as 0xFFFFFFFF; the emulated 68k then ran from a
garbage PC and scribbled down through the board's registers and the exception vectors, leaving a
black screen and a flood of unhandled-write messages on the serial 🟡. On that card a warm reset was
fine for "start the machine again" and useless for "start it from a known state".
Why an odd value at address 4 is not evidence of a corrupted ROM. With the Kickstart overlay cleared, address 4 is chip RAM, not ROM ✅ — an unexpected value there is stale memory, not a corrupted ROM fetch, and a single-bit difference between two observed values is not a bit flip.
The off interval is part of the procedure. Allow ≥15–20 s off between power-off and power-on.
Consequence for any procedure needing a known-clean machine — a diagnostic run, a clock change, an acceptance test: power-cycle with a ≥20 s gap, and gate the run on the reset vector the firmware prints before trusting anything the run produces.
50. A diagnostic ROM's error mask is sticky within a boot ✅¶
Withdrawn 2026-10-04 (bench evidence practice, not an Amix fact).
51. Memory detection that reads back immediately is fooled by capacitive echo ✅¶
A detection probe that writes a pattern and reads it straight back cannot tell populated memory from an unpopulated bus still holding the value it was just driven with ✅. Two independent instances in a single session: a 16 MB motherboard window whose top 4 MB is genuinely empty passed detection, and a phantom "Detected 127MB of memory" over a range the emulator maps to a dummy bank — one whose reads return all-ones and whose writes are discarded.
Treat any "Detected NNN MB" as unverified until a whole-block address check confirms it — fill the whole block with address-derived data and verify it afterwards, once the echo has decayed.
The same caution applies to a hand-rolled peek/poke check: one that probes only low addresses proves only that the low addresses echo.
52. A run whose boot path was not examined is not evidence ✅¶
Withdrawn 2026-10-04 (bench evidence practice, not an Amix fact).
77. syslogd needs m4, which only the development set ships ✅¶
The stock syslogd runs /etc/syslog.conf through /usr/ccs/bin/m4 before parsing it ✅. On an
installation without m4 the daemon starts, sh: /usr/ccs/bin/m4: not found goes by, and every
file action in the configuration is UNUSED: the log driver delivers messages and nothing is
written ✅. m4 is part of the stock C development set (Cdev) and nothing else ships it ✅; a minimal
installation without that set has the same hole 🟡. Together with the vendor exit
(§24) and the stale pid file
(networking), that is three independent reasons for an empty
/var/log.
78. Kernel messages after boot reach the message buffer only through a console logger ✅¶
Once the kernel finishes booting it sets its print destination to the buffer path (prt_where
0x2). From then on a kernel printf line is stored in the message ring (putbuf) only through the
console logger — the log driver's copy for a process that registered with I_CONSLOG, which is what
syslogd does ✅. With no logger registered (conslogging 0), every run-time kernel line goes to the
console device by putchar and is kept nowhere ✅ (read from the kernels' print code, identical on the
030 and 040/060 lines, and shown on both in emulation). Diagnose a driver on a box without syslogd
from the console, not from the ring.
80. The kernel namelist is /stand/unix, not /unix ✅¶
netstat, ifconfig (no namelist /stand/unix when it is missing) and crash compile in
/stand/unix as their symbol file; ps reads no kernel file; nothing reads /unix; there is no
/dev/ksyms ✅. They look up addresses by name in that file and read them through /dev/kmem, so if
/stand/unix is not the kernel that is running — a relinked kernel booted from elsewhere, or a hard
link left pointing at the stock kernel — they read the wrong addresses silently ✅. Keep /stand/unix
the booted kernel's bound image. On the project's 2.1c bench image /stand/unix is the retail
2.1 kernel, byte for byte, while the image boots a different 2.1c link ✅
(§93): every namelist reader there gets retail 2.1 symbols. On an
amigaux 1.1 module kernel kbootset keeps /stand/unix the booted generation's namelist ✅, and
adb -k reads through it, though crash still fails there (could not read namelist timestamp)
(kernel probing).
Driver-authoring quirks¶
15. An un-enumerated board silently never runs ✅¶
Amix 2.1's bootinfo.autocon[] table can miss a board that is physically present — seen on the
Z3660, both when its combo window sits at a fixed base outside the AutoConfig chain
(autoconfig_rtg NO) and when Kickstart does configure it (autoconfig_rtg YES) but the 2.1 table
still doesn't list it ✅. If the kernel's sd.c registers controllers only via autocon(), the
driver's queue function never runs — and the failure is silent: the kernel prints its banner, then
nothing (no panic, no I/O, no log) ✅. The same bootinfo table is independently known to be
unreliable on 2.1p2 — the hydra driver carries a three-method
detect for the same reason.
Fix — register the board by more than one method: try autocon() first, then a chipset-gated
probe of the board's known fixed base accepted only if a presence register reads a valid value (so an
absent board can't false-positive), then a driver.conf probe= fallback hook. On the Z3660
piscsi driver this multi-method detect is exactly what carried the first real-hardware boot past the
silent banner ✅. Full story on the Z3660 piscsi SCSI driver; the
chipset (AGA/ECS) gate is shared with the A4091 driver.
16. On an emulator, "the machine hung" ≠ "your driver hung" ✅¶
A board can appear to boot and then hang with a brand-new driver as the obvious suspect — and be innocent. Bringing up the Z3660 piscsi SCSI driver on real hardware, the box booted and then froze; the cause was the Z3660's firmware 68k emulator core faulting while demand-paging the driver's own text back in (two MMU exception-frame bugs), not the driver — which was carrying every disk transfer byte-perfectly ✅. On an emulated 68k the "CPU" is itself software, so a crash there presents identically to a driver lock-up.
Triage method: combine kernel-side serial instrumentation (prove your transfers completed and
were byte-correct) with core-dump analysis (adb / capstone disassembly of where the "CPU"
actually died) to isolate the fault to the emulator rather than the driver, before rewriting working
driver code ✅. The emulator fixes themselves are owned by the Z3660 firmware repo; the durable lesson
here is the triage discipline. The emulator-side mechanism — 68030 bus-error-frame semantics on the
demand-paging path — is documented on Emulation Fidelity.
17. An instruction-batching emulator can hang the SCSI bootstrap 🟡¶
Amix's A3000-mainboard SCSI bootstrap is driven by an INT2 (level 2) interrupt during the
earliest kernel startup, before the banner. An emulator that, for throughput, runs several guest
instructions between checks of the interrupt line raises the worst-case INT2-detection latency to
roughly that batch size — and if the batch is too coarse the bootstrap is starved of timely service
and hangs before the banner 🟡 (a pre-banner stall, distinct from the demand-paging faults in #16).
On the Z3660's emulator this is exposed by a service_cadence N knob (instructions per
interrupt-service poll, default 1): cadence 2 and 4 boot, but cadence 8 hangs the SCSI bootstrap, so
the Amix-safe value is service_cadence 4 (raise it only after boot). The knob mechanics are ✅; the
INT2-latency attribution and the exact boundary are an author hardware sweep 🟡. Full story on
Emulation Fidelity.
19. mount(2) silently stacks mounts (a fake broken-umount) ✅¶
mount(2)'s only busy check is v_vfsmountedhere on the vnode lookupname returns — but for a
directory that is already a mount point, lookupname traverses into the mounted filesystem,
whose root has that flag clear ✅. So a second identical mount of the same media on the same
directory returns 0 and stacks a second layer; a later umount pops exactly one layer and also
returns 0, while the disc appears "still mounted." This imitates a broken umount convincingly: a direct umount(2) returns 0 with the
filesystem apparently still there, while the real cause is silent stacking on repeated mounts ✅. Stock filesystems guard this in their
own mount op (prmount/fdmount refuse when the covered vnode has v_count > 1 or VROOT), and an
in-kernel filesystem must do the same (return EBUSY on the second mount). Diagnostic: count the
fstype's lines in /etc/mnttab — duplicates are the smoking gun. Full contract on
the driver model.
30. A scripted ed edit that misses its address still writes the file ✅¶
Anyone scripting kernel-source edits (master.d/kernel.c, master.d/filesys.c, a subdir
Makefile) ends up pushing an ed heredoc — and its worst failure mode is silent ✅. When an
address search misses, ed prints ?, but the commands that already ran stay applied to the
buffer and the trailing w still writes the file. Pair an insert with an address-searched change
in one body and a miss leaves the file half-patched: insert landed, row change never happened,
kernel builds and links, device never answers. If the wrapper's idempotency guard is a
name-wide grep, the surviving insert satisfies it, and every later run reports
already-patched — a permanent, invisible half-patch produced and then defended by the same
script ✅. Measured variants: a tag-addressed change overwriting the wrong claimant's row with
no error, and ed's wrapping /re/ search landing a table row inside a different table,
exit 0 ✅. ed's exit status is a weak oracle (behaviour across ? errors is not portable
between the host's GNU ed and the box's SVR4 ed, and a remote-shell transport can swallow it)
— the reliable mechanism is pre-probe the exact target row before writing, one ed run per
edit, and post-verify the row in the file afterwards ✅. The full failure catalogue and the
fail-closed verify-then-apply pattern are on
building a kernel.
63. Stock dd.c counts every unit in 512-byte blocks — a 2048-byte CD driver must cap the transfer ✅¶
The stock disk layer addresses every SCSI unit in 512-byte units (count = b_bcount / 512),
whatever the device's real block size ✅. A driver for a 2048-byte CD unit that multiplies that
count by its own block size asks the device for four times the caller's buffer, and the device
writes it linearly over unrelated memory. On the Z3660 this was the kernel hang behind a raw dd of a
CD unit (/dev/rdsk/c3d0s0), and the slice open's RDB scan was itself a 1 536-byte overrun ✅. cdfs
was never affected — it queues its own correctly-sized requests.
Rule for driver authors: before issuing a READ/WRITE, refuse any request whose block count ×
block size exceeds the buffer (nbyte) — fail it with EIO, count it, and never touch the hardware.
The Z3660 SCSI driver does exactly this since 2026-10-02 (raw dd of a CD unit at 512, 2048 and
65 536-byte block sizes returns I/O error, the box stays up, the refusal counter steps by 3 — metal,
A4000T) ✅. Kernels without the fix still hang on a raw read of a Z3660 CD unit; mount it with cdfs
instead.
The same 512-byte assumption shows on an ATAPI unit behind the Gayle IDE driver: stock dd.c puts
its 512-byte block numbers into READ(10), so dd bs=2048 of a CD asks for LBA 4N and keeps the first
2 KiB of each 8 KiB — every fourth block ✅ (emulation). Check a CD's contents with mount -F cdfs,
not with dd.
65. Stock autocon() can never match a board whose manufacturer id is ≥ 0x8000 ✅¶
A driver asks autocon() for its board by a 32-bit id, manufacturer in the high half and product in
the low half. Stock autocon() (amiga/kernel/support.c) holds that id in a signed long and
compares the board's er_Manufacturer with id >> 16 — an arithmetic shift, so for a
manufacturer id at or above 0x8000 the high half comes back sign-extended (0xC0DE0001 >> 16 =
0xFFFFC0DE) and never equals the unsigned 16-bit field ✅. The board is handed over correctly —
boot2 copies every ConfigDev into bootinfo.autocon[], and a read of the live kernel shows the
entry with exactly the right id — yet the driver's autocon() call misses it, sd registers no
controller, and nothing is printed ✅.
Nobody saw it for 30 years because every Commodore id (0x0202), the Z3660 (0x144B) and the
other boards Amix has met sit below 0x8000; the A4092 (0xC0DE) was the first ✅. Fix: make
the shift unsigned. In source that is a cast in support.c (the compiler's swap/ext.l becomes
clr.w/swap); in an already-linked kernel it is one byte, the shift's asr.l → lsr.l. Ids
below 0x8000 are unaffected. Proven on metal 2026-10-02: on an A4000D the A4092 at 0x40000000
was handed over but never registered without the fix, and registers as card 1 with it ✅. A driver
for a board with a high manufacturer id cannot rely on stock autocon(); it needs the fixed kernel
(or its own scan of bootinfo.autocon[]).
69. What a 68040/68060 kernel actually sends to the disk: 2 KiB pieces, inode blocks twice ✅¶
Measured on the project's emulator rig (2026-10-03, a capture of every disk write, replayed exactly onto the image) and read from the kernel binary — the facts a disk-driver author or an on-disk forensics tool needs about a 68040/68060 kernel relinked from the stock 2.1c binary (4 KiB pages) ✅:
- Every write larger than 4 KiB reaches the driver as 2 KiB requests. The buffer cache breaks
the buffer up: the first piece runs to the page edge, then
min(remaining, 0x800)— a 2 KiB constant left unconverted in the stock-derived code. A page-aligned 8 KiB UFS block therefore arrives as four 2 KiB requests at consecutive LBAs; raw (physio) I/O is cut into 2 KiB pieces as well; a request of 4 096 bytes or less passes whole. - A UFS inode block is written twice per
sync, about 70 ms apart — even for a singletouch f; sync. The second flush rewrites all four pieces (in the captures, three of them byte-identical to the first flush and one carrying updated inode values). A transient fault on the first write of the pair is healed by the second; only a fault on a block's last write persists on disk. - The disk layer's error path never misplaces data. With a driver refusing one 2 KiB piece of a
four-piece inode-block write, the refused piece reached no LBA, the rest of that pass was not
issued, and the next flush rewrote all four pieces at their own LBAs (the write record replayed
exactly). The retry in
dd.crebuilds address and command from the same buffer header. - Forensics note: a shifted or duplicated window of inodes carries newer time stamps than its original, because the copies are re-stamped on later use. A detector that compares inode copies must mask the dinode time fields, or it misses exactly the cases it is looking for.
71. A boot node's fssm_Device length counts its NUL — compare it as a C string ✅¶
expansion.library MakeDosNode() (LVO −144) stores the exec device name as a BSTR and then adds 1
to its length byte, so fssm_Device for foo.device has length 11 with the NUL counted; the DOS name
dn_Name is not adjusted ✅. The sequence is the same in all 52 checked Kickstart images from 2.0
(r36.141) to 3.2.3 (47.115) ✅; 1.x was not checked 🟡. The strap itself opens the device from
BADDR(fssm_Device) + 1 as a C string and never reads the length ✅ (3.2.3; other releases 🟡). A
loader or mount tool that walks eb_MountList and matches fssm_Device against a driver's
ln_Name over the counted length reads one byte past that name's NUL, so it must stop at the first
NUL instead. Layout, offsets and ROM evidence: boot process.
83. Kickstart leaves the CIA-A timer running ✅¶
Kickstart's timer.device leaves a CIA-A timer running when Amix takes the machine, and no Amix code
stops it ✅. Under Amix it raises an interrupt about every 92 ms that nothing handles, until some driver
reprograms the timer ✅. A driver that uses that timer (the Paula audio driver does) must stop and
reprogram it at first open; one that refuses to open because the timer "is already running, so someone
owns it" refuses every time ✅ (both kernel lines, emulation). Chained level-2 handlers cannot see the
CIA-A interrupt-control bits for this timer 🟡.
84. The stock /dev/audio open waits for the sound chip with interrupts blocked and no timeout ✅¶
The stock audio driver's open starts the sound chip and then spins at level 4, with no timeout,
for an interrupt from it ✅ (disassembly; Commodore's own comment there says a timeout should be added).
On a machine where that interrupt never comes — an emulator with its sound output off, or a faulty
chip — the first /dev/audio open freezes the box: network, console and all ✅ (emulation). On real
hardware the chip answers 🟡. Give emulators sound output when you test audio.
85. The 1992 console programs only OCS/ECS display registers ✅¶
The Amix console sets up the display through the OCS/ECS registers only ✅. Whatever ran before the
kernel and left AGA state behind — fetch mode in FMODE, BPLCON3/BPLCON4, a copper list — leaves
the Amix console garbled on an AGA machine (an A4000 with a boot loader that opened an Intuition
screen, for one) ✅ (emulation; A4091 + 68060 row). A boot loader must restore a null copper list and
those registers' power-on values before it starts the kernel. Related: §61.
Emulation & bench quirks¶
(Two more emulator gotchas live under driver authoring, because that is where they bite: 16 — the machine hung ≠ your driver hung and 17 — instruction-batching and the SCSI bootstrap.)
25. A hand-written .uae selects the cycle-exact core, and Amix dies at PID 1 ✅¶
Omitting cycle_exact from a UAE-family config does not leave cycle-exactness neutral — it selects
the cycle-exact 68030 core, and an Amix guest does not survive it ✅. Amiberry's default_prefs()
initialises cpu_cycle_exact, cpu_memory_cycle_exact and blitter_cycle_exact all true, and
nothing in the plain config-load path lowers them; the only thing that does is a host-side
default_disable_cycle_exact in amiberry.conf, which itself defaults to false — so the same
.uae can select different CPU cores on two machines ✅.
The failure imitates a guest/driver/memory-map bug perfectly. The kernel boots, configures devices and mounts root, and then PID 1 dies:
Pin cycle_exact=false — it is the one key that clears all three flags (blitter_cycle_exact=false
alone leaves the cycle-exact CPU enabled and fixes nothing) ✅. It also matters for a knob you thought
you had: fixup_prefs() forces cpu_compatible = true whenever memory-cycle-exactness is set, so
cpu_compatible=false silently does not take while the trap is armed ✅.
There is a pre-boot host-log tell: with sound disabled, the host log prints Cycle-exact mode
requires at least Disabled but emulated sound setting. if and only if the cycle-exact core
survived config load — grep for it before blaming the guest ✅. Why the cycle-exact core kills Amix
has not been root-caused; that it does is measured. Full mechanism on
emulation fidelity;
config keys on Amix on Amiberry.
39. A bare trailing & sent to the box's transport silently never runs ✅¶
In the SVR4 Bourne shell, &; is a syntax error ✅. So a command line that ends in a bare,
unparenthesised & and then has ; <something> appended — as any tool does that tacks a completion
marker such as ; echo DONE_$? onto each command it sends — fails to parse, and the job meant to
run in the background never starts at all ✅. Nothing on the line runs, so the marker never comes
back either.
The fix is to parenthesise the background launch so the line ends in ), which composes fine
with an appended ; …: (nohup sh job.sh > /job.out 2>&1 &); echo DONE_$? ✅. A & that is not
last on the line, such as cmd & echo launched-$!, is not affected. tools/host-net/amixsh.py in
this repository sends its marker on a line of its own, so a trailing & is safe there ✅.
Keep the nohup. A background job without it ran while its telnet login stayed open, but did not
finish once the login ended; with nohup it finished ✅ (measured on a 68030 guest). A hang-up at
logout is the likely cause 🟡, not traced.
99. A telnet line over the 256-byte canonical limit runs nothing and wedges the login ✅/🟡¶
The stock headers set MAX_CANON, the most bytes in a line for canonical processing, to 256 ✅. Over
telnet on a 68030 box, a 229-byte line answered normally, while a 269-byte line answered nothing,
and neither did any later command in that login until the client reconnected ✅ (measured). An
earlier bench run's 260-byte line (244 bytes of command plus a 16-byte marker) also ran nothing and
answered nothing ✅. A 237-byte line answered in a later run on a 68030 guest ✅.
There is little to notice. The measured failure had no truncated echo and no error, only silence ✅.
An earlier run did get ^G bells back, so bells are one form of the failure, and their absence
proves nothing ✅.
Three things were not measured:
- The exact threshold. The measurements place it between 237 and 260 bytes, and 256 comes from the header 🟡.
- The cause. The likely explanation is that the tty discards the overrun and the newline, so the line never ends and later input has no room 🟡. It has not been traced in the kernel.
- Other paths. A console or serial line and other kernels have not been run 🔴.
Rule: measure a command on the host before sending it, and count whatever the tool adds to the
line. A tool that appends ; echo DONE_$? (16 bytes) and a CRLF has 237 bytes left for the command,
keeping one byte in hand (256 − 16 − 2 − 1) 🟡. Split a longer line into several commands, which can
share one login. After a wedge, reconnect. tools/host-net/amixsh.py in this repository refuses a
line over 237 bytes before it logs in.
73. Amiberry before upstream 845af02: MOVES across a page boundary corrupts Amix data 🟡¶
Reported by the amix-x11-aga project, and not reproduced
here 🟡. In Amiberry builds before upstream commit
845af02 (2026-10-06, a merge of WinUAE
updates), the 68030 MMU's "more compatible" mode — cpu_compatible=true, which
cpu_memory_cycle_exact=true also forces — did not split a misaligned MOVES access across a page
boundary. Amix copies between kernel and user space with MOVES, so files, the kernel heap and X
traffic were corrupted. The reporter caught it by compiling the same C file three times and comparing
the outputs.
Rule: use an Amiberry build that contains 845af02; with an older build, set
cpu_compatible=false and leave cpu_memory_cycle_exact off, since it forces the mode back on.
Time & Y2K quirks¶
7. Y2K and the battery clock: a setclk bug, not a kernel cap ✅/🟡¶
Amix has one real millennium problem, and it sits in the tool that reads the battery clock ✅:
- The
setclkutility uses a%02dformat for the year, so it mishandles dates ≥ 2000. - ~~The kernel caps the date at 1999~~ — corrected 2026-09-29 ✅: the kernel has NO year cap:
date 092912002026sets 2026 on a stock 2.1c box. What fails is the tooling: the 2-digit form readsyyas19yy(clamped at the epoch), the 3-digit form that the stocksetclkproduces (%02dof years-since-1900 → "126") givesdate: bad conversion, andamixadm's finishing-touches step only accepts ≤ 1999.
What a stock box does at boot ✅. /etc/sysinit runs /usr/amiga/bin/setclk at every boot, and the stock setclk hands the battery-clock time to date in that 3-digit form. date refuses it (date: bad conversion), nothing else in the rc scripts reads the RTC, and the system clock stays where the kernel started it — on the bench images that was 1977. So a stock box comes up with the wrong date even when its battery clock is correct. Measured on a real A4000T (RP5C01 at $DC0000 ticking, holding the set time through a power-off) and on the bench with the stock image, 2026-09-28/29.
Writing the clock is wrong too 🟡 (read in the stock source, usr/amiga/src/cmd/setclk/setclk.c; the stock binary not run). For setclk -s the year is formatted %02d from tm_year, so 2026 (126) gives the 11 characters mmddHHMM126 plus the terminating NUL in an 11-byte buffer, one byte past its end. Reading it back takes the year as 12, and the year < 70 rule makes it 2012: the battery clock is set to 2012. The corrected setclk the amigaux 1.1 installer lays down uses 12-byte buffers and a 3-digit year; its -s writes whole minutes (seconds 0) in /etc/clkzone's zone ✅. amix-base's time sync refuses to run the stock binary and calls setclk -s at the start of a minute.
The fix is a corrected binary, not a kernel change ✅. The patch disk upgrades to 2.1p2a / kernel 2.1c, but its cpio carries no setclk (checked 2026-09-29: 103 members, none is setclk 🟡→✅ corrected). The corrected setclk is a separate community binary from the 2007 amiga.org thread (Piru's %02d-year/buffer fix, a_petri's 4-digit-year follow-up), published on the AmigaUnix wiki's y2k-dst page (binary sha256 6022274f…, with both diffs against setclk.c; no licence statement); it can be rebuilt reproducibly from the installed images' /usr/amiga/src/cmd/setclk plus those diffs. Because sysinit already calls setclk, replacing the binary is enough. With it, the console shows a setclk: line and the clock is right from the first boot ✅.
Three details that decide whether the time is right ✅ (measured on the metal and the bench, 2026-09-29):
/etc/clkzone.setclkreads the RTC value in the zone named in/etc/clkzone(:<zone>, the tzfile form; default:GMT). AmigaOS keeps the battery clock in local time, so set/etc/clkzoneto the box's zone or the clock runs hours off (2–3 h in Finland).- Seconds. The stock route goes through SVR4.0
date, which takes no seconds, so even a working Y2K-fixedsetclkthat callsdatestarts the box up to 59 s slow. Asetclkthat sets the time directly (stime) is exact to the second. - The zone files are 1991 data.
/usr/lib/locale/TZholds 1991 tzfiles with 1991 rules: the European zones end summer time on the last Sunday of September (EU since 1996: October) and the US zones use the pre-2007 rule, so in 2026 a Finnish box showed EET while the country was on EEST — the clock was right in UTC and wrong by one hour as local time. Regenerating the compiled zones from current tzdata, in the 1991 file layout the Amix libc reads, fixes it; no-DST zones are unaffected.
The RTC's year register itself holds the AmigaOS battery-clock encoding (years since 1900, so the tens digit runs past 9); the kernel's /dev/clock driver encodes and decodes it, and a clock set from AmigaOS and one set from Amix agree ✅. See the patch mechanism in the patch ADF anatomy and the version timeline in versions; for why the clock must never be set forward on a box with password ages, see §20.
20. Password expired is the future-lastchg check, never set the RTC forward ✅¶
An SVR4 box whose RTC battery is dead boots believing it is January 1978 (~day 2923 of the Unix
epoch). If a previous session ever "fixed" the clock by setting it forward, that stamped
/etc/shadow's lastchg field far in the future (e.g. 8117 = 1992). On the next cold boot — back at
1978 — lastchg is now in the future relative to "today", and SVR4 login treats a future
lastchg as expired, forcing a password change at every login ✅. This is the future-dated
check, not the max-age rule, so it recurs on every 1978 boot and stalls amixrun.py-style
automation at the password dialog. Root-caused on a real A4000 + Z3660 ✅.
Reliable fix at the prompt: set the new password to the same value (SVR4 accepted a 4-char root
password here); with the clock at 1978 the new lastchg (~2923) is not future-dated, so it does not
recur. Do NOT "fix" it by setting the RTC to now — that re-arms the trap for the next cold boot ✅.
The deep fix for a golden image is to set lastchg to a pre-1978 value offline. (This is a
distinct problem from the Y2K/setclk bug above —
here the danger is setting the clock forward, not past 1999.)
57. The stock kernel finds the A3000's internal SCSI only if the LOADER put the kernel above 0x07000000 ✅¶
The A3000's WD33C93 is not an AutoConfig board. Stock 2.1c autocon() registers it through a guess (.text 0x19222): if (index==0 && end > (char *)0x07000000 && pc==0x0202F003) *bp = 0xdd0000 — where end is the kernel's own end symbol, i.e. where boot2 placed the kernel. With the classic 16 MB motherboard bank at 0x07000000 the kernel loads there and the guess fires (card 0, root on c6d0s1). With 64 MB at 0x04000000 (or any layout that loads the kernel below the line: accelerator fast RAM elsewhere) the guess is skipped, no card 0 exists, sdopen answers ENXIO and the boot ends in s5mountroot VOP_OPEN error 6 / vfs_mountroot errno 89. The seven 1-byte reads at 0xfffffffa/c/e that follow are the panic's own backtrace decoding its terminal frame, not a memory probe. Measured on the bench 2026-09-29 and symbolised on the stock kernel. The "16 MB ceiling" folklore is this guess's only working placement. Fixes: the A4091 project's support.c.patch (chipset gate + a real WD33C93 probe) on the 030 line, and an equivalent probe in the 040/060 port. See §41 for the A4000T's $DD0000 case.
89. The TZ string is parsed in place — putenv() a literal and the next time call crashes ✅¶
The stock shared libc's timezone parser (the static getzname, reached from tzset() and
localtime()) writes into the TZ environment string: it stores a NUL to cut the zone name
and restores the byte afterwards ✅ (the crash's PC in getzname at a clrb (%a2), with the fault
address and %a2 at the 0 of "TZ=UTC0"). Harmless for a TZ the shell set, which lives in
writable memory; fatal for a program that putenv()s a string literal, which modern compilers
(GCC 14) place in read-only .rodata: the next time conversion dies with SIGSEGV ✅. GNU coreutils
5.2.1's date -u (putenv("TZ=UTC0")) crashed on every run this way, while env TZ=UTC0 date and
-d parsing with UTC/GMT/+0000 zones worked ✅ (emulated stock 2.1c, 2026-10-29). Fix: putenv() a
writable buffer (static char tz[] = "TZ=UTC0";). Whether period-compiler builds escaped it through
writable literals is unchecked 🟡. Related: the colon-less TZ crash in §21.
X11 & desktop quirks¶
9. The X keymap is wrong 🟡¶
Under X11 the default keymap is mis-mapped 🟡:
- y and z are swapped.
/is produced by SHIFT-8.
There are other X annoyances in the same family — xload crashes and the X11R4 server leaks memory 🟡. The modern RTG path (Xrtg / VA2000) sidesteps the old server but not the keymap mapping itself. Full X11 notes (mono tvtwm, A2410 color via TIGA, OpenLook font-path breakage on the R5 upgrade) are in X11 & desktop.
Userland & packaging quirks¶
10. amixpkg is install-media-only, and its "broken" reputation is partly a data defect ✅/🟡¶
The amixpkg wrapper is not present on the installed system at all ✅ — it lives only on the
root.adf install media, where it drives the whole distribution install (amixpkg -i -m -d -r /mnt -y
standard) ✅ via the same /var/sadm machinery as pkgadd. So its long-standing "widely reported
broken" reputation 🟡 is not about a flaky command you run day-to-day (you can't — it isn't
installed); the raw SVR4 tools that are installed (pkgadd/pkgrm/pkgmk/pkgtrans/pkginfo)
work. A large part of the "package tools are broken" lore is instead the concrete, reproducible defect
in the next item. A separate build-side gotcha: pkgproto omits symlinks 🟡. Full internals on
package management; install flow in the install walkthrough.
18. The stock package database is corrupt out of the box ✅¶
On a clean Amix 2.1c image, pkgrm <anything> and pkginfo -l <anything> abort immediately ✅
with ERROR: bad read of contents file … problem=unknown ftype. The cause is a single malformed
record in the /var/sadm/install/contents package database: an X11R5 font file named with a space
(…/MacFS/TrueType Fonts). Because contents(4) is whitespace-delimited with no quoting, the parser
ends the pathname at the space and reads the next word as the file-type field — which is invalid — so
every tool that fully parses the database aborts ✅. Deleting that one line makes both pkgrm and
pkginfo -l work again (and the DB is world-writable, so the fix needs no special privilege beyond
root's normal access) ✅. This is a strong candidate for much of the "Amix package tools are broken"
reputation (quirk 10). Full diagnosis and the fix on
package management.
12. /bin/sh is pre-POSIX ✅¶
Amix's /bin/sh predates POSIX ✅. The two bites that catch driver authors and scripters:
- No command substitution with
$(...)— you must use backticks`...`. - No
grep -q— redirect to/dev/nulland test$?instead.
This was discovered the hard way porting the VA2000 driver: install scripts written with modern shell syntax silently misbehave ✅. The default interactive shell is ksh (with sh/csh/tcsh also present), but build and install scripts that run under /sbin/sh or /bin/sh must stay pre-POSIX ✅. See the driver model and writing a char driver for the driver-build implications, including the companion rule to rm -f stale objects before relinking the kernel.
Eight more that specifically bite installer/automation scripts ✅ (bench-proven on Amix 2.1c; the ${1:?word} and EXIT-trap ones on the amigaux 1.1 images; the false one on a 68030 guest):
while read … done < FILEruns in a SUBSHELL — a counter incremented inside the loop reverts to its pre-loop value atdone. Write per-iteration state to a temp file and read it back after the loop.- **An empty
set -- \cmd`is a no-op on$@** — if the command prints nothing,$#keeps its *previous* value instead of going to 0. Guard with[ -s file ]` before counting. ${1:?word}ends at the first blank in the word —D=${1:?usage: box.sh dir [grep]}is taken as the assignmentD=${1:?usage:followed by the commandbox.sh, so the script fails withbox.sh: not foundandDstays empty (the 1992/sbin/sh). Keep the word free of blanks, or write[ -n "$1" ] || { echo usage >&2; exit 2; }; D=$1.- An
EXITtrap replaces the statusexitwas given, and$?reads 0 inside it — withtrap "true" 0,exit 3ends with status 0; withtrap "false" 0, with 255;trap "echo in=\$?" 0printsin=0, so saving$?inside the trap does not help (68030 and 68060 kernels alike). A script that cleans up in anEXITtrap reports success for every failingexit; dash and bash keep the status, so host tests miss it. Use noEXITtrap: clean up in a function that ends withexit $1, call it in place of eachexit, and trap only the signals, e.g.finish() { rm -rf "${LOCK:?}"; exit $1; }; trap 'finish 1' 1 2 15…finish 0. falseexits 255, not 1 —false; echo $?prints255. A script or test that compares against 1 gets it wrong; test for non-zero instead.mkdir -pis not idempotent — it exits rc=2 "File exists" on an already-present leaf directory, not 0. Guard with[ -d dir ] || mkdir -p dir.wc -cLEFT-PADS its count with spaces, so a rawwc -cin a numeric test fails; strip the padding (e.g. viaread) first.- The console tty truncates canonical input at ~256 characters — long one-liners are silently cut. Push a script file and run it rather than typing a long command.
And the toolbox is thin: an install miniroot ships no basename/sed/touch/date/wc. The portable substitutes are expr (for basename-style path/field work), : > file (for touch), and set …/*; echo $# (to count matches).
88. Stock sed drops a final line that has no newline ✅¶
Amix's stock sed silently discards a last input line that is not newline-terminated ✅: echo
abc | tr '\012' ' ' produces the four bytes a b c ␠ (no newline), and piping that into
sed 's/ $//' prints nothing — no output, no error, exit 0 (emulated stock 2.1c, 2026-10-29).
Terminated lines and ;-separated command lists behave normally ✅. Shell scripts that build a string
with tr '\012' ' ' and trim it with sed lose the whole value; upstream bzip2 1.0.8's bzgrep
printed empty file: prefixes on multi-file searches this way ✅. Workaround: make sure sed's input
ends with a newline, e.g. { …; echo; } | sed …. See also §12 and
§81.
90. Shrinking a big file with ftruncate can panic the kernel — both lines ✅¶
ftruncate() (fcntl(F_FREESP)) that shrinks a file to an EOF that is not block-aligned,
inside the block that held the old EOF, can panic the box with a kernel bus error ✅. It happens on
stock 2.1c (68030, 2 KiB pages) and on the 040/060 kernel line (4 KiB pages), with different cases:
- 030:
PANIC: KERNEL FAULT psw=0x2004, pc=<load base>+0x32C, fmt=0xA, vector=0x2 (Bus Error)(an emulated 68030 with 64 MB, ufs root with 8 KiB blocks, one case per boot, 2026-10-30). - 040/060:
PANIC: KERNEL FAULT psw=0x2004, pc=<load base>+0x32A, fmt=0x4, vector=0x2 (Bus Error)(an emulated A4000 with a 68060, ufs with 8 KiB blocks and 1 KiB fragments, one case per boot, 2026-10-30).
Writing N bytes and truncating to M:
| N → M | 030 | 040/060 |
|---|---|---|
480017 → 480000 (also after close, sync and reopen on the 030) |
panics ✅ | survives ✅ |
| 480017 → 477000 | panics ✅ | survives ✅ |
| 477017 → 477000 | — | panics ✅ |
| 110017 → 110000 | panics ✅ | panics ✅ |
| 112017 → 110000 | — | survives ✅ |
| 90017 → 90000, 1000 → 500 (within the direct blocks) | survives ✅ | survives ✅ |
| 110017 → 106496 (block-aligned) | survives ✅ | survives ✅ |
Also surviving on the 030 ✅: 20017 → 20000, 20000 → 15000, 200000 → 150000 and 480017 → 470000 (the EOF moves to an earlier block — survival only: these two leave stale data behind, §91), 200000 → 196608 and 480017 → 475136 (aligned), extending (20000 → 30000), and truncating to 0.
The rule that fits every case on both lines ✅: the shrink panics when M is unaligned, M and N
lie in the same 8 KiB block past the 12 direct blocks (N > 98304 bytes), and that block ends
beyond the page that holds the old EOF — roundup(N, PAGESIZE) < roundup(N, 8192). With 2 KiB pages
that is any old EOF outside the block's last quarter; with 4 KiB pages, only an old EOF in the
block's lower half. That is why 480017 → 480000 panics on the 030 (the EOF page ends at 481280,
short of 483328) and survives on the 040/060 line (the EOF page ends at 483328). The mechanism, read
from the kernels: for a block past the direct blocks ufs_itrunc asks to zero from M to the end of
the block, and pvn_vptrunc zeroes through segmap with kzero into a page wholly past the old
EOF ✅. Path, symbolised against the stock 2.1c kernel: fcntl(F_FREESP) → ufs_space →
ufs_freesp → ufs_itrunc → pvn_vptrunc → bzero, faulting on bzero's movel %a1,%a0@+ store
✅. In both kernels kzero and bzero are one routine (kernel text 0x302). SVR4's design calls
kzero there so that a page that cannot be read in does not panic the system, but stock Amix's
kzero has no fault catch ✅. The two PCs are the same store: the 060's format-4 frame reports the faulting
instruction, the 030's format-A frame the next one ✅. On the 040/060 line ufs_itrunc, ufs_freesp,
ufs_space and segmap_fault are the stock code, and pvn_vptrunc differs only in its page size ✅.
The 040/060 backtrace stops at the trap frame, so the chain above bzero there rests on the trigger
geometry 🟡. Why the fault on that page cannot be resolved (segmap_fault → ufs_getpage past
roundup(i_size, PAGESIZE)) is not proven 🟡.
Fixed on the 040/060 line — in emulation and on a real 68060 ✅: the port gives kzero the fault
catch SVR4 defines for it (ISSUE-107): it arms the stock per-process no-fault pad around the stock
bzero, returns −1 on an unresolvable fault, and accepts a landing only from its own bzero frame.
On a test kernel (an emulated A4000 with a 68LC060, two boots) nine shrink cases, including
110017 → 110000 and 477017 → 477000, all return 0 with the size right and [0, M) intact, and after
regrowing the file the bytes past M read zero; the box stays up. The same nine cases pass on metal
(an A4000T with a Z3660 running a real 68060, 128 MB, ufs root behind the onboard 53C710), with
three caught faults (one per former panic shape) and no foreign landing on the no-fault pad ✅; no
unfixed control was run on metal, so the 040/060 panic itself is measured in emulation only 🟡. The fault is taken at the first page
past the old EOF, and the zeroing runs upward, so everything readable has been zeroed by then ✅. The
fix uses 68000 instructions only and assembles byte-identically for the 68030.
Fixed on the 68030 line, in emulation ✅ (hardware pending 🟡): the project's 68030 kernel links
the same routine (the same object bytes) in place of the stock kzero, on by default (--no-kzero
rebuilds the previous kernel byte for byte). A test kernel survives the §90 shrinks and the
dd, cp and gtar --delete cases above with the data intact. It is not in a released 68030
kernel yet, and stock 2.1c still panics. Until your kernel carries it, use the workaround.
Workaround (proven ✅ on both lines): truncate to the block boundary at or below the new EOF —
aligned, so there is no partial tail to zero — then write the bytes from that boundary up to the new
EOF back. The rsync 3.1.3 port does this in do_ftruncate; its --inplace shrink 110011 → 110000
comes out byte-exact ✅, and the aligned cut 110017 → 106496 survives on the 040/060 line ✅.
Exposed programs (any program that shrinks a big file in place; measured on the emulated stock 2.1c 68030, one boot per panicking case, 2026-10-30):
- rsync
--inplace— panics unpatched ✅; the rsync 3.1.3 port carries the aligned cut. - coreutils
dd … seek=Nwithoutconv=notrunc—dd if=/dev/null of=<480017-byte file> bs=1000 seek=477panics ✅; an aligned seek (bs=8192 seek=50) survives ✅. - coreutils
cp --sparse=always— a sparse copy that ends in a hole writes one byte past the end and truncates back by one byte, a shrink inside the last block: a 110017-byte file ending in 3521 zero bytes panics ✅, a 53000-byte one survives ✅.mvacross filesystems andinstalluse the same copy code, not run 🟡. - GNU tar 1.14
--delete— with-b 1, deleting a small member from a 215040-byte archive (new EOF 214016, the same 8 KiB block) panics ✅, and survives when the new EOF happens to be aligned (214016 → 212992) ✅. With the default-b 20the archive shrinks by whole 10240-byte records, so the new EOF always lands in an earlier block, which is safe ✅. tar also truncates when it rewrites a--listed-incrementalsnapshot — the same path, not run 🟡. shredtruncates to 0 only, which is safe (read from the source) 🟡.
Porting rule: never call ftruncate() or F_FREESP to an unaligned shorter length on Amix. The
static libc archive defines ftruncate as a weak alias of _ftruncate (truncate.o), so a strong
ftruncate linked into the program replaces it: cut at the block boundary, then rewrite the tail.
It needs a readable descriptor (coreutils dd opens its output O_RDWR when seek= is given) ✅.
The project's coreutils and GNU tar packages carry such a replacement, and its cp writes the
final hole's NUL by seeking back rather than by truncating (upstream's own non-ftruncate branch) ✅.
91. A shrink to an earlier block, then a sparse regrow, reads the old bytes back — stock 2.1c and both lines ✅¶
Related to §90 but separate: no crash, wrong data, on disk. On a
ufs with 8 KiB blocks and 1 KiB fragments, write N bytes to a fresh file and shrink it with
fcntl(F_FREESP) (ftruncate) to an M in an earlier 8 KiB block than N, both past the 12 direct
blocks. The shrink returns 0, the size is M and [0, M) is intact. Now regrow the file across the
gap by writing one byte at N−1: [M, roundup(M, PAGESIZE)) reads zero as it should, but
[roundup(M, PAGESIZE), roundup(M, 8192)) holds the file's old bytes whenever that range is not
empty ✅. With the 040/060 line's 4 KiB pages that takes an M in the lower half of its block; with the
68030's 2 KiB pages, any M outside its block's last 2 KiB page. A block-aligned M, an M in its
block's last page, and a regrow that stays inside M's block are clean ✅. That is the warm case,
with the file's pages in the page cache. If the file has no cached pages when it is shrunk (after
an unmount and mount, say), all of [M, roundup(M, 8192)) keeps the old bytes, M's own page
included, for any unaligned M ✅ (16 of 16 runs, deterministic).
| N → M | 68030 (2 KiB pages) | 040/060 (4 KiB pages) |
|---|---|---|
| 200000 → 150000 | stale pages 151552 and 153600 (all old), zero from 155648 ✅ | stale [151552, 155647] ✅ |
| 480017 → 470000 | stale pages 471040 and 473088 ✅ | stale [471040, 475135] ✅ |
| 200000 → 148000 (M in the block's first page) | stale 149504–155647 ✅ (clean once on one disk 🟡) | — |
| 200000 → 151552 (page-aligned, not block-aligned) | — | stale [151552, 155647] ✅ |
| 200000 → 154000 (M in the block's last 2 KiB page) | clean ✅ | — |
| 200000 → 150000, regrow to 155001 (inside the block) | clean ✅ | — |
| 300017 → 250000, 1000000 → 900000 (M in the upper half of its block) | — | clean ✅ |
| block-aligned M (200000 → 147456 / 196608, 480017 → 475136) | clean ✅ | clean ✅ |
| within the direct blocks (1000 → 500, 20000 → 15000, 90017 → 90000) | clean ✅ | clean ✅ |
Measured in emulation, 2026-10-30: on the 2.1c bench image (stock userland; a 2.1c kernel with drivers added and the stock ufs and VM code; an emulated A3000 with 64 MB, ufs root), on the
project's 68030 kernel line (an emulated A4000 with the Z3660 in 68030 mode, its root and a freshly
made ufs), and on the 040/060 line (an emulated A4000 with a 68LC060). Each line ran two kernels,
with the stock kzero and with the fault-catching kzero of §90, and got identical results, so that
fix neither causes nor cures it. The project's 68030 line runs the stock ufs and VM code byte for
byte. The old bytes are on the disk ✅: sync, shrink, unmount and remount (which empties the
page cache), regrow — the same old bytes; regrow, sync, remount, then read — the same. It is not
fully deterministic 🟡: a sync and 40 s between the shrink and the regrow came out clean once on
one filesystem and stale on another. On the 040/060 line the old bytes also survive a reboot ✅: shrink, sync
twice, a clean shutdown and a fresh start, then the regrow reads exactly the same 4096 old bytes as
a same-boot control (all three stale shapes; the clean shapes stay clean). Real hardware is
untested 🔴.
Cause, read from the stock objects ✅ (the last step is inferred by elimination 🟡): past the direct
blocks ufs_itrunc keeps the last block whole and calls pvn_vptrunc(vp, M, fs_bsize − blkoff(M))
with the inode lock held, before it sets the size to M. pvn_vptrunc zeroes [M, block end) through
segmap with kzero — in the page cache only — releases the 8 KiB window with an asynchronous
put, then aborts every page whose offset is ≥ M (not rounded to a page). So the zeroed pages
past M's own page are discarded, and their zeros reach the disk only if the asynchronous put really
wrote them first, and the remount proof says they usually do not. It is not ufs_putpage's
locked-inode refusal: that early return fires only for the scheduler and pageout processes (pid 0
and 2), the usual pageout-deadlock guard ✅. Nor are the segmap-zeroed pages invisible to the
put's dirty scan: zeroed the same way and released synchronously, they reach the disk every time
✅. That leaves the asynchronous write losing the race with the abort of the pages past M, by
elimination, not observed directly 🟡. The cold case has its own cause ✅: pvn_vptrunc returns at
once when the vnode has no cached pages, so nothing at all is zeroed.
The code comes from a design with 8 KiB pages, where M's page covers the whole rest of the block and
is never aborted; Amix's 2 KiB (68030) and 4 KiB (040/060) pages break that assumption.
Fixed on the 68030 line, in emulation ✅ (hardware pending 🟡): the project's 68030 kernel wraps
ufs_itrunc's pvn_vptrunc call — when a shrink cuts into a kept block past the direct blocks, it
first zeroes [M, block end) through the page cache and writes it synchronously, warm or cold,
before the stock code runs. On a test kernel, 136 of 136 shrink-and-regrow runs came out clean
(warm, after a remount, and cold), the §90 shapes and the exposed programs' cases are clean too,
and fsck of the test filesystem is clean ✅. Stock 2.1c never will carry it.
Fixed on the 040/060 line, in emulation ✅ (hardware pending 🟡): the 68040/68060 port binds the
same routine (the same code) into its ufs module at build time, on by default (UFS_TAILZERO=0
turns it off and rebuilds the previous module byte for byte). On an emulated 68LC060, the 68030
line's probe set came back 68 of 68 clean (warm ×3, after a remount ×2, cold ×4), with the §90
shapes and cases passing too; a control kernel without the routine was stale on 56 of 68 runs, on
the predicted shapes ✅. The rule there is the 68030's with 4 KiB pages: warm,
[roundup(M, 4096), roundup(M, 8192)); cold, all of [M, roundup(M, 8192)).
Workaround ✅ (by construction), until the kernel carries the fix: after a shrink to an unaligned M past the direct blocks, never regrow the file across a gap — write real zeros through the gap instead, from M. Or shrink only to an 8 KiB-aligned M and write the tail back, which §90's aligned-cut workaround already does. (An M in its block's last page is clean only when the file is cached, so it is not a safe cut.)
92. A failed STREAMS open can leave a freed queue on the run list: stock 2.1c panics in queuerun ✅¶
The symptom. PANIC: KERNEL FAULT with vector 0x4 (Illegal Instruction) or 0xB (Line-F), the
pc in the kernel heap rather than text, and a backtrace of ureturn → queuerun ✅: the kernel
was running deferred STREAMS service work on the way back to user mode, and jumped through freed
memory. The pc differs from run to run (0x403C9600, 0x403C3F00, 0x403C0400 in three runs).
The cause, read from the stock objects and caught in the act ✅. When a module's or driver's open
fails, qattach undoes the attach with qdetach(q, 0, 0, cr). With clmode 0, the stock qdetach
takes a short path. It skips runqueues, the close routine, the removal from the run list and the
flushq calls, and goes straight to the backq fix-ups and freeq. If the failing open had already
enabled its own queue, the freed pair stays on the run list (qhead). The next queuerun calls
q_qinfo->qi_srvp through freed memory. On the stock kernel with instrumentation in freeq, two of
two runs caught exactly one such free each: a sockmod read queue, q_flag 0x33
(QENAB|QWANTR|QREADR|QUSE), at the head of the run list, freed from qattach → qdetach. With
observation-only instrumentation (no unlinking) the same kernel panicked with the same backtrace ✅.
kmem_free reuses the freed block's first word, q_qinfo, as its free-list link, so the jump goes
wherever that link's neighbouring word points. That is why the panic is probabilistic 🟡. (An earlier
reading here, a deferred put through a garbage qi_putp, is withdrawn: it is the service routine.)
Ruled out ✅: a bufcall or timeout re-enabling a dead queue. No callback ran on a freed queue,
and no bufcall or callout was pending at any free, over about 4.7 million frees. (sockmod drops
its bufcall/timeout ids, so that hole exists but did not fire.) Separately, the stock strclose
walks the run list for the write queue only, at the caller's priority 🟡 (read from the code, not
seen to fail).
What triggers it. It was found with the prngd 0.9.29 entropy daemon and a 10 MB swap. prngd's
gatherers exhaust the swap reservation (anoninfo 4777 of 5119 reserved, with 82% of memory
free) ✅. fork then fails EAGAIN, and prngd spins on pipe, failed fork, close and syslog
without serving its clients. Its prngd-ctl clients hang, and in that window a sockmod open fails ✅.
Killing prngd frees the reservation, so the exhaustion is userland plus a small swap, not a kernel
leak ✅. Under that load a loop of prngd-ctl get panicked twice and 100 × prngd-ctl read 32 | wc
once; 100 × get and 100 × get | tr survived ✅. Simpler AF_UNIX servers did not reproduce it,
which fits: the trigger is a failed open, not socket traffic as such. Any program that makes a
STREAMS open fail after the queue was enabled is exposed. A larger swap makes this route less likely
and does not remove it.
Fixed on the 68030 line, in emulation ✅ (hardware pending 🟡): the project's 68030 kernel sends
all five freeq calls (in qdetach, strclose and stropen) through a guard that first unlinks the
pair from the run and scan lists at spl7, then calls the stock freeq. On by default;
--no-freeq-guard rebuilds the previous kernel byte for byte. The stock kernel with the guard caught
the free once and survived, and the fixed test kernel passes its regression runs. Not in a released
68030 kernel yet; hardware is untried 🔴.
The 040/060 line has the same hole ✅, and the same guard 🟡. A byte comparison shows the 68040/68060
kernel's qattach, qdetach, freeq, queuerun, strclose, stropen and runqueues are the stock
2.1c bytes at the stock addresses, with the same five freeq call sites ✅. The 68030 line's guard
compiles unchanged for the 040 and is bound by default in the 040/060 line's cut candidate. In
emulation (an A4000 with a 68060, 16 MB), seven guarded runs of the trigger ran clean with the guard
live ✅. The freed queue never occurred in them, though: the guard took nothing off the run list, and
prngd's fork-failure spin did not start on that machine. Four unguarded control runs did not fault
either. So the guard's save is shown on the 68030 line only; on the 040/060 line it is untested 🔴, as
is hardware 🔴.
The package's own test, on three kernels 🟡 (emulation, one boot each). The runs were pkgadd,
the package's smoke test, then 150 × prngd-ctl get, 100 × prngd-ctl read 32 | wc and 100 ×
prngd-ctl readb 16, then pkgrm. On the 2.1c bench image (64 MB) the smoke test panicked before
any load ran: vector 0x4, pc 0x403C4F00 in the heap. It came after the test's deliberate no-socket
check, about when the daemon starts on its socket; the exact step was not logged. The booted kernel
has no symbol table, so the backtrace cannot be resolved: this panic is consistent with quirk 92, not
confirmed as it. A 2.1c kernel with only the
freeq guard added (16 MB) passed the smoke test and the three loads. But the loads ran while
prngd was still gathering: get reported 0 bits and no read 32 returned data. The project's
68030 test kernel, on an emulated 68030 with FPU, passed both with entropy served (99 of 100 reads).
These runs did not reproduce the swap-reservation exhaustion. A single clean boot says little about
a probabilistic panic, so these rows add to the regression runs above and do not replace them.
93. netstat -rn fails with read error on routehash when /stand/unix is not the booted kernel ✅/🟡¶
On the project's 2.1c bench image, netstat -rn prints read error on routehash and exits 1 ✅
(four times over three boots). On the same box netstat -in exits 0 but prints only its header line,
with no interface rows, while the box is being driven over aen0 ✅. That image boots a 2.1c
kernel with cdfs and the A3091/A4091 drivers linked in (its text is about 72 KB larger than the stock
link). Its /stand/unix is the retail 2.1 kernel ✅ (read offline from the image file): one inode
with /stand/2.1_unix, byte-identical to kernels/2.1/unix in
amigaux/amix-system (1,475,108 bytes, sha256
97ae253a…, versions), and /stand holds no other kernel. The booted
kernel is a different, larger link: its load image is 1,252,347 bytes, against 1,171,216 bytes for
/stand/unix converted by elf2brel, and it carries no symbol table. netstat looks its symbols up in
/stand/unix and reads them through /dev/kmem (§80). If the two
kernels' layouts differ, it reads the wrong addresses. More of that image's netstat is wrong ✅:
netstat -s reports 0 IP packets received on a box being driven over the network, while ifconfig
aen0 and ifconfig lo0 show both interfaces up with their addresses. Naming the namelist
(netstat -rn /stand/unix, and with /dev/kmem) changes nothing ✅, as expected, since that is the
default. That file is an ELF relocatable (ET_REL, entry 0) ✅, so its symbol values are offsets,
not the addresses the kernel was loaded at
(reverse engineering).
So on that image netstat, ifconfig, crash and any /dev/kmem lookup by name get retail 2.1
symbols, neither the running kernel's nor bound to a load address ✅. That this is what breaks the
three netstat views is the leading explanation 🟡; a run with the booted kernel's own bound
symbols is the next test, and the image has none on disk. Boxes whose /stand/unix is the booted kernel have run -in and -rn
(§26).
Practical rule: check that /stand/unix is the kernel that booted before trusting netstat,
ifconfig or crash. A script that must know its routes should set them with route add and keep
its own record of them (networking).
The same image's userland is stock 2.1c. Measurements of userland behaviour on it (man, the
pkg* pages, libm) are stock facts. Kernel behaviour seen on it is the stock code only where that
code is the stock object, which the kernel work checks for each case.
21. A colon-less zoneinfo TZ crashes every tzset() caller ✅¶
A /etc/TIMEZONE that sets TZ=US/Eastern without a leading colon makes every SVR4 program that calls localtime()/tzset() die with User BUS ERROR at 474D5400 ✅ — and 0x474D5400 is ASCII "GMT\0", the GMT-fallback timezone abbreviation being dereferenced as a pointer after the POSIX-rule parser chokes on the / in the zone name. The fix is the colon form, TZ=:US/Eastern, which tells SVR4 to load /usr/lib/locale/TZ/US/Eastern as a tzfile path instead of parsing it as a rule (a valid POSIX rule string such as EST5EDT or GMT0 also works — for a zone file always use the colon form, e.g. TZ=:EET). The crash set is exactly the localtime callers — date, who -r, cron, sac, ls -l — while console login survives (it needs getpwnam, not localtime), which is the tell that distinguishes this from the passwd-side defect. Second-order symptom: a crashed who -r empties the positional parameters in the /etc/rc2.d/S* scripts that begin set \who -r`, so those degenerate intotest: unknown operator S` at boot. Note this is a constant fault address at cold boot — do not confuse it with the emulator's variable-address MMU-frame bus errors (emulation fidelity).
22. Reused writable test media accumulates state that masks bugs ✅¶
The Amix fact behind this item: a freshly built miniroot ships no /etc/mnttab, and its bare mount(2) writes none, yet pkgadd needs the file — without it the install stops with pkgadd: ERROR: unable to open mount table (/etc/mnttab) ✅. An installer running from the miniroot must therefore seed the file with a / line before it calls pkgadd (echo "/dev/root / s5 rw 0" > /etc/mnttab). A reused root floppy that still carries an /etc/mnttab from an earlier mount hides the missing seed. Why the / line matters: package management.
28. The partitioning tools' units are 512-byte blocks, and rdb won't tell you its own grammar ✅¶
Three conventions of the miniroot's partitioning tools, each of which bites a scripted install, all bench-proven ✅ (first-party, 2026-07-31):
/etc/rdb -crequires-d disksize— in 512-byte blocks — and-dis absent from the usage text the tool prints when it refuses. The working grammar cannot be learned from the tool itself; the authority is the stock install script (/etc/profileon the root floppy)./etc/rdb -ptakes a 1-based partition number, never a device path — the argument isatoi()-ed, so a path decodes as 0 and every flag/type stamp is refused. Partitions are appended in slice order, so partition N is slice N.mkfs_s5's size operand is also 512-byte blocks — it converts to its 1 KB logical blocks itself; hand it a pre-converted count and it builds a filesystem over half the slice, silently.
Full grammar, units, and the read-back proofs on filesystems & disks; the stock script's own command lines on the root-floppy anatomy.
62. On-box mkfs fails on a root slice above ~603 MB, and a raw read past it sleeps forever ✅¶
Stock sddevsize() (amiga/alien/sdpart.c) answers a partition's real length only once the
partition table has been read; for any other slice it returns a fixed "large number", 1 234 567
blocks (602.8 MiB) ✅. Stock dd.c clamps every raw read and write to that size and reports it as
the device size. Two failures follow, on 68030 and 68060 kernels alike ✅ (bench, 2026-10-02):
mkfs_ufscannot author a root slice above 1 234 567 blocks. Its first write probes the slice's last sector, which lies past the default, and fails:write error: D / wtfs: No such device or address, rc 32, nothing written (fsckthen reportsBAD SUPER BLOCK). The RDB itself is written correctly. Bracketed exactly: a root of 1 234 560 blocks authors, 1 234 624 does not; 600 MB disks author, 640/900/992/1024 MB disks fail. Host-side minted images are unaffected.- A raw read that starts past the default never completes. On a whole-disk node (
c?d0s0, never partitioned, so always at the default) the remaining count goes negative and the process sleeps forever: block 1 100 000 reads, 1 300 000 hangs.
Fix (proven on the bench, 2026-10-02) ✅: raise the default to 0x3FFFFF blocks (the largest whose
byte offset still fits a signed 32-bit value) and make dd.c refuse an offset past the size instead
of computing a negative remainder. With it, 600/640/900/992/1024 MB roots all author, and the
whole-disk read past the end returns I/O error instead of sleeping. Until a kernel carries the fix,
keep an on-box-authored root at or below 1 234 567 blocks.
76. Stock diff has no unified format; the stock patch reads one ✅¶
/usr/bin/diff -u a b answers diff: invalid option -u — the 1992 diff has context diffs (-c)
only ✅. The stock public package's /usr/public/bin/patch (Larry Wall's, patch level 12u4) applies a
unified diff correctly: a four-hunk diff of a 2000-line file reproduced the target byte for byte ✅
(both on an emulated stock 2.1c). To produce unified diffs on the box, install a GNU diffutils.
79. bcheckrc always reboots after repairing the root ✅¶
The stock /etc/bcheckrc runs fsck -y on a dirty root and, whenever fsck repaired anything,
always reboots (*** REBOOTING SYSTEM ***) ✅ (stock script, and emulation). A root that keeps
coming up dirty therefore reboots in a loop, and anything that counts boots — a "boot the new kernel
once, fall back if it does not reach multi-user" scheme — sees each repair as a failed boot unless it
is told otherwise.
81. The 2.1 shell's read keeps its buffer across exec <file ✅¶
On the Amix 2.1 (1991) Bourne shell, read keeps its own input buffer, and a later exec <file does
not reset it: a second exec <file followed by read continues where the first pass stopped instead
of at the top of the file ✅. IFS and set -f make no difference ✅. while read …; done < file and
read line < file behave correctly ✅. A script that reads a file twice through exec < and rewrites it
from what it read can empty it — inittab included. Separately, a while read loop strips trailing
blanks from each line ✅ (both on the real 2.1 sh). Rule: one redirection per pass. See also
§12.
94. Four 2.1 /bin/sh facts for scripts that run background jobs ✅¶
Measured on the stock 2.1c userland (emulated A3000, 2026-10-30):
wait <pid>loses the status of a child that has already ended ✅.sh -c 'exit 3' & p=$!; wait $p; echo $?prints 0, at top level, in a subshell and in a function. A child still running whenwaitis called reports its status correctly (4 forsleep 2; exit 4). Don't takewait's status as the job's unless the job is known to be still running.- Two long-lived jobs appending to one file with
>>overwrite each other's bytes ✅. One job'sslow-startedline was overwritten by the other's output. Short-livedecho … >> logper line did not collide in the same runs. The likely mechanism is that>>opens withoutO_APPEND🟡. Rule: give each job its own output file. - A shell waiting for a foreground child does not die of
SIGTERMuntil that child ends ✅.killofsh -c "…; sleep 20; …"left it running, andkill -9ended it at once (status 137); a plain binary such assleepdies ofSIGTERMat once (status 143). To stop a shell job on time, followTERMwithKILLafter a grace period. sh -c 'cmd' a b csets$0=a,$1=b,$2=c✅, the POSIX convention.
95. The A2065 aen driver's DLPI traps ✅¶
For a program that talks DLPI to aen0 itself, such as a DHCP client (measured on the 2.1c bench
image; the stock source agrees on the first, second and fourth) ✅:
DL_ATTACH_REQgets no answer at all, and neither does an unknown primitive. Attach only on a Style 2 provider;aenis Style 1.- An open can return a slot an earlier process left bound: state 3, the old SAP, and 53–58 frames already queued. Check the state and drain before trusting what you read.
- Every received frame is 14 bytes longer than its IP datagram. Use the IP and UDP lengths.
- A
DL_UNITDATA_REQwhose data part is under 60 bytes fails withERANGE(errno 34): the driver's minimum counts the 14-byte header. Pad an ARP or other short frame to 60 bytes.
And from the IP side ✅: with nothing bound to UDP port 68, a unicast DHCP ACK draws an ICMP port
unreachable, and a send to 255.255.255.255 leaves as the subnet's directed broadcast. Details in
networking.
86. Swap is not optional, and its node must exist ✅¶
With no swap device added, the stock kernel's anonymous-memory reservation refuses every request and
the first fork panics ✅. With the default swap name configured and the node missing from /dev,
the stock swapconf panics at boot (swapconf lookupname failed - error 2) ✅. A medium that roots on a
RAM disk and has no swap slice points swap at /dev/fakeswap — block major 19, minor 0, the stock
dummy disk, on which every I/O fails and nothing is paged — and the installer adds a real slice later
with swap -a ✅ (emulation, both kernel lines).
96. The stock tar crashes on a ustar or GNU archive whose owner or group the box lacks ✅/🟡¶
The 2.1 tar is /usr/sbin/tar (45180 bytes, dated Feb 20 1992, SHA-256 c5a04638…); there is no
/usr/bin/tar ✅. When it restores ownership from a header with the ustar magic (POSIX ustar or GNU
format), it looks up the header's owner and group names with getpwnam and getgrnam. If a name
does not exist on the box, it prints
tar: problem reading passwd entry
tar: <file>: owner not changed
tar: problem reading group entry
tar: <file>: group not changed
and then still reads the uid and gid through the NULL result to pass them to the ownership call. The
process dies with SIGSEGV (exit 139) after laying the first file ✅ (a core and the disassembly of
the binary). An empty name counts as missing, and that is what GNU tar's --numeric-owner writes ✅.
The lookups are skipped when ownership is not restored (the o modifier, or a non-root user) and for
v7 headers, which carry no names ✅.
Measured on a 68030 module kernel in emulation (A4000, 64 MB), archives from host GNU tar ✅:
| Archive | tar xf as root |
|---|---|
| v7 | extracts |
ustar or GNU with --numeric-owner (10 KB or 2.3 MB alike) |
exit 139 after the first file |
ustar with owner root, group sys |
extracts |
GNU with owner jdoe, group users (a typical Linux user's tarball) |
exit 139 |
the same, tar xof as root |
extracts, files owned by 0 |
the same, tar xf as a non-root user |
extracts, files owned by that user |
Who hits it 🟡: root running a plain tar xf on most tarballs made on another system. The default
PATH lists /usr/sbin before /opt/amix/bin, so plain tar is the 1992 binary even when the GNU
tar package (/opt/amix/bin/tar) is installed. A stock 2.1 kernel was not run; the fault is a user-space
NULL read and needs only an unmapped page 0.
On the 68040/060 line it depends on the kernel, not the CPU ✅. A 68060 kernel with the TT fix
crashes the same way as the 68030: exit 139 with a core and no regular file written, for ustar and GNU
archives with absent names and with empty ones. The 68060 kernel of the amigaux 1.1 image predates
the fix: the same tar prints the same messages and carries on, exit 0, every file laid and owned by
0/0 instead of the archive's 1234/5678. That kernel lets user code read low physical memory
(the 040/060 user address space), so the NULL read
returns a word instead of faulting. The 68030 kernel of the 1.1 image and a later 68030 build both
crash (all four in emulation, archives from host GNU tar 1.35).
Two more limits of the same binary ✅ (the same four systems):
- No GNU long names and no pax headers. A GNU archive with one 117-byte name, and a pax archive
with the same name, stop at that entry with
tar: directory checksum error(exit 2), after laying the files before it.tflists././@LongLink(or aPaxHeaders/…entry) and the name cut at 100 bytes. A ustar archive that keeps the same name in the 155-byte prefix field extracts fine. - What
tar cfwrites, compared field by field with Heirloom tar's: the mode field carries the file-type bits (0100644,0040755,0120777); devmajor and devminor carry the file system's own device in every header; a directory comes before its contents, named with a trailing slash (./d/); records are 20 blocks. Sizes are right: it keeps them in along, so the libc%llodefect (toolchain) does not reach it.tvfshows directories and symlinks with-, and a symlink's target length as its size.
Workarounds ✅:
- install the amix
heirtarpackage (Heirloom tar 070715), which replaces/usr/sbin/tarand keeps the/etc/tarlink. As root it extracts all nine test archives (v7; ustar and GNU with names present, absent and empty; pax) with exit 0 on the four systems above. A name that exists maps to its local id, as before; an absent or empty name keeps the archive's numbers. It also reads GNU long names and pax headers. It is an essential package: apkg refuses to remove it, and a plainpkgrmleaves Heirloom's binary in place, sincecorestill lists the path. Nothing else on the 1.1 images callstar; - as root, extract with
tar xof(owner and group not restored); - or extract as a non-root user;
- or use
/opt/amix/bin/tar; - or build v7 archives, or ustar archives whose owner and group exist on the box (
root,sys).
For staging files into a guest, a v7 archive of regular files from a sorted list works on every kernel:
find . -type f | sort |
tar cf stage.tar --format=v7 --owner=0 --group=3 --numeric-owner --no-recursion -T -
97. Whether a NULL read returns 0 or faults depends on the linker ✅¶
193 of the 434 ET_EXEC files in the stock system tree carry a read-only zero page: a PT_LOAD
at vaddr 0 with file size 0, memory size 0x1000 and flags R (for example usr/lib/saf/listen) ✅.
Mapped, it reads as zeros and refuses writes. So in such a stock binary a NULL read returns 0 and a
NULL write faults. Binaries built with the gcc14 cross toolchain carry no such segment, so a NULL read
faults there too (its nullread test: reading address 12 gives SIGSEGV) ✅. A program that passes
as one kind of binary can crash as the other.
That holds on the 68030 line, and on the 68040/060 line since a fix that is not yet in a release. Before that fix, the 040/060 kernel's transparent-translation window covered the low 1 GiB for user accesses too, so a NULL read in either kind of binary returned chip-RAM bytes instead ✅ (the 040/060 user address space).
The stock tar of quirk 96 is one such case: it reads through a failed getpwnam result.
98. A vfork child that sets a signal handler breaks the parent's ✅¶
The static vfork in the sysroot's libc archive (usr/ccs/lib/libc.so) is a real vfork: the child
runs in the parent's memory until it calls exec or _exit ✅. That collides with how the stock
libc delivers signals. signal, sigset and sigaction store the handler in a table in
libc.so.1's own data (_siguhandler) and register a libc trampoline with the kernel (savr for
signal/sigset, sigacthandler for sigaction), which loads the handler from the table and calls
it ✅ (disassembly of the stock libc.so.1). SIG_DFL and SIG_IGN go straight to the kernel, but
the table entry is overwritten all the same ✅.
So a vfork child that calls any of the three rewrites the parent's table entry. The parent's
trampoline stays registered with the kernel; at its next delivery of that signal it calls the value
the child left, 0 or 1, and dies with SIGBUS (si_code 1, BUS_ADRALN) at the trampoline's call
✅. Seen on a real A4000D (68LC060) with X11R6.3's xinit built with vfork: its child sets
SIGUSR1 to SIG_IGN before it execs the server, the server signals the parent when it is ready,
and xinit printed Bus Error - core dumped (2 of 2 runs, two matching cores). X never starts
through xinit/startx, and the server is left running with no client. The same xinit.c built
with fork ran the client and ended cleanly on the same box ✅.
The stock vsyslog vforks too ✅ (both libc.so.1 and the static copy GCC 14 links in): when
openlog was given LOG_CONS and the write to the log device fails, its child calls
signal(SIGALRM, SIG_DFL) before writing to the console. A program with a SIGALRM handler that
logs that way would lose the handler and crash at its next alarm 🟡 (follows from the above, not yet
seen on a box).
Rule ✅: use fork. Where vfork stays, the child calls nothing but exec*() and _exit(); no
signal calls, and by the general vfork rule no syslog, stdio or malloc. X11R6.3's Imake gives every
platform not declared SystemV vfork (HasVFork YES), so an SVR4 cf file must say
#define HasVFork NO (toolchain).
See also¶
- Hardware — the machines, RAM ceiling, Zorro II, FPU/MMU requirements in full.
- Boot process — Superkickstart, the bootstrap, and on-disk layout.
- Networking — DNS, routing, SLIP, NFS, and clock notes.
- X11 & desktop — the X server, window managers, and keymap.
- Glossary — RDB, AUTOCONFIG, STREAMS, Superkickstart, and other terms used here.
Sources¶
- The
amixsh.pyrun on a 68030 guest (first-party record, 2026-11-05) ✅/🟡 — quirk 12'sfalseitem, quirk 39'snohupparagraph and quirk 99's 237-byte line: the amixkernel/k30tooling's run oftools/host-net/amixsh.pyagainst a 68030 kernel build, with the marker on its own line. The hang-up at logout is inferred from the behaviour. Nofalseortruefile is inamigaux/amix-system, and where the 255 comes from was not traced. - The canonical line wedge (first-party record, 2026-08-27) ✅/🟡 — quirk 99: the amix
kernel/k30bench tooling's line-length probe on a 68030 box (229 bytes answered, 269 stalled and wedged the login), its earlier failure on a 260-byte line and the^Gbells seen on 2026-08-01.MAX_CANONis inusr/include/limits.handusr/include/sys/param.hinamigaux/amix-system, read here. The threshold, the cause and the 237-byte budget are inferred from these, not measured. - The empty Zorro III read per CPU path (first-party record, 2026-11-02) ✅ — quirk 66: the amix
kernel/dlmbprobetest modules (root pass at level 7, boot passes at levels 0, 4 and 7, each read timed by CIA-B's TOD clock) on an A4000D with a Z3660 in real-68LC060 and emulated-68030 modes, and under Amiberry; the 2026-10-27 root-context probe aboveVSECT1that faulted in the MMU.amigaux/amixkernel/dlm/tests/bench/bprobe/(51dd57a, onmaster); the coverage note inkernel/dlm/docs/contracts/C4-2-root-class-driver.md§2.1 (9407b0f, onmaster). - The zero page (2026-11-01) ✅ — quirk 97: the program headers of the
ET_EXECfiles inamigaux/amix-system(193 of 434 carry the read-onlyPT_LOADat vaddr 0, checked here withreadelf -l); the gcc14 toolchain'snullreadtest and the 68040/060 port's before-and-after runs in emulation (first-party record, 2026-11-01). The 040/060 sources are on the status page. - The stock
tarNULL owner lookup (first-party record, 2026-11-01) ✅/🟡 — quirk 96: the amixkernel/k30runs in emulation, which pulled/usr/sbin/tarfrom the 68030 and 68LC060 images (one SHA-256 for all copies) and took a core on the 68030 (the faulting read through a zero address register). The disassembly of the binary shows thegetpwnam/getgrnamresults read after their failure messages, checked here against the pulled copy. The archive table (v7, ustar and GNU, named and numeric owners,xof, non-root) ran on two 68030 kernel builds; the 68LC060 run was on a 68040/060 kernel from before the TT fix. The first observation (exit 139 on ustar and GNU, v7 fine, the 68040/060 image unaffected) came from the amixkernel/dlmstaging runs the same day. - The
EXITtrap status andsetclk -s(first-party record, 2026-11-04) ✅/🟡 — quirk 12'sEXIT-trap item: the amixuserland/baseprobes (trap true/false/echo $?/saved$?, and no trap) on the amigaux 1.1 images with 68030 and 68060 kernels in emulation, the same results on both; the fixedntpsyncandxstartand thetest-lint.shEXIT-trap check (amigaux/amixuserland/base,a2c2222,b1e256b,075b143, onmaster). Quirk 7'ssetclk -s: the stockusr/amiga/src/cmd/setclk/setclk.cin amigaux/amix-system, read here (the 11-byte buffer, the%02dyear, theyear < 70rule) 🟡 until run; the corrected binary from the Installer-NG project'ssetclk-y2k.patch. - The vfork child and the signal table (first-party record, 2026-11-02) ✅/🟡 — quirk 98: two
xinitcores from a real A4000D (68LC060) with the x11r63 package built with vfork, and the fork build's clean run on the same box; the disassembly of the stocklibc.so.1fromamigaux/amix-system(signal/sigset/sigaction, thesavrandsigacthandlertrampolines,_siguhandler, andvsyslog's vfork), thesavrcall checked here; X11R6.3config/cf/Imake.tmplandprograms/xinit/xinit.c; the fix inamigaux/amixuserland/packages/x11r6.3(c3abeff,443fe46, onmaster). - The stock
taron four systems, andheirtar(first-party record, 2026-11-01) ✅ — quirk 96 and quirk 12's${1:?word}item: the amixuserland/packagesHeirloom tar port's emulator runs, nine archives made with host GNU tar 1.35 on the 68030 and 68060 kernels of the amigaux 1.1 image, a later 68030 build and a 68060 kernel with the TT fix; the stocktar cfheaders compared byte for byte with Heirloom's; the package's install, extraction and removal checks.amigaux/amixuserland/packages/heirloom-tar(b07d4cb, onmaster: README,tests/). -
The
aenDLPI traps (first-party record, 2026-10-30; the short-frame refusal 2026-10-31) ✅ — quirk 95: see the matching Sources entries on the networking page. -
The 4 KB TCP lower-read queue (2026-10-21) ✅ — quirk 75: the stock kernel's
ipintr/putq/canputdisassembly against the USL SVR4netinetsource, thetcpm_infoword read in the 2.1c/stand/unix, and the live-kernel and patched-kernel push rows on a real Z3660 box (first-party record); detail and citations on networking. - The 1.1 namelist and the
sar/crashmessages (first-party record, 2026-10-31) ✅ — quirks 26, 44, 80: see the matching Sources entry on the kernel-probing page. - The
netstat -rnroutehash failure (first-party record, 2026-10-30) ✅/🟡 — quirk 93: see the matching Sources entry on the networking page. The bench image's kernel (first-party record, 2026-10-30) ✅: the k30 kernel work's comparison of the booted kernel with the image's/stand/unix(size,sum -r, cdfs and A3091 strings, text size against the stock link). - The 2.1
/bin/shbackground-job facts (first-party record, 2026-10-30) ✅ (theO_APPENDmechanism 🟡) — quirk 94: the amixuserland/baseshell tests on the emulated 2.1c bench image's stock userland (waiton a finished and a running child, at top level, in a subshell and in a function; two background jobs appending to one log;TERMthenKILLof a shell job and ofsleep;sh -coperands). The base package'src_bgis built around them and its comments record them (amigaux/amixuserland/base/rc/rc.subr,8ee48af). - The
queuerunpanic (first-party record, 2026-10-30) ✅ — quirk 92: the amix-packagesprngd0.9.29 port's runs on the emulated 2.1c bench image (three panics captured on the console with their backtraces, symbolised by the k30 kernel work; the survived loads and the twoprngd-free reproducers); the recipe in amix-packagesrecipes/prngd(cc61d24). The cause (first-party record, 2026-10-30) ✅: the k30 kernel work's disassembly of the stockqattach,qdetach,freeqandstrclose; its runs of the stock 2.1c link on an emulated A3000 (16 MB bank) withfreeqinstrumentation (two catches, one panic with observation only), thebufcall/callout counts, and theanoninforeservation figures; the fixamigaux/amixkernel/k30f4aef26(src/freeqguard030.c,tools/freeqguard-retarget.py, onmaster). The package-test runs on three kernels (first-party record, 2026-10-31) 🟡: the amix-packagesprngdport's smoke test and loads on the emulated bench image, a 2.1c kernel with only the guard, and the project's 68030 test kernel, one boot each; results in the recipe's README (amix-packagesrecipes/prngd,fd96cc7; the bench-image panic left unconfirmed as quirk 92 in7349c19, the guard-only row's empty reads in3208937). The 040/060 line (first-party record, 2026-10-31) ✅/🟡: the port'sISSUE-109inKNOWN-ISSUES.md(amigaux/amix-040-060-portk46-module: the guard and its binding at17501b9, the write-up ata1418bb) — the byte comparison against the stock 2.1c link, seven guarded runs and four controls in emulation. - The wider SIGSYS set on an FPU-less 68030 (first-party record, 2026-10-30) 🟡 — quirk 31: the k30 kernel work's runs on an Amiberry 68030 with MMU and
fpu_model=0under the signal-gated 68030 kernel: stockmkfs -F ufsand the gcc14-built GNUcpandgtarexit 140 withBad System Call;dd,cmp,sh,sleepand stockcprun. - The no-FPU signal panic (2026-10-14) ✅ — quirk 74: the console capture from a real A1200 with an FPU-less 68030 card (Kickstart 3.1) and its reproduction in Amiberry with
fpu_model=0(first-party record, 2026-10-14); the stockstand/unixsymbolfrestore @ 0x0000012ainasokero/amix-040-060-portdocs/68040-68060-support-analysis.md§2.4; the eleven FP-state call sites, ten gated in stock andsendsig+0x1f8→fpu_setupthe one ungated, in the same repository'sdocs/060-F1-M2-GATE-CENSUS-260824.md§3;src/fpu060.s(every FP-state body issues FSAVE or FRESTORE, an F-line exception in supervisor mode on a part with no FPU) andsrc/fpuinit060.s(fpu_setup_gated, the 68060 gate at thesendsigsite). The 68030 fix (first-party record, 2026-10-30) ✅: the k30 kernel work's emulator runs, stock 2.1c and the gated test kernel on an Amiberry 68030 with MMU and no FPU, and the gated kernel with a 68882;amigaux/amixkernel/k302a3e894(src/fpugate030.s,tools/fpugate-sendsig.py, onmaster). - The
fssm_DeviceNUL count (2026-10-07) ✅ — disassembly ofexpansion.libraryMakeDosNodeand thestrapin Kickstart 3.2.3 and 3.1 r40.68, and a check of 52 Kickstart images from 2.0 to 3.2.3 (first-party record, 2026-10-07); NDKdos/filehandler.h(quirk 71). Details on boot process. - The 2026-09-29 stock-kernel RAM-layout measurement ✅ — on the emulated bench the same 64 MB A3000 motherboard bank boots a kernel that probes for the A3000 SCSI and stops the pinned stock kernel at
s5mountroot VOP_OPEN error 6(no card 0), symbolised against the stockautocon()load-address guess (first-party record, 2026-09-29; quirks §57). - Research brief §12 (quirks checklist), with specifics pulled from §2 (hardware & limits), §9 (install flow +
viper_kludge/Y2K), and §11 (networking, X11, userland). amix_21_root.adfanalysis viatools/inspect-adf.sh—/dev/rmt/4h,BPART=/dev/dsk/c${SCSI}d0s${BOOTPART},amixpkg -i -m -d -r /mnt -y standard.amix_21_patch.adfanalysis viatools/inspect-adf.sh— patch-disk Y2K/inet fixes (→ 2.1p2a / kernel 2.1c).asokero/va2000-amix(pre-POSIX/bin/sh, no$(...), nogrep -q; relink hygiene) — https://github.com/asokero/va2000-amix.- Michael Ditto, Writing Amix Device Drivers, 1990 European Amiga Developer's Conference (driver/kernel model behind the SCSI-ID and
/bin/shnotes). - amigaunix.com DokuWiki — requirements, networking, x11, patch-disk, y2k-dst, tips-tricks, dual-boot pages (community-reported items): https://www.amigaunix.com/doku.php/home.
- BlitterStudio/amiberry issue #1376 and WinUAE/FS-UAE docs (emulation consequences of the SCSI IDs and RAM ceiling).
- The A4091-on-Amix project — networking investigation, 2026-06-07 (reproduced locally ✅): instrumented
/etc/rc2per-script timing on Amix 2.1c under Amiberry isolatedS69inetat 180 s; boot 210 s → 29 s after switching the boot-timeifconfigs to literal IPs. Files:/etc/rc2.d/S69inet,/etc/inet/network-config,/etc/inet/rc.ineton the running system (quirk 13). - The amix-z3660net project — the
z3660ethdriver bring-up on a real A4000 + Z3660, 2026-06-21 ✅: the firmware INT6-per-RX-frame storm (Amix has no level-6 ethernet handler) and the disable-IRQ/poll fix (ZZ_CONFIG_DISABLE,timeout()RX drain), diagnosed via the firmware serial[PC]heartbeat +nmofrelocunix(quirk 14). See the Z3660 ethernet driver. - The amix-z3660scsi project, commits
86b7a37(the multi-method detect, quirk 15) and0d67693(the emulator triage, quirk 16) — thez3660piscsi block-driver bring-up on a real A4000 + Z3660, 2026-06-12/13 ✅: the 2.1bootinfo.autocon[]miss and the multi-method detect that cleared the silent hang (quirk 15), and the firmware-emulator-vs-driver triage (quirk 16 — the apparent hang was the 68k emulator demand-paging the driver's own pages, not the driver; firmware-owned fix, cited not reproduced). See the Z3660 piscsi SCSI driver. - Research brief §19 (emulation fidelity), from the Z3660 firmware project (branch
amix-main, 2026-06) — quirk 17 (the A3000-SCSI bootstrap's INT2 sensitivity to interrupt-service cadence; theservice_cadenceknob ✅ / the 4-boots-8-hangs boundary 🟡) and the emulator-side mechanism behind quirk 16 (68030 bus-error-frame fidelity on the demand-paging path). See Emulation Fidelity. - Research brief §20 (stock SVR4 package system), from the amix-packagemanager project (firsthand on a clean Amix 2.1c image, 2026-07-01) — quirk 10's
amixpkg-is-install-media-only correction ✅/🟡, and quirk 18 (the/var/sadm/install/contentsspace-in-pathname record that breakspkgrm/pkginfo -lout of the box, ✅). See Package management. - The amix-cdfs project @
31e8c3b— quirk 19 (mount(2)silently stacks on an already-mounted directory;mount(2)'s only busy check isv_vfsmountedhere, whichlookupnameclears by traversing into the mount; guard in the fs's own mount op likeprmount/fdmount), from stock-kernel disassembly + reproduction on a real A4000 + Z3660, 2026-07-13 ✅ (844c2ed). See the driver model. - Bench forensics (first-party record, 2026-07-11) — quirk 20 (SVR4
login's future-lastchg"expired" trap on an RTC-1978 box after a clock was set forward; same-password workaround, never set the RTC forward), root-caused on a real A4000 + Z3660 ✅. - The Installer-NG field work (first-party record, 2026-07-22/24; the installer project is not public, amix-packagemanager is; released as amigaux 1.0) — a blank-disk→bootable-install effort that root-caused these platform behaviours on the Amiberry bench and the real A4000+Z3660 (acceptance-run captures, s5/UFS state reads, and the on-metal digest attestation) ✅ (🟡 where tagged).
- The
inetdinvestigation and a wedge soak on thecdfsbench (first-party record, 2026-07-26/27) ✅ — quirk 23 (theTOOMANY=40/CNT_INTVL=60 s/RETRYTIME=600 sthrottle read out of the shipped/usr/sbin/inetdas compile-time immediates, the absentnowait.<max>syntax proven by disassembly and a liveSIGHUPtest, per-service independence measured both directions, and the rate bound: 29.9/min never trips in 200 invocations, ~575/min trips within seconds) and quirk 24 (the vendor-hardcodedexitin/etc/init.d/syslogd, its 3-hard-linkcp-in-place hazard,/var/log/*rather than/var/adm/messages, and the measured 293 B of log growth under abuse). Quirk 26'snetstat -acontrol run and thesar -v/ipcs -afailures are from the same soak's wedge-time capture ✅. - The stale
/etc/syslog.pidnote under quirk 24 (first-party record, 2026-10-26) ✅ — the script text, thesh -xtrace and the early-boot variant test, as cited on Networking. - A
cdfsbench run (first-party record, 2026-07-26) ✅ — quirk 27: on a 1.25 GiB multi-extent UDF disc,dd skip=to the 1 GiB mark transfers rather than seeks and needs a ~20-minute command timeout. - A read of the upstream Amiberry source (BlitterStudio/amiberry:
src/cfgfile.cppdefault_prefs(),src/main.cppfixup_prefs()→fixup_cpu(),src/osdep/amiberry.cppdefault_disable_cycle_exact) and a rig ablation on the emulated bench (first-party record, 2026-07-26) ✅ — quirk 25: thedefault_prefs()all-true initialisation, the four non-equivalent config keys, the host-dependentdefault_disable_cycle_exact, thefixup_prefs()host-log tell (present in 6/6 omitting runs, absent in 10/10 pinning runs) and itscpu_compatibleside effect, and the 10-boot single-variable ablation whose decisive run addedcycle_exact=falseand nothing else. - The Installer-NG author-mode matrix proof (not public, 2026-07-31) ✅ — quirk 28: the stock
/etc/rdb -c/-a/-p+mkfs_s5sequence run on a blank bench disk and read back host-side (the-drefusal and its absence from the usage text; theatoi()1-based partition-number convention; the 512-byte-blockmkfs_s5operand), with the stock install script (/etc/profileon the root floppy, read by path with an s5 reader) as the reference caller. See Filesystems & disks. - Linux mainline
arch/m68k/amiga/config.c— the A3000/A3000T-onlyMAGIC_REKICKhardware feature ("Magic Hard Rekick") and its0xde0002 |= 0x80write, commented as forcing a hard rekick — grounding quirk 29's A3000 register semantics ✅: https://github.com/torvalds/linux/blob/master/arch/m68k/amiga/config.c. - BlitterStudio/amiberry,
src/gayle.cpp(mbres_write/mbres_read; the bank is mapped at0xde0000insrc/memory.cpp) — the UAE-family model of0xde0002: Fat Gary's bit-7 "coldreboot flag", settable and readable (read-back gated on a configured Fat Gary revision), with no consumer — grounding quirk 29's A4000 side ✅: https://github.com/BlitterStudio/amiberry. - First-party stock-kernel analysis and the project's metal-boot record (real A4000 + Z3660),
2026-07 ✅ — quirk 29: the stock kernel's unconditional
0xde0002write in both the boot and the reboot/halt paths, and its benignity on the A4000, where every metal boot forwards the write through the accelerator to the physical bus with no ill effect. - A cdevsw free-row gate and half-patch audit (first-party record, 2026-07-30/31) and the
amix-z3660net patch-script hardening (
603c719) ✅ — quirk 30: theedmiss-then-write half-patch reproduced host-side with GNUedover the real stock file layouts, its name-wide-grep permanence, the wrong-row-overwrite and wrapping-search variants, and the pre-probe / one-run-per-edit / post-verify-the-row pattern. See building a kernel. - The project's transport lint (first-party record, 2026-08-27) and the two live defects it
generalises from — an
amix-kerntoolsrunner defect (first-party record, 2026-08-27) and the Z3660 firmware fork'sCHANGELOG.mdentry of 2026-08-27 (branchrelease-1.0) ✅ — quirk 39: the transport's appended; echo DONE_$?turning a bare trailing&into the Bourne syntax error&;, the parenthesise-the-launch fix, and the four superficially-similar shapes that are safe and must not be flagged (the linter's--self-testexercises all four plus the unsafe one). - The RAM-ceiling measurements (first-party record, 2026-08-12/14)
✅ — quirk 41 (stock
autocon()'send > 0x07000000inference of the A3000 SCSI — read then as a RAM-size test, corrected 2026-09-29 to the kernel's load address, failing in both directions, with real A3000s above 16 MB inside the affected envelope) and the measurements' own method laws, quirks 42–45: the neighbour sweep that found the arena's derived constants but missedsegkmap(sized from physmem); the mandatory disassembly-diff third gate, every patch site located by instruction context rather than by offset; theelf2brel-stripped/stand/unixthat is useless as anadb -knamelist; and the spontaneous reboot under a heavy rawddto the boot slice, whose only reliable record is the verify line the job wrote to disk. See the RAM ceiling. - The accelerator acceptance campaign, real A4000D + Z3660 with a socketed MC68LC060
(first-party record, 2026-08-31 / 09-01) ✅ — quirk 49 (the 5/5 versus 0/3 reset-vector rates for a
power cycle versus the firmware's warm reset, the
0xFFFFFFFFvector read and the runaway-CPU register/vector scribble that follows it, and the overlay-cleared address-4-is-chip-RAM correction), quirk 50 (the sticky address-error mask reproduced against a zero-error dense pass, plus the destructive Autoconfig assign step, the multi-keypress test stop and the 4-hex-digit scan field), quirk 51 (the two capacitive-echo detections — an empty 4 MB motherboard window and a phantom 127 MB over a dummy-mapped range), and quirk 52 (the serial signature of a failed warm reset: no firmware start, no Kickstart load and no ROM reload). Board-level signal-path documentation and the campaign's tooling notes are deliberately not reproduced here. - A first-party record, 2026-08-14/15, real A4000 + Z3660 ✅ —
quirk 46 (
z3660_direct_map/Dprinting16777216for a set one-byte flag, read live against the running kernel), quirk 47 (sync; sync; /sbin/uadmin 2 0reported by the host runner as "failed after 3 tries" on both halt cycles, with thefsck-silent verification boot as the only proof of cleanliness). Same session for §44's exception: the on-box native-linked/stand/unixkeeps its symbol table and is a validadbnamelist for itself. - First-party read-only metal session, real A4000 + Z3660, 2026-08-27 ✅ — quirk 40:
dmesgnot present on the system;wtmp's header dated Mar 24 1992 with zero reboot records andlast rebootreturning nothing;/sbin/bcheckrcread verbatim (pre-check verdict discarded to/dev/null, repair pass to the console with no redirection); and the passive HDMI capture at a freshlogin:prompt recovering the full boot transcript, used in the same session to answer a "did fsck run?" question. A delta to quirk 24, orthogonal to the syslogd state. - The 68060's unimplemented-integer instruction class, read against the stock SVR4
trap-dispatch table on this platform ✅ — quirk 53: the CPU's own definition of the integer
forms it declines to implement in silicon and traps out for software emulation (vector 61), versus
a dispatch table whose populated range ends at vector 55, so the vector reaches only the default
unhandled-vector path and the process dies as a bare
Killed(rc 137) with no SIGILL, no SIGFPE and no core. Because the forms are integer ops, the outcome does not depend on whether an FPU is present ✅. The 32-bitMULU.L/DIVU.L/DIVUL.Lspellings are hardware-implemented and unaffected ✅;CMP2/CHK2/CAS2/MOVEPare named as the same architectural class rather than each individually exercised 🟡. The matching porting note is on 040/060 status; the unrelated GNUas2.8.1 mis-encode that produces a 64-bit divide form on a 68030 is on the toolchain page. What the amigaux 1.0 kernels emulate (Motorola's 68060 Integer Support Package: the 64-bit multiply and divide forms,MOVEP,CMP2/CHK2; onlyCAS2and a misalignedCASstill killed; earlier kernels only some multiply forms) is from the 1.0 release notes (Installer-NG, not public,docs/release-notes-amigaux-1.0.md, "The package engine: 1.12.3 replaces 1.12.0 on the 68060") and the amigaux.org known-issues page ✅. - The Z3660 real-68060 sessions, real A4000 + Z3660 with a real
68LC060 (first-party record, 2026-09-08/09) ✅ — quirks 54, 55 and 56. Quirk 54 read from the firmware source (the
amix_mode→cpu_ram = 0forcing, and thescsiboot && cpu_ramgate whose else-arm printsCPURAM disabled -> SCSI boot disabled), with both preset shapes booting on the metal the same evening. Quirk 55 measured on the metal — the same Amix disk booting to multiuser underUAE_040_MMUon a KS 2.04 A3000 ROM and reset-looping on the real 68060 until the preset named an A4000 3.2.x ROM instead — with the "not a generation requirement" half established by scanning theUNI\1dostype constant across every ROM from 2.04 to 3.2.3, and the CRC-not-filename rule. Quirk 56's loader behaviour from the firmware's own[SD Init]lines across a deploy (FAIL (Z3660.BIN) / FAIL (FAILSAFE.BIN) / Done (BOOT.BIN)before,Done (Z3660.BIN)after), and its menu half from the firmware's key handling. - The A4000T
$DD0000reading behind §41's A4000T bullet (first-party record, 2026-09-24, as cited on the Z3660 SCSI driver page): the shipped 060 kernel's unstrippedautocon()disassembly plus the stocksupport.c/sd.c/a3091.c— the load-address phantom at0xDD0000sorting as card 0, the lazy first-I/O initialisation, the no-timeout busy-wait; on an A4000T that window is the onboard NCR 53C710. The hang itself is predicted from source, not reproduced on the metal 🟡. The chipset gate in the amigaux 1.0 68060 kernels ✅: the Installer-NG (not public) acceptance-rig configs (configs/accept-z3660-060-fd0.uae: with the probe in, the Z3660 is card 0) and amix-a4091docs/A4000T-ONBOARD-710.md§2 (the A4000T kernel must carry the gate, enforced at media build); the public port's mainline leaves the guess alone per its README. - The 2026-09-28/29 battery-clock work, first-party, real A4000T and the installer bench ✅ —
quirk 7's boot behaviour and its three details: the RP5C01 ticking and holding time across a
power-off, the stock image coming up at 1977 with a working clock, the stock
setclkrun ending indate: bad conversion, the minute-granulardateroute, the/etc/clkzoneconvention, the battery-clock year encoding read back both ways, and the 1991 zone files' pre-1996 EU and pre-2007 US rules (first-party record, 2026-09-28/29; Installer-NGdocs/installer.md, not public). - The 2026-09-29 shutdown-stall discrimination, first-party, eight runs on the IDE and A3000
bench rigs ✅ — quirk 58 (first-party record, 2026-09-29;
amix-gayle-ide/README.md, the stock-initnote). - The Z3660 firmware fork's
docs/AMIX.md, "isoN <file>— CD-ROM images" and the beta-21 resync defect list (item 4) ✅ — quirk 60, read from the firmware's config parser; the out-of-boundsiso7–iso19half is fixed on the fork. - The amix-gayle-ide review (2026-09-28/29) and the three fixes it prompted —
amix-a4091CHANGELOG 2026-09-29 (support.c.patch, low-nibble gate),amix-z3660scsiCHANGELOG 2026-09-28, and the ECS-NTSC bench row, VPOSR id0x30(first-party record, 2026-09-29) ✅ — quirk 61. - The 2026-10-02 large-target bracket ✅ — the installer's own author sequence on blank 600–1024 MB targets on a 68030 and a 68060 kernel, the stock
sddevsize()default of 1 234 567 blocks found as the cause, the whole-disk raw-read hang bisected, and the fix (default0x3FFFFF+ add.coffset check) re-run green at every size (first-party record, 2026-10-02). - The CD-unit overrun fix ✅ —
amix-z3660scsiCHANGELOG 2026-10-02 (commite072408: refuse a READ/WRITE larger thannbyte; host harness 184/0) and its metal check on an A4000T the same day (rawddat three block sizes →I/O error, counter +3; first-party record, 2026-10-02). - The FTP passive-mode truncation ✅ — the 2026-10-02 bench matrix (passive from
cdfs18/80 short, passive from UFS 0/80, active 0/40,226on every truncated transfer; first-party record, 2026-10-02) and the earlier 68030 measurement plus the length-gate recipe inamix-kerntools/tools/amixsync.py("the truncation trap"; thisftpdhas noSIZE). The short-write mechanism is a diagnosis from behaviour 🟡. - The A4000/030's stock CPU card — Commodore's 68EC030 card, identified on the bench (first-party record, 2026-10-01) ✅.
- The signed manufacturer compare in stock
autocon()✅ —amix-a4091commit06a7d96(2026-10-02,support.c.patch: the unsigned shift, codegenswap/ext.l→clr.w/swap), the matching one-byteasr.l→lsr.lpatch in the 68040/68060 relink tooling the same day, and the A4000D metal run that proved it (the A4092's0xC0DE0001handed over inbootinfo.autocon[]but unregistered without the fix, registered as card 1 with it; first-party record, 2026-10-02). - The 2026-10-02 local-I/O peek measurements, first-party, on an A4000D with a Z3660 ✅ — a supervisor-mode, fault-catching peek tool under AmigaOS: the first read of
$DD0000never returned; reads of Gary's$DE0000/$DE0001on the A4000D terminated (FF→7F,7F) (first-party record, 2026-10-02). The CPLD side is read from the Z3660's CPLD sources (TERM.vhd/BUSTERM.vhd: termination waits onDSACK/AVEC/BERR,nTEAonly from the bus-error state, no timeout counter anywhere). The meaning of the A4000's$DE0000/$DE0001bits is unsourced 🔴. - The CPU-path resolution and the TOENB cell (2026-10-02) — the Z3660 emulator sources (
Z3660_emu/src/cpu_emulator.cppwait_read_ack()timeout,uae_emulator.cppmake_dummy_address_bank(),uae/memory.cppGary non-range →hardware_exception2whengary_toenb), UAE's Gary register map (amiberry/src/gayle.cpp:$DE0000/$DE0001/$DE0002bit 7 = timeout/TOENB/coldboot), and a first-party A4000D test writing0x80to$DE0001(read back0xFF) before a$DD0000read that still hung ✅; whether the A4000 board logic can assert a bus error there at all awaits a scope look 🟡. - The Z3660
kickstart 0ordering and the.d6rule (2026-10-03) ✅ — the FPGA source (z3660.v:maprom_enablelatched only underRESET_IN || GPIO_s[31], the MAPROM range served from DDR when set) and the firmwaremain.corder (MAPROM enabled at start-up, FPGA reset pulsed, CPU side started, the bit cleared forkickstart 0afterwards with no further reset), the same order in upstream shanshe/Z366050e98e0(v1.03 beta 21), and a first-party A4000D run with Kickstart 3.2.3 in the sockets (black screen, no SCSI I/O, 2/2), then the reordered firmware on the project's fork bootingkickstart 0and SCSI-booting Amix withkickstart 1unchanged (same box, 2026-10-03), and on an A4000T booting DiagROM 2.0 from the sockets (2026-10-03); the fix shipping in the fork's firmware 1.0 (C96EAD03) per jusii/Z3660-amix branchrelease-1.0(CHANGELOG.md2026-10-04,release/MANIFEST-1.0.txt) and the amigaux 1.0 release notes; the.d6patch and its CRC pairs from the project's patcher, with DiagROM's$F800D6reset PC as seen on the bench (first-party record, 2026-10-03). - The 68040/68060 disk write stream (2026-10-03) ✅ — measured on the project's emulator rig through a disk-write capture replayed exactly onto the image (every 2 KiB piece landing with its own tag; inode blocks flushed twice per
sync, 69–75 ms apart in three captures (first-party record, 2026-10-03); a driver fault knob refusing one piece of an inode-block write, with the refused piece reaching no LBA and all four rewritten at their own LBAs on the next flush; a time-masked duplicate detector), and the request break-up read from the kernel binary (first-party record, 2026-10-03). - The stock
installfcrash on the 68060 (2026-10-03) 🟡 — an installer acceptance cell in an emulator run of the 68060 kernel:installf -f core→User BUS ERROR at 4AFC0002, a core in/, the entry left with?fields;/usr/apkg/bin/installf -f coreon the same system → exit 0 and the kernel's true checksum; the installer's first-boot step switched to APKG'sinstallf, run from/tmp(first-party record, 2026-10-03). The engine builds (1.12.3 shipped in 1.0; the July 2026 1.12.0 build killed by 64-bit divides on a 68060 kernel before 1.0) are from the amigaux 1.0 release notes (Installer-NG, not public,docs/release-notes-amigaux-1.0.md, "The package engine: 1.12.3 replaces 1.12.0 on the 68060") ✅. - The A4091 driver's real-hardware scope — the amix-a4091 README's status table: the A4092 on the 68060 line (A4000D + Z3660) with its root filesystem ✅, the A4091 board and the 68030 line still pending on real hardware 🟡 (quirk 3).
- The
AF_UNIXmemory-pressure corruption and the AmiberryMOVESpage-boundary defect 🟡 — third-party reports from hmlio/amix-x11-aga (docs/findings.md; the reproducertest/sockstress.c), not reproduced here; the Amiberry fix is upstream commit845af02(quirks 72, 73). - Quirks 76–86 (first-party record, 2026-10-25 to 2026-10-29) ✅: the kernel-module campaign's emulator rows on both kernel lines and the stock-script and disassembly reads behind them — the
syslogd/m4row, theputbuf/consloggingread of the kernels' print code, the stockbcheckrc, the namelist probe, the 2.1shconstruct probes on the miniroot shell, the ufs/s5 page-size rows, the Paula timer and/dev/audiodiagnosis, the AGA console row, theswapconfandfakeswaprows (amigaux/amixkernel/dlm/CHANGELOG.md, 2026-10-25 to 2026-10-28); the diffutils 2.8.1 port's rows on an emulated stock 2.1c (diff -u,patch); the Gayle IDE driver's ATAPIddrow (amix-gayle-ideCHANGELOG, 1.1.0-2). - Quirks 88–89 (first-party record, 2026-10-29) ✅: the amix-packages bzip2 1.0.8 and coreutils 5.2.1 ports — probes on an emulated stock 2.1c 68030 (the
tr/sedsteps;datewith and withoutTZfrom the shell), the core file'sNT_PRSTATUSnote for the crash PC and registers; the ports' patches (recipes/bzip2/patches/02-bzgrep-bourne.patch,recipes/coreutils/patches/07). - Quirk 90 (first-party record, 2026-10-30) ✅: the amix-packages rsync 3.1.3 port — a C truncation
probe on an emulated 2.1c 68030 (64 MB, ufs root,
st_blksize8192), one case per boot, the console captured at each panic; the panic path symbolised against the stock 2.1c kernel (sha25680a1204f…, versions); the port'sdo_ftruncatepatch and its--inplacesmoke test (amix-packagese7ac916,recipes/rsync/patches/01-ftruncate-freesp-tail.patch, the probe matrix inrecipes/rsync/README.md). The 040/060 rows (first-party record, 2026-10-30) ✅: the 68040/68060 port'sfcntl(F_FREESP)probe on an emulated A4000 with a 68060 and ufs loaded as a module, one panic case per boot with the console captured; the kernel read behind the rule (ufs_itrunc,pvn_vptrunc,kzero/bzero, compared with the stock 2.1c code) and the SVR4vm_pvn.cdesign note onkzero. The 040/060 fix (first-party record, 2026-10-30) ✅: the port'sISSUE-107inKNOWN-ISSUES.md(amigaux/amix-040-060-portk46-module:src/kzero_nofault.satb65b43d, the write-up atab53ccc) and its emulator rows: nine shrink cases on two boots, the fix's own fault, landing and foreign-landing counters (three faults, all at the first page past the old EOF, none foreign). The metal rows (first-party record, 2026-10-30) ✅: the same nine cases and counters on an A4000T with a Z3660 at 68060, plus acp/cmpand odd-sizeddsmoke test (recorded in the port'sISSUE-107,574cea1). The 68030 fix (first-party record, 2026-10-30) ✅:amigaux/amixkernel/k308b670ad(src/kzero_nofault.s, onmaster) and itsCHANGELOG.mdentry (13884fc): the §90 shrinks and thedd/cp/gtar --deletecases survived in emulation, data intact. - Quirk 91 (first-party record, 2026-10-30) ✅: the k30 kernel work — the port's two-phase shrink
probe (write N,
F_FREESPto M, check size and[0, M), write one byte at N−1, check[M, N−1)reads zero) on two 68030 kernels of the project's 68030 line, an emulated A4000 with the Z3660 in 68030 mode, ufs 8192/1024, one boot per cell; the cell outputs naming the first nonzero offset. The 040/060 rows (first-party record, 2026-10-30) ✅: the 68040/68060 port's run of the same probe on an emulated A4000 with a 68LC060 (4 KiB pages), its record kernel and itskzerotest kernel, eight cases with the stale extents measured; the rule was written down before the run. The port inferred the mechanism; the source reading below confirms it. The stock 2.1c rows, the extents, the on-disk proof and the cause (first-party record, 2026-10-30) ✅: the k30 kernel work's page-map probe on the 2.1c bench image, the project's 68030 root and a fresh ufs on a scratch disk (withsync, unmount/remount and delay variants); the disassembly of the stockufs_itrunc,pvn_vptruncandufs_putpageobjects (theufs_putpageearly return decoded as a pid 0/2 guard); the build-time hash of the 68030 line's ufs module against the stock tree. The 040/060 reboot rows (first-party record, 2026-10-30) ✅: the port's shrink, doublesync, clean shutdown, fresh emulator start and regrow, against a same-boot control. The cold-cache rows, the (a)/(b) elimination and the 68030 fix (first-party record, 2026-10-30) ✅: the k30 kernel work's cold-shrink cells on its stock-code kernel (16 of 16), a synchronous-release cell, and two two-boot cells of the fixed test kernel (68 + 68 clean runs,fsck -nclean);amigaux/amixkernel/k301be27f8(src/ufs_vptrunz.c, onmaster). The 040/060 fix (first-party record, 2026-10-30) ✅: the port'sISSUE-108inKNOWN-ISSUES.md(amigaux/amix-040-060-portk46-module:src/ufs_vptrunz.candtools/ufs-tailzero.shat3d0b348, the write-up at3c6160d) — 68 of 68 probe runs clean on its test kernel, 56 of 68 stale on the control. The exposure rows (first-party record, 2026-10-30) ✅: amix-packages probes of coreutils 5.2.1ddandcp, and GNU tar 1.14--delete, on the emulated 2.1c bench image, the console captured at each panic; the fixes in amix-packagesrecipes/common/amix-ftruncate.c(cad15e4), coreutils 5.2.1-3 (97bbcf1) and gtar 1.14-7 (cbd8c25), passing on the bench.