forked from arti/vt1200
docs: plan the LANCE Ethernet model + network font delivery
Evidence for the RAP/RDP window pair (0x5900000/0x5800000), INT1 as the LANCE interrupt, and the separate MAC address ROM at 0x6800000; MAME am79c90 drop-in assessment with the kn01/ncd68k templates; the TFTP/LAT font delivery path (no X font server on 7100); acceptance, risks and sequencing.
This commit is contained in:
1 parent
47dac6b283
commit
d017953d52
1 file changed
+546
@@ -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<am79c90_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 `<dir>/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 `<dir>/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<off> ...
|
||||
```
|
||||
|
||||
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).
|
||||
Reference in new issue
Block a user