diff --git a/dwt/evidence/mame-msgbox-allpassed-frame3000.png b/dwt/evidence/mame-msgbox-allpassed-frame3000.png new file mode 100644 index 0000000..ec9845c Binary files /dev/null and b/dwt/evidence/mame-msgbox-allpassed-frame3000.png differ diff --git a/dwt/evidence/mame-msgbox-ether24-frame1200.png b/dwt/evidence/mame-msgbox-ether24-frame1200.png new file mode 100644 index 0000000..83d4f6c Binary files /dev/null and b/dwt/evidence/mame-msgbox-ether24-frame1200.png differ diff --git a/dwt/evidence/real-unit-msgbox-allpassed.png b/dwt/evidence/real-unit-msgbox-allpassed.png new file mode 100644 index 0000000..efc6115 Binary files /dev/null and b/dwt/evidence/real-unit-msgbox-allpassed.png differ diff --git a/dwt/notes/MESSAGEBOX.md b/dwt/notes/MESSAGEBOX.md new file mode 100644 index 0000000..a89946e --- /dev/null +++ b/dwt/notes/MESSAGEBOX.md @@ -0,0 +1,369 @@ +# 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`. diff --git a/dwt/notes/REAL_UNIT.md b/dwt/notes/REAL_UNIT.md index 6b749c8..7b955cb 100644 --- a/dwt/notes/REAL_UNIT.md +++ b/dwt/notes/REAL_UNIT.md @@ -388,3 +388,25 @@ The build name must be <= 8 characters: `tftpc_real` silently fails (no Evidence: `dwt/loader/evidence/real-unit-xtext/` (loads, markers, server logs, screenshots, emulator runs, `xtext_probe.lua`). + +### Bottom diagnostics box: live state (2026-10-18, read-only) + +The separate bottom X window is owned by the Terminal Manager; its window id is +the global at bit `0xB7C60`. The text is selected from the status mask at bit +`0xCB640` and a pointer list at bit `0x19F150` (20-byte entries, string pointer +at +8 bytes). Live reads, two samples 2 s apart: + +``` +peek 0xCB640 4 -> 00 00 00 00 mask 0 -> "All Diagnostics Passed" +peek 0xB7C60 4 -> 2d 00 10 00 box window 0x0010002d +peek 0x19F190 4 -> a0 b9 4a 3f list[0] -> 0x3F4AB9A0 +peek 0x19F230 4 -> 00 00 00 00 null terminator +peek 0x19FF10 2 -> 00 00 language 0 (English) +peek 0x19E8D0 4 -> 90 b0 4a 3f record 8: "Ethernet Port Error - 24" +peek 0x27C190 4 -> 00 00 00 00 DIAGNOSTIC MAILBOX pointer is NULL +peek 0x3F4AB9A0 -> "All Diagnostics Passed" +``` + +The mask bit mapping (bit 15 = Ethernet Port Error - 24, bit 8 = Video RAM +Error - 22, ...) was verified dynamically in MAME by setting `@0xCB640` before +the builder at frame 569. Full trace: `dwt/notes/MESSAGEBOX.md`.