Files
vt1200/harness/mame/FINDINGS.md
T

235 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VT1200 execution harness — results
Companion to `README.md` in this directory. This records what was built, the
exact commands, the evidence, and what was learned about the two font questions.
## 1. What was set up
A minimal MAME machine for the VT1200 firmware, built as a single-driver
subtarget of MAME 0.289:
| File | Purpose |
|---|---|
| `harness/mame/vt1200.cpp` | the driver (TMS34010 + RAM + ROM) |
| `harness/mame/build.sh` | build wrapper (`-j8`, registers the driver) |
| `harness/mame/trace.lua` | headless run: trace execution from the reset vector |
| `harness/mame/overflow.lua` | headless run: exercise `pcfReadBitmaps` `n*4` |
| `harness/mame/roms/vt1200/` | MAME ROM dir (symlink to `vt1200_v22.bin`) |
MAME source lives outside the project (disposable) at
`/home/arti/code/k-space/mame0289` (shallow clone of tag `mame0289`).
The driver is deliberately incomplete: only the CPU, 4 MiB of RAM at bit
address 0, and the ROM are modelled. LANCE Ethernet, the DUARTs and the board
peripherals are **not** modelled, so the terminal never boots to the X server.
That is fine: the goal is execution visibility, and reset + breakpoints +
single-step + trace are enough.
### Address model (the thing that had to be right)
`memory_access<32,16,3>` ⇒ the address-map unit is **1 bit**. The ROM is mapped
at bit `0x3f200000` (its byte base `0x07e40000 × 8`), and the reset-vector page
at logical bit `0xffffffe0` is covered by mirroring the ROM into the top
logical quadrant `0xff200000`–`0xffffffff`. The reset vector
(`0x3f200040`) lands on ROM file offset `0x1bfffc`.
## 2. Build and run
```sh
# build (memory-hungry; defaults to -j8)
cd /home/arti/code/k-space/vt1200
harness/mame/build.sh # -> /home/arti/code/k-space/mame0289/vt1200
# run: trace from the reset vector
M=/home/arti/code/k-space/mame0289/vt1200
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
-debug -debugger none -autoboot_script harness/mame/trace.lua -autoboot_delay 0
# run: pcfReadBitmaps n*4 overflow (OVF_N optional, decimal)
$M vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \
-debug -debugger none -debuglog -seconds_to_run 5 \
-autoboot_script harness/mame/overflow.lua -autoboot_delay 0
```
Notes on MAME headless debugging: `-debugscript` does **not** work without a
debugger UI (the console only processes scripts while the debugger UI drives
the loop). The reliable headless route is the Lua debugger API
(`manager.machine.debugger`, `device.debug`, `emu.register_frame_done`) driven
from `-autoboot_script`.
## 3. Evidence it executes the firmware
`trace.lua` prints the reset PC and writes an instruction trace of the first
two frames (`harness/mame/vt1200-reset.trace.gz`, 650 815 lines; a head excerpt
is in `reset-trace.head.txt`):
```
[space] program shift=3 data_width=16
PC at harness start = 3F200040 (expect 3F200040)
after 2 frames: PC=3FDF99F0
```
First instructions of the trace (bit addresses; byte = /8):
```
3F200040: DINT
3F200050: SETF 10h,0,0
3F200060: SETF 20h,0,1
3F200070: CLR A0
3F200080: MOVE A0,@64000D0h,0 ; board peripheral
3F2000B0: MOVI FFh,A0
3F2000D0: MOVB A0,@64000F0h
3F200100: MOVI Fh,A0
3F200120: MOVB A0,@64000E0h
3F200150: CLR A0
3F200160: MOVE A0,@62000D0h,0 ; board peripheral
3F200190: MOVI 4h,A0
3F2001B0: MOVE A0,@62000F0h,0
3F2001E0: CLR A8
3F2001F0: MOVI 1h,A0
3F200220: MOVB A0,@64000F0h
3F200250: CLR A0
3F200260: MOVE A0,@C00000B0h,0 ; CONTROL
3F200290: MOVE A0,@C0000080h,0 ; DPYCTL
3F2002C0: MOVE A0,@C0000100h,0 ; HSTCTLH
...
```
This matches the known power-on code (`DINT; SETF 16,0,0; SETF 0,0,1; CLR A0 …`)
and the peripheral/CONTROL writes documented in `MEMORY_MAP.md`. The CPU then
runs several hundred thousand instructions of POST and ends the second frame at
`0x3fdf99f0` (`PIXBLT B,XY`).
### In-emulator `pcfReadBitmaps` overflow
`overflow.lua` jumps straight into the arithmetic block and stops after the
shift. Measured results (register `a11` is the argument to `fosAlloc`):
| n (input) | `n*4` / fosAlloc arg | note |
|---|---|---|
| `0x00001000` | `0x00004000` | control, no wrap |
| `0x10000001` | `0x40000004` | wraps |
| `0x40000000` | `0x00000000` | `fosAlloc(0)`, then 4 GiB of writes |
| `0x40000001` | `0x00000004` | `fosAlloc(4)`, then 4 GiB of writes |
```
RESULT n=40000001 a11_fosAlloc_arg=00000004 nChars=40000001
```
## 4. Findings
### 4.1 `pcfReadBitmaps` arithmetic and `num_chars*4` (confirmed)
The firmware's `pcfReadBitmaps` is at bit `0x3f984e70` (byte `0x0f09ce`).
Disassembly (bit addresses) of the relevant block:
```
3f984e70: move *a9(0x270),a11,1 ; a11 = pCS->nChars
3f984e90: jrne L3F984EE0 ; nChars != 0 -> just cross-check n
3f984ea0: move *a13(0x20),*a9(0x270),1 ; nChars == 0 -> nChars = n
...
3f985030: move *a13(0x20),a11,1 ; a11 = n (pcfReadInt32 result)
3f985050: sll 0x2,a11 ; a11 = n*4 <-- NO overflow check
3f985090: calla L3F703D90 ; fosAlloc(n*4, 1)
3f9850c0: move a8,*a13(0x40),1 ; pCS->pBitOffsets = result
...
3f985260: (loop i = 0 .. n-1) ; writes n CARD32 entries
3f985290: move a10,a7 ; a7 = i
3f9852a0: sll 0x5,a7 ; i*32 bits = i*4 bytes
3f9852d0: add a11,a7 ; dest = offsets + i*4
3f985310: calla L3F96EEE0 ; read 4 bytes from file into dest
```
There is **no bound check on `n`** between the read and the multiply, exactly
matching X11R4 `reference/x11/r4/contrib/server/pcf/fonts/lib/pcf/pcfrbitmaps.c`
(`offsets = fosAlloc(n*sizeof(CARD32)); for (i=0;i<n;i++) offsets[i]=…`).
`n = 0x40000001` makes `n*4` wrap to 4 while the loop writes ~4 GiB.
One important detail verified against MAME's core: the font code uses the
TMS34010 **field-1** moves (`…,1`), and `SETF 0,0,1` leaves field 1 at 32
bits. So `move *a13(0x20),a11,1` really is a 32-bit load — `n` is the full
32-bit `pcfReadInt32`, not a 16-bit value. The emulator run above confirms it.
### 4.2 The ~1 KB TFTP font limit
The failure seen on the wire (`ACK block 1`, `ACK block 2`, then TFTP
`ERROR 3`/ENOSPACE) is the firmware's own `tftp_read` giving up:
```
3f5eed30: move *a13(0xffa0),a11,1 ; a11 = bufsize / remaining capacity
3f5eed50: cmp a11,a9 ; a9 = current DATA payload length
3f5eed60: jrle L3F5EEED0 ; payload <= capacity -> copy it
...
3f5eeda0: movi 0x3f5f0c98,a8 ; "tftp_read: bufsize (%d) too small for %d bytes\n"
3f5eee50: movk 3,a9 ; TFTP error code 3 (ENOSPACE)
3f5eee90: calla L3F5EF610 ; send error packet
```
So ENOSPACE is not a disk error: it is `tftp_read` rejecting a datagram
because the destination buffer's remaining space is smaller than the payload.
The observed 1024-byte cutoff (works ≤ 2 blocks, fails on the 3rd) implies the
font receive buffer is ~1 KiB. `tftp_read` itself uses a 0x204-byte (516 =
512+4) datagram buffer (`movi 0x204` at `3f5ed5e0`), so the ~1 KiB limit is in
the *file accumulation* buffer, not the packet buffer. Pinning the exact size
needs a breakpoint on `3f5eed30` while a live transfer runs, or a small
additional harness.
## 5. Q1 — why the small synthetic font is rejected
Not yet answered with certainty. What is established:
* The firmware contains a full X11R4-era PCF reader. The dispatcher
(`pcfReadFont`) is around bit `0x3f98bxxx`; it prints
`Failed to read PCF format properties/accelerators/encodings/metrics/bitmaps`
when a table reader returns failure, and the per-reader strings
(`unknown bitmap format 0x%x`, `bad count in pcfReadBitmaps (expected %d, got %d)`,
`bad packed/unpacked metrics, glyph=…`, `pcfReadBitmaps called with bitmaps loaded`, …)
all match the R4 sources.
* The `fonts.dir` path also has a name check:
`font name different: name in path file='%s'` / `name in font file='%s'`
(bit `0x3f990538`), a strong candidate for "name listed" but "OpenFont
unmatched".
* A freshly generated `baseline.pcf` has all nine tables and version
`0x70636601`, so the version/TOC checks should pass; the failure is one of
the per-table or name checks.
**Why it isn't answered yet:** invoking the parser requires the `fos` file
abstraction (`fosRead` at `0x3f96eee0`, `fosAlloc` at `0x3f703d90`,
`fosFilePtr` at `*a13(0xffe0)`), which the boot path sets up only after the
network/TFTP fetch. The harness can run the CPU, but reaching `pcfReadFont`
means either booting far enough (needs LANCE/DUART models — out of reach) or a
small purpose-built call stub.
## 6. Blockers and remaining effort
* **Q1 needs a call harness.** Cheapest next step: give the driver a writable
overlay at the `fosRead`/`fosAlloc` addresses, have Lua write a tiny
TMS34010 stub there that serves bytes from a RAM copy of `baseline.pcf`, then
set PC to the PCF loader with a crafted `fosFilePtr`. That lets us single-step
the exact bail path. Effort: moderate (hand-encoding a handful of TMS34010
instructions, or assembling with `as34`).
* **The ~1 KB limit** can be pinned by breaking at `0x3f5eed30` during a real
transfer, or by the same call harness feeding oversized data.
* **Full boot** (X server, network) needs LANCE + DUART + video models; that is
a large, separate project and was explicitly out of scope.
* The driver is a visibility tool, not an accurate VT1200: the CRT timing,
palette and peripherals are stubs.
## 7. Tree layout (new files)
```
harness/mame/
vt1200.cpp driver (source of truth; symlinked into MAME)
build.sh build wrapper (JOBS=8 default)
trace.lua reset-vector trace
overflow.lua pcfReadBitmaps n*4 harness
reset.dasm disassembly of the reset vector window
reset-trace.head.txt first 60 traced instructions
vt1200-reset.trace.gz full 650k-instruction trace
roms/vt1200/vt1200_v22.bin -> ../../vt1200_v22.bin
harness/dis/
fontregion-v22.txt dis34 of the font code
tftp-v22.txt dis34 of the TFTP client
full-v22.txt.gz dis34 of the whole ROM
```