forked from arti/vt1200
235 lines
10 KiB
Markdown
235 lines
10 KiB
Markdown
# 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
|
||
```
|