Files
vt1200/dwt/notes/MESSAGEBOX.md
T

370 lines
15 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.
# The bottom diagnostics message box — window, text source, and the Session→Quit re-show
Status: **DONE**, with one honest emulator caveat (the MAME skip-level1 restart
does not bring the Terminal Manager back; the last step is static code + the
real unit's observed box, not an end-to-end MAME reproduction).
Address convention: **Ghidra = `0x07E40000 + ROM file offset`**, firmware **bit
address = Ghidra × 8**. The TMS34010 program space is bit-addressed, so every
firmware address below is a **bit** address unless it is explicitly a Ghidra
address.
---
## 1. Which window is the bottom box?
It is a **separate top-level X window owned by the Terminal Manager
(`SESSIONMGR_PROC`)**, not a child of the TM window and not a status bar drawn by
the window manager.
Creation code: `FUN_rom_07e94880` (Ghidra `0x07E94880`, bit `0x3F4A4400`).
```
07E9492A MOVE *A11(0x40),*A14+,0x1 ; parent = screen->root
07E9492E MOVE A7,*A14+,0x1 ; display
07E94930 CALLA 0x07FA8372 ; XCreateSimpleWindow(d, root, ...)
07E94936 MOVE A8,@0xB7C60 ; <-- box window id
07E9494E CALLA 0x07FB2FAA ; XSelectInput(d, win, 0x8000)
07E94976 CALLA 0x07FA70EE ; XChangeWindowAttributes(d, win, 0x200, attrs)
```
The window is mapped at the end of the builder, `0x07E949FA CALLA 0x07FAC9EC`
(`XMapWindow(d, @0xB7C60)`).
The parent really is the root window: `0x07E948AC` loads `@0xB78E0` (the TM's
`Display *`, see `GUI_APPS.md`), walks `display->screens[]` (`+0x480`, `+0x440`)
and takes `screen->root` (`+0x40`) before the `XCreateSimpleWindow` call.
Live on the real unit (`peek`, read-only, two samples 2 s apart):
```
peek 0xB7C60 4 -> 2d 00 10 00 = 0x0010002d
```
`0x0010002d` is an X resource id (client base `0x00100000`), i.e. a real server
window, not a sentinel. The screenshot
`dwt/evidence/real-unit-msgbox-allpassed.png` shows the separate wide box at the
bottom, with its own border and no title bar.
**Owner/process:** the TM's process entry is `0x07E5CDBC` (bit `0x3F2E6DE0`,
`SESSIONMGR_PROC`); the box id and all its state are in that process's globals.
## 2. Where the text comes from
### 2.1 The message table
The strings are in ROM in a table of **22 records, 20 bytes each**, at Ghidra
`0x07FE5552` (bit `0x3FF2AA90`):
```
record = 8 zero bytes + [English ptr][French ptr][German ptr] (20 bytes)
```
The pointer for record `r`, language `L` is at
`0x3FF2AA90 + r*0xA0 + 0x40 + L*0x20` (the `+0x40` skips the 8-byte header;
`0x20` bits = 4 bytes per language pointer).
| record | mask bit | English text |
|---|---|---|
| 0 | 7 | `NVR Checksum Error - 14` |
| 1 | 8 | `Video RAM Error - 22` |
| 2 | 9 | `MAIN Memory Error - 13` |
| 3 | 10 | `Host Port Error - 15` |
| 4 | 11 | `Printer Port Error - 16` |
| 5 | 12 | `Keyboard Port Error - 18` |
| 6 | 13 | `Mouse Port Error - 19` |
| 7 | 14 | `Keyboard Error - 27` |
| 8 | **15** | **`Ethernet Port Error - 24`** |
| 9 | 16 | `Mouse Error - 26` |
| 10 | 17 | `Video Error - 25` |
| 11 | 18 | `Ethernet ROM Error - 23` |
| 12 | 19 | `Timer 1 Error - 17` |
| 13 | 20 | `Timer 2 Error - 21` |
| 14 | 21 | `Optional RAM Simm 1 Error - 13` |
| 15 | 22 | `Optional RAM Simm 2 Error - 13` |
| 16 | 23 | `Optional RAM Simm 3 Error - 13` |
| 17 | — | `UNEXPECTED DIAGNOSTIC ERROR RECEIVED` (table entry; not selected by the box builder's bit branches) |
| 18 | mask == 0 | **`All Diagnostics Passed`** |
| 19 | bits 0–3 | `ROM Checksum Error - 12` |
| 20 | — | `Memory Path Error - xx` (table entry; not selected by the box builder) |
| 21 | bit 28 | `Image Board Error - 43` |
Record 18's English pointer is `0x3F4AB9A0` (Ghidra `0x07E95734`), the exact
string in the box. Record 8's is `0x3F4AB090` (Ghidra `0x07E95612`).
### 2.2 The selection code
The builder is `FUN_rom_07e94986` (bit `0x3F4A4C30`), called from the TM's
session-start path at `0x07E5E6BC`. It calls `FUN_rom_07e94a08`
(bit `0x3F4A5040`) to turn the status mask into a list of message pointers:
```
07E94A16 MOVE @0xCB640,A11,0x1 ; <-- STATUS MASK
07E94A1C JREQ 0x07E94E4C ; mask == 0 -> "All Diagnostics Passed"
...
07E94A3A MOVE @0x19FF10,A7,0x0 ; language index
07E94A40 SLL 0x5,A7 ; *32 bits = *4 bytes
07E94A4C ADDI 0x19E3D0,A7 ; record 0 + 8 (pointer field)
07E94A52 MOVE *A7(0x0),*A9(0x40),0x1 ; message ptr -> list entry +8
```
Destination list arithmetic (`0x07E94A28`–`0x07E94A32`):
```
SLL 0x7,A9 ; count * 128
SLL 0x5,A7 ; count * 32
ADD A7,A9 ; count * 160 bits = count * 20 bytes
ADDI 0x19F150,A9 ; list base
```
So the live list is a **20-byte record per error** at bit **`0x19F150`**, with
the string pointer at **entry + 0x40 bits (8 bytes)**; the builder appends a
null-pointer terminator. The "All passed" branch uses record 18's pointer
(`0x19EF10`); the error branches use record 0..16's pointers
(`0x19E3D0` … `0x19EDD0`), record 19 for bits 0–3 (`0x19EFB0`), and record 21
for bit 28 (`0x19F0F0`).
The table is also present as a **RAM copy**, byte-for-byte:
`0x19E390` = record header 0 (bit address), `0x19E3D0` = record 0's English
pointer. (Verified by comparing the dump at frame 3000 with ROM
`0x7FE5552`/`0x7FE555A`; the RAM copy begins at byte `0x33C72` in a RAM dump,
i.e. bit `0x19E390`.)
Live on the real unit:
```
peek 0x19F190 4 -> a0 b9 4a 3f = 0x3F4AB9A0
peek 0x19F230 4 -> 00 00 00 00 (null terminator)
peek 0x19E3D0 4 -> f0 aa 4a 3f = record 0 (NVR)
peek 0x19E8D0 4 -> 90 b0 4a 3f = record 8 (Ethernet Port - 24)
peek 0x19EF10 4 -> a0 b9 4a 3f = record 18 (All Passed)
peek 0x3F4AB9A0 24 -> b'All Diagnostics Passed\x00A'
```
### 2.3 The status variable and its writer
**Status variable: the 32-bit failure mask at bit `0xCB640`.**
Read by the box builder at `0x07E94A16`. Accumulated by the diagnostics test
result handler `FUN_rom_07e7bcf6`:
```
07E7BD7C MOVE @0xCB640,A11,0x1
07E7BD82 OR A11,A9 ; A9 = one result bit (1 << test number)
07E7BD84 MOVE A9,@0xCB640,0x1 ; <-- OR the failure bit into the mask
```
The same routine prints the corresponding table entry at
`0x07E7BDA8 ADDI 0x19E390,A11` (record base) and `0x07E7BDB2 CALLA printf`, so
the mask and the printed diagnostic report use the same table.
Cleared by the two wrappers:
```
07E7C212 SUB A11,A11
07E7C214 MOVE A11,@0xCB640,0x1 ; FUN_07e7c20a
...
07E7C276 MOVE A11,@0xCB640,0x1 ; FUN_07e7c26a
```
`FUN_07e7c20a` is called from the boot diagnostics block
`0x07EAF184 CALLA 0x07E7C20A`; `FUN_07e7c26a` from the trap/error path
`0x07EAF526 CALLA 0x07E7C26A`. No other instruction writes `0xCB640`.
Live on the real unit (two samples):
```
peek 0xCB640 4 -> 00 00 00 00 (0 -> All Diagnostics Passed)
```
## 3. Why Session→Quit re-shows the box
The box is **not a boot-only alert**. It is created in the TM's session-start
path and re-reads the persistent mask every time that path runs:
```
07E5E676 CALLA 0x07EE0D48 ; create_process(VTE, ...) -- start the session
07E5E6B6 CALLA 0x07FAC9EC ; XMapWindow(session window)
07E5E6BC CALLA 0x07E94986 ; <-- build list + create box window + draw + map
```
`0x07E5E6BC` is unconditional: every session start reaches it.
The Session/Quit action sets the TM quit flag `@0xB70E0`:
```
07E891D8 MOVE @0xAECDF,A11,0x0 ; confirmation required?
07E891DE JRNE 0x07E891EC
07E891E0 MOVK 0x1,A11
07E891E2 MOVE A11,@0xB70E0,0x1 ; <-- quit flag
```
(The confirmation path `FUN_07e895e2` sets the same flag at `0x07E895F8`.)
The TM event loop then exits:
```
07E5E6DC MOVE @0xB70E0,A11,0x1
07E5E6E2 JRNE 0x07E5F762
...
07E5F762 CALLA 0x07E5CD00 ; close display / sleep / restart
```
`0x07E5CD00` ends with `0x07E5CD90 CALLA 0x07EAF18A` — the kernel/OS restart
entry. That entry is **after** the diagnostics call, and the restart sets
`A10 = 1` so the boot block's `0x07EAF182 JRNE 0x07EAF198` skips
`0x07EAF184 CALLA 0x07E7C20A`:
```
07EAF180 MOVE A10,A10
07EAF182 JRNE 0x07EAF198 ; skip diagnostics on restart
07EAF184 CALLA 0x07E7C20A ; boot diagnostics (initial boot only)
...
07EAF192 MOVI 0x1,A10
```
Therefore Session→Quit **does not re-run the diagnostics check and does not
clear `@0xCB640`**. It restarts the OS, the restarted `SESSIONMGR_PROC` runs
the same session-start code, and `0x07E5E6BC` recreates the box from the mask
that was already in RAM. On the real unit the mask is `0` (`All Diagnostics
Passed`), so the same box comes back.
On a plain Expose the TM does not rebuild the list; it only repaints it
(`0x07E5FAB8` checks that the event window is `@0xB7C60`, then calls the draw
routine `0x07E94EB0`). That is why the box stays put across ordinary repaints,
but the *re-creation* after Quit is the session-start builder.
## 4. Live read format
The box text is **not** stored as ASCII in RAM. The readable state is:
| what | bit address | format |
|---|---|---|
| status mask | **`0xCB640`** | 32-bit little-endian; `0` = All Passed; bit 15 (`0x8000`) = Ethernet Port Error - 24; bit 8 = Video RAM Error - 22; … |
| message list | **`0x19F150`** | 20 bytes/entry; string pointer at entry **+0x40 bits (+8 bytes)**; entry *i* pointer at `0x19F150 + i*0xA0 + 0x40`; null pointer terminates |
| language | **`0x19FF10`** | 16-bit field; 0 = English (source: 2-bit field at `@0xAEC E8`, copied at `0x07E94990`–`0x07E94998`) |
| RAM table copy | **`0x19E390`** | 22 × 20-byte records, same layout as ROM `0x3FF2AA90` |
| box window id | **`0xB7C60`** | 32-bit X resource id |
Example live reads (`dwt/exploit.py`-style `peek`, read-only):
```
peek 0xCB640 4 -> 00 00 00 00 mask = 0
peek 0x19F190 4 -> a0 b9 4a 3f list[0] = 0x3F4AB9A0
peek 0x3F4AB9A0 24-> "All Diagnostics Passed"
peek 0xB7C60 4 -> 2d 00 10 00 window 0x0010002d
```
`0x19F190` is `0x19F150 + 0x40`; no count is stored globally — the builder
returns it in `A8`, and the draw routine walks the list until the null pointer.
## 5. What `DIAGNOSTIC MAILBOX` actually is
`DIAGNOSTIC MAILBOX` is a **debug dump of a separate diagnostics structure**,
not the bottom box's source.
* The format string is `0x3FAD4890` (Ghidra `0x07F5A912`), only referenced from
the dump routine `FUN_rom_07f5a026` (`0x07F5A026`).
* It reads the pointer **`@0x27C190`** and prints `@+0x00, +0x20, … +0x100` as
`test_type`, `test_mode`, `test_result`, `error_ext0..6`, `diag_hld`,
`memory_bss`, `test_cnt`, `test_f_cnt`, `memory_lth`, `video_dydx`,
`num_of_lnks`, and a memory-error link table.
* **No code ever arms it.** The only instruction that writes `0x27C190` writes
zero:
```
07F5A38E SUB A9,A9
07F5A394 MOVE A9,@0x27C190,0x1 ; clears the pointer
```
A separate routine (`0x07F59FC8`) fills a fixed buffer at `@0x27C1F0`, but the
dump pointer is never pointed at it. On the real unit:
```
peek 0x27C190 4 -> 00 00 00 00
```
So the mailbox is a dead/debug report dump; the bottom box never reads it. The
old candidate `0x16F0` (level-1 dispatcher slot) is likewise not referenced by
`0x07E94986`/`0x07E94A08`.
## 6. Evidence
### Real unit (read-only; 2026-10-18)
| check | value |
|---|---|
| `@0xCB640` | `0x00000000` (twice, 2 s apart) |
| `@0xB7C60` | `0x0010002d` (twice) |
| `@0x19F190` | `0x3F4AB9A0` (twice) |
| `@0x19F230` | `0` (sentinel) |
| `@0x19FF10` (16-bit) | `0` (English) |
| `@0x19E3D0` / `@0x19E8D0` / `@0x19EF10` | `0x3F4AAAF0` / `0x3F4AB090` / `0x3F4AB9A0` |
| `@0x27C190` | `0` |
| string at `0x3F4AB9A0` | `All Diagnostics Passed` |
| screenshot | `dwt/evidence/real-unit-msgbox-allpassed.png` |
### MAME (skip-level1, RAM poke only — ROM untouched)
* Breakpoint on the builder entry `0x3F4A4C30` fires at **frame 569** with
`d@0xCB640 = 0`.
* Write `@0xCB640 = 0x8000` at frame 568, continue to frame 1200:
`list[0] = 0x3F4AB090`, and the frame shows **`Ethernet Port Error - 24`**
(`dwt/evidence/mame-msgbox-ether24-frame1200.png`).
* Write `@0xCB640 = 0x1` -> `list[0] = 0x3F4ABB20` (`ROM Checksum Error - 12`).
* Write `@0xCB640 = 0x10000000` -> `list[0] = 0x3F4ABC98`
(`Image Board Error - 43`).
* Write the quit flag `@0xB70E0 = 1` at frame 1200: `0x3F578C50`
(`0x07EAF18A`, OS restart) is reached at frame 3054 with `@0xCB640` still
`0x8000`; the boot diagnostics breakpoint `0x3F3E1050` does **not** fire
again. (In the skip-level1 emulator the restarted X server comes back at the
root weave but the TM does not recreate its window within 8500 frames — see
caveat.)
* Control frame with mask 0: `dwt/evidence/mame-msgbox-allpassed-frame3000.png`.
## 7. Caveats
* The **MAME skip-level1 restart** reproduces the OS restart and the mask
persistence, but not the TM's return. The real unit does return to the TM with
the box (the owner's observation and
`dwt/evidence/real-unit-msgbox-allpassed.png`). The final rebuild step is
therefore static (`0x07E5E6BC` is unconditional in the session-start path) plus
the real-unit observation, not an end-to-end emulator trace.
* The exact "Session → Quit" menu item may be the `Abort X Session` item or the
`Quit Control` button; both are session-ending actions. The quit flag
`@0xB70E0` and the `0x07E5CD00 → 0x07EAF18A` restart are the path proven here.
A Quit that only closes the VTE window would leave the box window untouched and
the Expose redraw (`0x07E5FAB8 → 0x07E94EB0`) would repaint the same list; the
end result (same text from the same mask) is identical.
* The box can hold **several** messages: the builder appends one record per set
mask bit and terminates with a null pointer. The window height is derived from
the count in `0x07E94880`.
* `UNEXPECTED DIAGNOSTIC ERROR RECEIVED` and `Memory Path Error - xx` are table
entries but are not selected by the box builder's current bit branches; they
are used by the diagnostics reporter.
## 8. Reproduce the MAME checks
```sh
# box builder location / mask at build time
VT1200_SKIP_LEVEL1=1 BOX_FRAMES=600 \
/home/arti/code/k-space/mame0289/vt1200 vt1200 \
-rompath harness/mame/roms -video none -sound none -nothrottle -skip_gameinfo \
-cfg_directory .vt1200-scratch/msgbox/cfg \
-nvram_directory .vt1200-scratch/msgbox/nvram \
-debug -debugger none -debuglog \
-autoboot_script .vt1200-scratch/msgbox/probe_box.lua -autoboot_delay 0 \
-seconds_to_run 30
# grep BOXBUILD debug.log -> BOXBUILD fr=569 ... mask=00000000
# force a failing bit just before the build
MASK_VAL=0x8000 BOX_FRAMES=1250 VT1200_DUMP_FRAME=1200 \
VT1200_DUMP_DIR=$PWD/.vt1200-scratch/msgbox VT1200_SKIP_LEVEL1=1 \
/home/arti/code/k-space/mame0289/vt1200 vt1200 ... \
-autoboot_script .vt1200-scratch/msgbox/probe_box_mask.lua ...
# frame 1200 shows "Ethernet Port Error - 24"; RAM @0x19F190 = 0x3F4AB090
```
The probe scripts live in `.vt1200-scratch/msgbox/` (scratch, not source of
truth). The ROM is untouched: `md5 b46a3e442ccb86134d2356bd70d22fd2`.