diff --git a/docs/superpowers/plans/2026-10-16-lance-ethernet.md b/docs/superpowers/plans/2026-10-16-lance-ethernet.md new file mode 100644 index 0000000..b12a8a0 --- /dev/null +++ b/docs/superpowers/plans/2026-10-16-lance-ethernet.md @@ -0,0 +1,546 @@ +# Plan: model the LANCE Ethernet controller, and deliver a font over the network + +Date: 2026-10-16 +Status: **planned, not started.** This runs **after** the level-0 puzzle and the +keyboard/mouse input models land. Read-only investigation only; nothing in +`harness/mame/`, the MAME tree, `ghidra/` or the ROMs was changed to produce it. + +Address conventions (four, reconciled — same as `reference/level1-bypass-analysis.md`): + +| name | formula | example | +|---|---|---| +| firmware **bit** address (what `hostport_dis34.txt` prints) | `0x3F200000 + file_offset*8` | `0x5800000` | +| physical **byte** address | `bit >> 3` | `0x00B00000` | +| driver / board-model address ("harness unit") | `bit >> 4` | `0x05800000` | +| a handler at `base` receives | `(bitaddr - base) >> 4` | offset `0x0` at the base | + +Throughout I quote the **bit** address and give byte equivalents. "PC" values are +TMS34010 `PC` (bit addresses). ROM byte-identical (`md5 +b46a3e442ccb86134d2356bd70d22fd2`). + +--- + +## 0. Verdict (short) + +* **INT1 is the LANCE interrupt; `0x5900000`/`0x5800000` is its register + interface.** They are **RAP / RDP** (register-address port / register-data + port) of an Am7990/Am79C90-class LANCE — *not* a generic board DMA latch. + Evidence in §1.1–1.2. This **corrects** `reference/memory-space-map.md` rows 5–6 + ("DMA/board data/command port") and `vt1200_board.h`'s "DMA pair" naming. +* The **address ROM is a separate device** at physical byte `0xD00000` (bit + `0x6800000`), not a region of the firmware image: the known MAC + `08:00:2b:16:4b:5a` does **not** appear anywhere in `vt1200_v22.bin`. +* MAME has **`am79c90` / `am7990`** (`src/devices/machine/am79c90.cpp`). It is a + **near drop-in for the register protocol** (its `csr0` STOP semantics satisfy + the firmware's level-0 check by construction, and its buffer-length encoding + matches the OS driver byte-for-byte). It is **bus-master**, so the work is in + the **DMA callbacks, the IRQ line and the address ROM**, not in the chip. +* The terminal gets fonts over **TFTP (its own font path format) or DEC's LAT + font daemon** — **not** an X Font Server on TCP 7100 (refuted in §3.1). + The smallest host-side server is the project's existing TFTP server; the + emulator additionally needs a host-side network peer (`tap` backend + bridge; + this build has **no pcap and no slirp**, §2.3). +* **Difficulty: 1–2 weeks part-time**, one engineer. The chip is easy; the two + real costs are the **bus-master DMA address translation** and the + **host-peer plumbing** (TAP bridge + BOOTP). Biggest single risk: the board + window that *fronts* the LANCE is otherwise still undecoded, and the OS's + LANCE driver may touch board registers beyond RAP/RDP (§5). + +--- + +## 1. Evidence — the firmware's side + +### 1.1 The interface is RAP/RDP (Am7990 register file) + +The firmware writes a **register number** to `0x5900000` and then reads/writes +the selected **register** at `0x5800000`. Every access is 16-bit (`move …,0`). +The command values and data are, in order, exactly the LANCE's CSR0–CSR3 access +protocol. Decisive site, `io5800_5900_init` `L3FDDCBE0` (bit `0x3FDDCBE0`): + +``` +3fddcc20: movi 0x5900000, a2 ; a2 = RAP +3fddcc50: movi 0x5800000, a3 ; a3 = RDP +3fddcc80: movk 1, a0 +3fddcc90: move a0, *a2, 0 ; RAP = 1 (CSR1) +3fddcca0: move *a14(0xffe0), a1, 1 ; caller's argument = a BIT address +3fddccc0: srl 0x3, a1 ; >>3 -> BYTE address (the device is byte-addressed) +3fddccd0: move a1, *a3, 0 ; CSR1 = address low +3fddcce0: movk 2, a0 +3fddccf0: move a0, *a2, 0 ; RAP = 2 (CSR2) +3fddcd00: srl 0x10, a1 +3fddcd10: move a1, *a3, 0 ; CSR2 = address high +3fddcd20: movk 3, a0 +3fddcd30: move a0, *a2, 0 ; RAP = 3 (CSR3 / bus-master config) +3fddcd40: movi 0x0, a1 +3fddcd60: move a1, *a3, 0 ; CSR3 = 0 +3fddcd70: clr a0 +3fddcd80: move a0, *a2, 0 ; RAP = 0 (CSR0) +3fddcd90: movi 0x7f41, a1 +3fddcdb0: move a1, *a3, 0 ; CSR0 = 0x7f41 = INIT|INEA|IDON|TINT|RINT|MERR|MISS|CERR|BABL +``` + +That is the textbook LANCE "program IADR then issue INIT" sequence: +CSR1/CSR2/CSR3 hold the address of the 24-byte initialization block; CSR0 bit 0 +(`INIT`) starts initialization. `0x7f41` sets `INIT` (bit 0) plus the standard +interrupt-enable/clear mask. + +The other driver routines confirm the same decode: + +| routine | bit addr | sequence | meaning | +|---|---|---|---| +| `lance_stop` | `L3FDDCA90` | `RAP=0; RDP=0x0004` | CSR0 = STOP | +| `lance_start` | `L3FDDCB30` | `RAP=0; RDP=0x0042` | CSR0 = STRT\|INEA | +| `lance_read_csr0` | `0x3FDDCE10` | `RAP=0; read RDP` | read CSR0 | +| `lance_int_ack` | `L3FDDCEA0` | `RAP=0; read RDP`; OR into `@0x8fc00`; `RDP = status & 0x7ff0` | read-and-clear CSR0 interrupt/error bits | + +The **level-0** and **level-1** self-tests use the same two addresses: + +* Level-0 "Ethernet port (LANCE) not reset on power-up test" — + `0x3F201320`: `RAP=0` (`3f201340`), read CSR0 (`3f201370`), + `btst 2` (`STOP`, `3f2013a0`); if clear → error path `L3F207A60`, which sets + `a10 = 0x33` (the LED code for **error 33**) **and** writes `4` (CSR0 STOP) + back to `0x5800000` (`3f207a80`). Service-guide string + `LEVEL0 ERROR -> LANCE NOT STOPPED - 33` (`0x3FDF4CB0`). + **A reset LANCE has CSR0 = 0x0004, so bit 2 is set → the test passes.** +* Level-1 "Ethernet port (LANCE) test" — `0x3F202830`: `RAP=1` + (`3f202830`), then 15 iterations write `0`/`a7` to RDP (CSR1) and read back, + accumulating mismatches (`3f2028d0`…`3f202a00`). A register read/write + path test — no packet loopback. + +OS cold-start runs `lance_stop` immediately (`0x3F578900`: `RAP=0`; +`0x3F578960`: `CSR0=4`), then the OS LANCE driver init lives at +`0x3F9D2880`–`0x3F9D46D0` (board log: `W 05900000 … val=0000/0001/…`, +`R 05800000 … val=0001`, per `OS_BRINGUP_REPORT.md` §4.2). It builds the init +block and **two descriptor rings**, and reads the MAC ROM (below). + +**Verdict:** `0x5900000` = RAP, `0x5800000` = RDP. The `>>3` on a bit address +before programming CSR1/2 proves the device sees **byte** addresses. + +### 1.2 The interrupt is INT1, and the LANCE's ISR reads `0x5800000` + +* TMS34010 external interrupts are **active-low** on `LINT1`/`LINT2`. + `INT1` vector = `0xFFFFFFC0`, `INT2` vector = `0xFFFFFFA0` + (`docs/TMS34010-users-guide.txt` Table 8-2). `INTENB` bit 1 = `X1E` + (enable INT1), bit 2 = `X2E` (enable INT2); `INTPEND` bit 1 = `X1P`, + bit 2 = `X2P` (guide §6, register descriptions). +* The ROM vector page holds `RLONG(0xFFFFFFC0) = 0x00000F60` and + `RLONG(0xFFFFFFA0) = 0x00000F30` — **RAM stubs** (bit `0x0F60` / `0x0F30`) + into which the firmware writes `JA` instructions at runtime (`MEMORY_MAP.md` + §"Reset / interrupt vector page"; `DUART_REPORT.md` §1). +* The **LANCE ISR** is `0x3FA93400`: + ``` + 3fa93400: move a8, -*sp, 1 + 3fa93410: calla L3FDDCEA0 ; <-- the LANCE read-CSR0 + interrupt-ack + 3fa93440: move *sp+, a8, 1 + 3fa93450: reti + ``` + `L3FDDCEA0` **reads `@0x5800000`** (`3fddcf10`). This is the lead confirmed: + the handler on one of the two external-interrupt vectors reads the RDP. +* The LANCE driver **installs** `0x3FA93400` and then enables **INT1 only**: + `0x3FA8C340` – `0x3FA8C470` calls `L3FA90230(handler=0x3FA93400, …)` and then + `L3FDF9420(2)`. `L3FDF9420` is `INTENB |= mask` (`3fdf94b0` loads + `0xC0000110`), so `2` = **bit 1 = `X1E` = enable INT1**. +* Contrast: the DUART1/board-window setup (`0x3FA84C70`) installs its handlers + with `L3FDF9420(6)` = `INT1|INT2`, and later OS-phase DUART1 handlers + `0x3FA934C0` / `0x3FA935D0` (window `0x6400000`) are installed on the two + slots in a mode-dependent pair. The exact slot bookkeeping is the subject of + the **preceding** "interrupt-line" work; what this plan needs is the fact in + the bullet above — the LANCE's own handler is installed and INT1 is enabled. +* The level-0/OS dispatcher likewise branches on `INTPEND` bit 1 as the + external-interrupt-1 source (`0x3F2026C0: btst 1,a2 / jane L3F207D80`). + +One reconciliation note for the implementer: `DUART_REPORT.md` §1 concluded +"DUART1 is expected on INT1" because the *POST* handler stub +`0x3FA92F40` (INT1) also dispatches to window `0x6400000`. That report itself +records that `L3FA78D90` ignores its mask argument, and `0x3FA93070` (the INT2 +stub) dispatches to the **same** window. So the DUART is happy on either line, +while the LANCE is **not**: its `lance_int_ack` emits its IRQ and its sole +handler is installed on the INT1 slot. Route **DUART1 → INT2, LANCE → INT1** +(the "interrupt-line question" work that precedes this plan). + +### 1.3 The address ROM is a separate device, holding the MAC + +The firmware reads exactly **six bytes** from bit `0x6800000` (physical byte +`0xD00000`), one MAC byte per 16-bit step (`movb *a7(0),dst; addk 16,a7`): + +* Level-1 **"Ethernet address ROM test"** (error 23): `0x3F230890`– + `0x3F2309D0` — `movi 0x6800000,a11`, read 6 bytes into a local struct. + Strings: `Ethernet ROM Error - 23` (`0x3F4AB2F8` / `0x3FAD60F8`). +* POST LANCE init: `0x3FDD74B0`, `0x3FDD7C30`/`0x3FDD7CE0`. +* OS: `0x3F57C6B0`, `0x3F57DE00`, and the OS LANCE init `0x3F9D2920` + (reads 6 bytes into `@0x23cca0`, then at `0x3F9D2B60` copies them into the + **init block's PADR** at `@0x23e780 + 16 bits`). + +**The MAC is `08:00:2b:16:4b:5a`** (DEC OUI `08:00:2b`), from the live unit +(`NEXT_SESSION.md` §6; service guide shows the same OUI on other units). +A byte search of `vt1200_v22.bin` (and v2.1) finds **no** occurrence of those +six bytes → the ROM is a **physical device in the memory map**, not firmware +data. So MAME must supply it. + +Open sub-question (not needed to start): the guide calls the test "format and +checksum", but the firmware reads only the 6 MAC bytes from this window; no +second read or sum over `0x6800000` exists in the image. Resolve empirically — +run T12 and, if it fails, reverse the check. **Model the ROM as +`mac[i]` at handler offset `i` (6 read-only bytes).** + +--- + +## 2. MAME's side + +### 2.1 The device exists and is well-suited + +`src/devices/machine/am79c90.{cpp,h}` define `AM7990` (`am7990`) and `AM79C90` +(`am79c90`), class `am7990_device_base`. Driver-facing surface: + +```c++ +u16 regs_r(address_space &space, offs_t offset); // offset 0 = RDP; !=0 = RAP +void regs_w(offs_t offset, u16 data); +auto intr_out(); // devcb_write_line — /INTR, ACTIVE-LOW (0 = asserted) +auto dma_in(); // devcb_read16 — bus-master read: offset is a BYTE address +auto dma_out(); // devcb_write16 — bus-master write +void reset_w(int state); // reset input +``` + +* **Bus master:** yes. It reads the 24-byte init block with single-word DMA and + then reads/writes packet descriptors and buffers through `dma_in`/`dma_out`. + `initialize()` computes `init_addr = ((csr2<<16)|csr1) & 0xfffffe` — a **byte** + address — and reads 12 words. `get_buf_length()` implements the standard + LANCE buffer-length encoding. +* It already includes `dinetwork.h` (`device_network_interface`), so it plugs + into MAME's `-netdev` provider chain and gets a real peer (§2.3). +* **The address PROM is not an input to the device.** `initialize()` takes the + MAC from the init block's `PADR` (bytes 2–7) and calls `set_mac()`. So the + address ROM is just a small read-only region the **CPU** reads and copies into + PADR — exactly what the firmware does (§1.3). + +Two ready templates: + +* `src/mame/dec/kn01.cpp` — **a DEC machine with `am79c90`**: + ```c++ + AM79C90(config, m_lance, XTAL(12'500'000)); + m_lance->intr_out().set_inputline(m_cpu, INPUT_LINE_IRQ1).invert(); + m_lance->dma_in().set([this](offs_t o){ return m_nram[(o & 0xffff) >> 1]; }); + m_lance->dma_out().set([this](offs_t o, u16 d, u16 m){ COMBINE_DATA(&m_nram[(o & 0xffff) >> 1]); }); + map(0x18000000, 0x18000007).rw(m_lance, FUNC(am79c90_device::regs_r), + FUNC(am79c90_device::regs_w)).umask32(0x0000ffff); + ``` +* `src/mame/ncd/ncd68k.cpp` — an **NCD X terminal** with LANCE + SCN2681 + VRAM + (the closest whole-machine analogue), showing explicit `lance_dma_r/w`. + +`FULL_BOOT_ESTIMATE.md` §3 already names both. + +### 2.2 Concrete wiring sketch (for the implementer — not done here) + +Add `#include "machine/am79c90.h"`, a `required_device m_lance`, +and — keeping the board model for every other offset — map **offset 0 only** of +the two windows to the chip: + +```c++ +// RDP (data) at bit 0x5800000; RAP (register select) at bit 0x5900000. +map(VT1200_WIN0, VT1200_WIN0 + 0x000f).rw(FUNC(vt1200_state::lance_rdp_r), + FUNC(vt1200_state::lance_rdp_w)); +map(VT1200_WIN1, VT1200_WIN1 + 0x000f).rw(FUNC(vt1200_state::lance_rap_r), + FUNC(vt1200_state::lance_rap_w)); +// RDP handlers call m_lance->regs_r(space, 0) / regs_w(0, data) +// RAP handlers call m_lance->regs_r(space, 1) / regs_w(1, data) +``` + +* **DMA:** the LANCE address is a **byte** address; main RAM is bit-addressed. + Map byte `B` → Main-RAM word `B >> 1`: + ```c++ + m_lance->dma_in().set([this](offs_t o){ return m_mainram[o >> 1]; }); + m_lance->dma_out().set([this](offs_t o, u16 d, u16 m){ COMBINE_DATA(&m_mainram[o >> 1]); }); + ``` + (or go through `m_maincpu->space(AS_PROGRAM)` if DMA must reach outside RAM; + the init block and both descriptor rings are in RAM). **Verify the unit**: + the whole driver already uses the convention `handler offset = (bitaddr-base)>>4` + (each 16-bit word occupies 16 address units), so a physical byte `B` is at + CPU bit address `B*8`. +* **IRQ:** `m_lance->intr_out().set_inputline(m_maincpu, 0).invert();` + (line 0 = INT1; `.invert()` because `/INTR` is active-low and the TMS34010 + input is asserted by `ASSERT_LINE`, cf. the DUART wiring at + `vt1200.cpp:645`). Gate it to the OS phase if a POST-phase INT1 conflict with + the DUART appears — the LANCE is never `INEA`-enabled during POST, so it + should be idle anyway. +* **Address ROM:** map bit `0x6800000` (byte `0xD00000`) as a 6-byte read-only + handler returning `{08,00,2b,16,4b,5a}` at handler offsets 0–5 (the firmware + reads bytes at 2-byte stride, so handler offset `i` = MAC byte `i`). + Where the board model currently serves this window, override it. + Long-term a `rom` region + `set_rom` is cleaner than an inline lambda. + +### 2.3 Network backends in *this* build + +* **Compiled into `/home/arti/code/k-space/mame0289/vt1200`: `none` and `tap` + (taptun) only.** Verified by symbol inspection: `netdev_none` and + `netdev_tap` are present; **`netdev_pcap` is absent**. The source tree has + `pcap.cpp` and `taptun.cpp` (`src/osd/modules/netdev/`), and `NETDEV_PCAP` + exists as a name string, but pcap was not built. +* **No slirp / NAT / user-mode network** exists in this build (or in the + snapshot's source tree). +* To attach a **host-side peer** (the font server): + 1. build/run with the **`tap`** provider (`-netdev tap -netdev_interface …`), + create a persistent TAP interface, and **bridge** it to the host; or + 2. **rebuild MAME with `OSD_NET_USE_PCAP`** and attach to a real NIC/bridge. + Then point the host TFTP/LAT server at the bridge address. +* For the level-0/level-1 tests and for "the OS brings the interface up", + **`-netdev none` is sufficient** — no host peer is needed. + +**Drop-in verdict: *yes, with caveats.*** The register protocol matches exactly +and MAME's reset state (`CSR0 = STOP = 0x0004`) makes the level-0 test pass by +construction. The caveats are: (a) the two registers live in two windows +`0x20000` bytes apart, so the map is two entries, not one 4-word block; +(b) the **DMA callbacks and their byte→bit translation** are new code; +(c) the **IRQ polarity/routing** must be got right; (d) the **address ROM** is a +new small region; (e) the parts of the fronting board window beyond offset 0 +are still the board model. + +--- + +## 3. The delivery path — how a font actually reaches the terminal + +### 3.1 Mechanism (refutes "X font server on TCP 7100") + +The VT1200 supports **two font paths**, selectable by a radio in the Terminal +Manager (`docs/EK-V1000-SV-002_service-guide.txt` lines 2555–2700; +`docs/DTJ-1991-vol3num4_X-Window-Terminals.txt` lines 545–600): + +| path | syntax | transport | who serves | +|---|---|---|---| +| **LAT Font Path** | `\|ROM,BETA::` | DEC **LAT** (ethertype `0x6004`) | DEC's **font daemon** on the host | +| **TFTP Font Path** | `\|ROM,123.45.67.89:/vt1200/fonts.dir` | **TFTP** (UDP 69) | any TFTP server | + +* The LAT path is DEC's own **font daemon**; the DTJ article calls it "a + precursor to a general font server, and it is very similar to the X Font + Server" — i.e. *similar in spirit*, **not** the X Font Server protocol. + There is **no TCP 7100 / `fs` protocol** in the firmware (ROM strings show + `Font server is %s, font path is %s`, `LAT Font Path`, `TFTP Font Path`, + `/fonts.dir`; no 7100 usage). **The XFS hypothesis is refuted.** +* The TFTP path file is the index (`fonts.dir` / `font.paths`). The terminal + **TFTP-fetches `/fonts.dir`**, then fetches each font file named inside. + This is the mechanism already confirmed live in `EXPLOIT_NOTES.md` + ("Working delivery recipe (confirmed)"): RRQ `"69"`, RRQ `"69/fonts.dir"`, + then RRQ `"baseline.pcf"` etc. + +### 3.2 Smallest host-side server + +Nothing new is needed for the **TFTP** route — the project already has it: + +* `harness/serve.py` — the working TFTP font server (serves `fonts.dir` + the + PCF/SNF set from `reference/fonts/`), and +* `tools/fontgen.py` (`TFTPServer`, `mutate`, `pcf_tables`, `set_num_chars`) — + builds the crafted PCF (`num_chars = 0x40000001` → `n*4` wrap → heap + overflow, the confirmed primitive). + +For a **live** terminal this is already end-to-end (see `EXPLOIT_NOTES.md`); +the LANCE work does not change it. It matters here because it is the thing the +LANCE must *let through* in the emulator. + +### 3.3 For the emulated terminal + +`-netdev none` gives the emulated LANCE no peer, so the OS's LANCE init runs but +no font is ever requested. The honest minimal route to "a font arrives in the +emulator" is: + +1. LANCE modelled (§2.2) and the OS interface up (§4). +2. A host peer via **`tap` + bridge** (or pcap rebuild). +3. The terminal needs an **IP**. The emulator loads default NVRAM settings, so + the IP is `0.0.0.0`; the OS then prints + `Could not determine IP address after 5 retries / TCP/IP is now down.` The + firmware has BOOTP (`bootp_proto`, `do_bootp`), so boot the OS either with a + **minimal BOOTP responder** on the bridge (not yet written; ~a few dozen + lines) **or** set the IP through the Terminal Manager UI — which is exactly + what the preceding **keyboard/mouse model** work enables. +4. Serve `fonts.dir` + the crafted PCF with `harness/serve.py` / `fontgen`, + pointed at the Terminal Manager's TFTP Font Path. +5. Watch the parse: breakpoint the confirmed sites `pcfReadBitmaps` + `0x0F09CE`, `pcfReadFont`, `fosAlloc` `0x3F703D90` (already done by + `overflow.lua`). This is the payoff the LANCE unlocks. + +--- + +## 4. Acceptance criteria + +Each criterion is observable and testable. + +**A. Register protocol (unit / early boot).** +1. With the LANCE modelled, the **level-0 "LANCE not stopped" test passes**: + no `LEVEL0 ERROR -> LANCE NOT STOPPED - 33`, and the error handler at + `0x3F207A60` is never entered. +2. The **level-1 "Ethernet port (LANCE) test"** (`0x3F202830`) and the + **"Ethernet address ROM test"** (`0x3F230890`) both pass — no + `Ethernet Port Error - 24` (`0x3F4AB090`) or `Ethernet ROM Error - 23` + (`0x3F4AB2F8`). Because level 1 wedges on the earlier host-port test today + (`reference/level1-bypass-analysis.md` §4), invoke these tests through the + diagnostics **test map** (the `l1patch.lua` runtime-patch mechanism, or the + NCP `T 9` / `T 12` menu) rather than a full level-1 run. + *Acceptable evidence:* a run that reaches `L3F202B60` (LANCE pass branch) and + the ROM-test pass branch, with the error strings absent. + +**B. OS interface up.** +3. The OS LANCE driver completes INIT: `CSR0` reads back with `IDON` set, + `lance_start` is issued, and the machine **does not** print + `Could not determine IP address after 5 retries / TCP/IP is now down.` for + the LANCE reason (a missing *address* is still allowed). Observable: a + breakpoint on `L3FDDCEA0` / the OS init at `0x3F9D2880`, plus the absence of + the LANCE error banner. +4. The Terminal Manager reports the real MAC (`08-00-2B-16-4B-5A`) instead of + `Ethernet Address 00-00-00-00-00-00`. Observable: frame dump of the + Terminal Manager (`OS_BRINGUP_REPORT.md` §5 shows the field). + +**C. Font end to end.** +5. With a host peer on the bridge and a TFTP Font Path set, the emulator's + terminal issues an RRQ for `/fonts.dir` and then the named PCF, and the + parser is entered. Observable on **both** sides: the `harness/serve.py` log + shows the RRQs, and the MAME debugger breaks at `pcfReadBitmaps` + (`0x0F09CE`) with `num_chars` from our file. This is the acceptance that the + whole exercise exists to reach. + +**D. No regressions.** `-netdev none`, no-LANCE runs remain byte-identical to +today's baseline (the promoted-default discipline of +`2026-10-16-env-gate-cleanup.md`), and the DUART/level-0 paths are unchanged. + +--- + +## 5. Scope, risks and unknowns (honest) + +**Realistic size: 1–2 weeks part-time** for one engineer who already knows this +driver. Breakdown: LANCE register window + IRQ + address ROM ≈ 1–2 days (the +`kn01.cpp` pattern); DMA callback + byte→bit translation + first INIT observed +≈ 1–3 days (this is where surprises live); host-peer TAP bridge + BOOTP + TFTP +end-to-end ≈ 2–4 days. This is far smaller than the 6–12-week "full boot" +estimate in `FULL_BOOT_ESTIMATE.md`, because that estimate included decoding the +undecoded `0x6200000`/`0x6400000` ASIC and the whole video path, which are now +mostly done. + +**What could make it fail to be a drop-in:** + +1. **The fronting board window.** `0x5800000`/`0x5900000` are the LANCE's RAP/RDP, + but the same windows (and `0x5600000`, `0x6000000`) are still partly + unexplained board logic. If the OS LANCE driver touches board registers + *within those windows* beyond offset 0 (chip select, reset, interrupt + routing, a DMA controller), the board model will not satisfy them. **This is + the single biggest risk.** Mitigation: keep the board model for all other + offsets (the sketch in §2.2 does) and log every access the OS makes. +2. **DMA address translation.** The LANCE's byte addresses must be mapped to the + TMS34010's bit-addressed space exactly (`<<3`), and descriptor rings use the + masked-address semantics (`RING_ADDR_MASK = 0xfffff8`). Getting the unit + wrong produces silent garbage rather than a clean failure. Mitigation: cross- + check against the POST init's known ring addresses (`0x7fb00`, `0x7fbc0`, + `0x87bc0` bit → `0xFF60`, `0xFF78`, `0x10F78` byte) and the blitter's RAM use. +3. **LANCE clock unknown.** The reference clock feeds interrupt/poll timing + only; `kn01` uses 12.5 MHz, `ncd` 10 MHz. Any value should work for the + register tests and INIT; note it if transmit pacing matters. +4. **Address-ROM checksum.** The guide says "format and checksum"; the firmware + visibly reads only 6 bytes. If T12 still fails, the format (or a checksum + check we have not located) must be reversed before criterion A.2 is met. + Low cost, but unknown until tried. +5. **Host-peer plumbing is a host-configuration problem, not a code problem.** + This build has **no pcap and no slirp**, so the emulated LAN needs a TAP + + bridge (or a MAME rebuild). `tap` needs privileges; the bridge must be up + before MAME starts. +6. **No slirp means no user-mode NAT**, so "just make TFTP work out of the box" + is not available; the bridge is mandatory for criterion C. + +**Known but out of scope here:** the custom interrupt controller inside the +`0x6200000`/`0x6400000` ASIC (the LANCE IRQ must reach INT1 through whatever the +board does, but at the TMS34010 level the harness wires the device straight to +`INT1`, which is what the OS expects); full OS/IP configuration; the LAT font +daemon (the existing `lat/` + `latd` cover the LAT side). + +**Open, stated plainly:** +* Whether the OS's LANCE driver needs board registers in the `0x5800000` window + beyond RDP — resolve by logging the OS run. +* The exact address-ROM format/checksum. +* The LANCE's real clock. +* Whether `INT2` fully substitutes for `INT1` for DUART1 during POST (the + preceding interrupt-line work settles this; the LANCE requires it). + +--- + +## 6. Sequencing + +1. **Prerequisites (land first):** the level-0 puzzle; the keyboard/mouse input + models; the interrupt-line work that settles **DUART1 → INT2** and frees + **INT1** for the LANCE. +2. **This plan:** LANCE register window + IRQ + address ROM → DMA → OS + interface up → host peer (TAP/BOOTP/TFTP) → font parse in-emulator. +3. Only then revisit the live-terminal LAT/announcement crash work + (`EXPLOIT_NOTES.md`, `NEXT_SESSION.md` §5) with the offline reproducer. + +--- + +## 7. Executable task list + +Each step has a check; do not proceed on a failed check. + +1. **Map the two registers.** Add the LANCE device and the WIN0/WIN1 offset-0 + maps (§2.2), keeping the board model elsewhere. + *Check:* run with `VT1200_BOARD_LOG`; the level-0 test's `R 05800000 pc=3F201370` + now returns `0x0004` (bit 2 set) and `3F207A60` is not reached. +2. **Address ROM.** Map `0x6800000` with `08 00 2b 16 4b 5a` at handler + offsets 0–5. + *Check:* the ROM read at `0x3F2308F0` yields those six bytes; run the ROM test + via the test map and confirm no `Ethernet ROM Error - 23` (reverse the check + first if it still fails — risk 4). +3. **Level-1 LANCE test.** Verify the register read/write path. + *Check:* `0x3F202830`–`0x3F202A00` accumulates zero and branches to `L3F202B60`. +4. **DMA callbacks.** Wire `dma_in`/`dma_out` to main RAM with the byte→bit + translation; verify against the POST init's ring addresses. + *Check:* breakpoint `initialize()`; the 12 init-block words read back are the + ones the firmware wrote; the PADR equals the MAC. +5. **IRQ.** Wire `intr_out` to INT1 (`.invert()`), with DUART1 on INT2. + *Check:* an asserted LANCE IRQ is serviced by `0x3FA93400`, which reads + `0x5800000`; `INTENB` bit 1 is set (`L3FDF9420(2)`). +6. **OS interface up.** Boot the skip-level-1 path; confirm INIT/IDON/start. + *Check:* criterion B.3 and B.4. +7. **Host peer.** Add the `tap` provider path (bridge) or rebuild with pcap; + add a minimal BOOTP responder (or set the IP via the keyboard/mouse UI). + *Check:* `ping` from the host to the emulated terminal, or a host capture + showing the terminal's ARP/BOOTP. +8. **Font end to end.** Serve `fonts.dir` + a crafted PCF; breakpoint + `pcfReadBitmaps`. + *Check:* criterion C.5. +9. **Baseline discipline.** Re-baseline the no-LANCE run and document the diff. + +Expected driver observability knobs to add (keep in the instrumentation style of +`vt1200.cpp`): a LANCE access log, and a dump hook around `initialize()`. + +--- + +## 8. Reproduce this investigation + +```sh +cd /home/arti/code/k-space/vt1200 + +# interface / IRQ / address-ROM evidence (bit addresses): +grep -n "0x5800000\|0x5900000\|0x6800000" harness/mame/hostport_dis34.txt +sed -n '195,240p;400,445p;1180,1215p;556630,556720p' harness/mame/hostport_dis34.txt +sed -n '391160,391200p' harness/mame/hostport_dis34.txt # LANCE ISR 0x3FA93400 + +# MAME device + templates: +sed -n '535,720p' /home/arti/code/k-space/mame0289/src/devices/machine/am79c90.cpp +grep -n "AM79C90\|regs_r\|dma_in\|intr_out" \ + /home/arti/code/k-space/mame0289/src/mame/dec/kn01.cpp \ + /home/arti/code/k-space/mame0289/src/mame/ncd/ncd68k.cpp + +# network backends actually compiled into this build: +strings -n 5 /home/arti/code/k-space/mame0289/vt1200 | grep -E "netdev_(none|tap|pcap)" + +# the MAC is not in the ROM: +python3 - <<'EOF' +d=open("vt1200_v22.bin","rb").read() +print(d.find(bytes.fromhex("08002b164b5a"))) # -> -1 +EOF + +# split instructions (256 KiB ROM banks): +tools/smart34010/dis34 vt1200_v22.bin 0x ... +``` + +References: `reference/memory-space-map.md` (rows 5–6, 10 — **corrected here**), +`reference/level1-bypass-analysis.md` (address conventions, level-1 gating), +`reference/level1-test-sequence.md` (test list, error codes), +`harness/mame/{OS_BRINGUP_REPORT,DUART_REPORT,FULL_BOOT_ESTIMATE}.md`, +`EXPLOIT_NOTES.md` (font delivery recipe, the heap primitive), +`NEXT_SESSION.md` §5–6 (font path gotchas, MAC), `lat/notes/lat-receive-audit.md` +(LAT path), `docs/EK-V1000-SV-002_service-guide.txt` (levels, P3, font paths), +`docs/DTJ-1991-vol3num4_X-Window-Terminals.txt` (font daemon vs TFTP).