udpstream: osboot -- jump to the OS cold start (0x3F5788C0), the path that quietens the LANCE

The reset vector is the wrong entry: it starts POST, which re-programs the
board window from a state a payload has already changed.  The firmware has a
second entry, 0x3F5788C0, where the boot gate at 0x3F2042D0 branches when it
skips level 1.  It begins with DINT, CSR0=STOP on the LANCE, and a board-window
re-program -- exactly what a takeover breaks.

MAME, from the LANCE access log: the payload's last access and the OS's first
are 130 us apart, then pc=3F578940 writes CSR0=0x0004 (STOP) and pc=3FDDCAE0
(the OS driver) re-initialises the chip, after which the OS keeps servicing it.

cmd 6 / vtcmd.py osboot.  Unit run pending; the report section is 73.
This commit is contained in:
arti committed 2026-10-30 17:10:45 +02:00
1 parent 504d63a705
commit baffb8292e
4 files changed
+73 -4

No files matched your search

+53
View File
@@ -4777,3 +4777,56 @@ the screen blank while it reprograms the video window.
If a faster return than that is ever needed, the alternative is not a reboot but
a hand-back of the chip while the OS keeps running (section 64): the display and
the OS survive, the network does not.
## 73. The real reboot target: the OS cold-start entry, not the reset vector
Section 72.4 established that the firmware has no reboot routine and that
jumping at the reset vector wedges the machine (0 for 2). But the reset vector
is the *wrong* entry: it is the start of POST, which reprograms the board window
from a state a payload has already changed. The firmware's own boot has a second
entry, the **OS cold start at `0x3F5788C0`** — the address the boot gate at
`0x3F2042D0` branches to when it skips the level-1 batch (`vt1200.cpp:134`).
That entry starts by fixing exactly what a payload breaks
(`rom:0x07eaf118`, bit `0x3F5788C0`):
```
07eaf118 DINT
07eaf11a CLR A10
07eaf11c SETF 0x0,0x0,0x1
07eaf11e SETF 0x10,0x0,0x0
07eaf120 CLR A0
07eaf122 MOVE A0,@0x5900000 ; LANCE RAP = 0
07eaf128 MOVI 0x4,A0
07eaf12c MOVE A0,@0x5800000 ; CSR0 = 0x0004 = STOP <- quiet the chip
07eaf132 MOVI 0xC,A0
07eaf136 MOVE A0,@0xc00000b0 ; board window = 0x0C
07eaf140 MOVI 0x3f57aa70,A2 ; then re-program the board tables
07eaf146 MOVI 0xc0000080,A3
07eaf14c MOVE *A2+,*A3+,0x0 ; (3 words)
07eaf156 MOVI 0x3f57aaa0,A2 ; and the next table to 0xc0000130
```
`DINT`, `CSR0=STOP`, board window — in that order. This is the path that is
designed to run when the machine is handed over, and it is *fast*: no POST.
`lance_cmd.c` exposes it as command 6, `osboot`.
### 73.1 MAME: the OS re-initialises the chip
`VT1200_LANCE_DMA_LOG`/`VT1200_LANCELOG` shows the handoff and the vendor's own
re-init, with the payload's last access and the OS's first access 130 µs apart:
```
t=11.823527 W RDP val=0048 pc=018C0E40 <- last payload access
t=11.823657 R RDP val=02F3 pc=3F9D5BC0 <- OS LANCE handler
t=11.828933 W RDP val=0004 pc=3F578940 <- CSR0 = STOP, inside 0x3F5788C0
t=11.828960 W RDP val=0004 pc=3FDDCAE0 <- OS driver (io5800_5900_init)
```
and the OS keeps servicing the chip afterwards (`pc=3F9D*`, 134 accesses in the
window). So the cold start restores the LANCE and the OS continues.
### 73.2 Real unit
Result recorded in 73.3 once the unit run completes.
Binary file not shown.
+12
View File
@@ -445,6 +445,18 @@ int main()
}
wb(TXBUF + 19 * 8, (wlen >> 8) & 0xFF);
wb(TXBUF + 20 * 8, wlen & 0xFF);
} else if (cmd == 6) {
/* OS cold start: the entry POST branches to at 0x3F2042D0
* when it skips the level-1 batch. Unlike the reset
* vector this path first STOPs the LANCE and re-programs
* the board window, which is exactly the state a payload
* leaves behind -- see NETWORK_REPORT 72.5. */
jaddr = 0x3F5788C0;
dojump = 1;
wb(TXBUF + 19 * 8, (jaddr >> 24) & 0xFF);
wb(TXBUF + 20 * 8, (jaddr >> 16) & 0xFF);
wb(TXBUF + 21 * 8, (jaddr >> 8) & 0xFF);
wb(TXBUF + 22 * 8, jaddr & 0xFF);
} else if (cmd == 4 || cmd == 5) {
/* jump (4) to an address, or reboot (5): jump to the ROM
* reset vector, which is the same entry the CPU takes at
+8 -4
View File
@@ -11,7 +11,11 @@ Protocol, ethertype 0x88B6, both directions:
cmd 2 stats: no arguments
cmd 3 poke : [17..20] address (BIT), [21..22] len, [23..] the bytes
cmd 4 jump : [17..20] address (BIT) -- reply is sent, then control moves
cmd 5 reboot: no arguments -- jumps to the ROM reset vector
cmd 5 reboot: no arguments -- jumps to the ROM reset vector (wedges the
real machine; kept only because MAME tolerates it)
cmd 6 osboot: no arguments -- jumps to the OS cold-start entry 0x3F5788C0,
the path POST takes when it skips level 1. That path STOPs
the LANCE and re-programs the board window first.
reply : [16] cmd echoed, [17..18] status, [19..] data
Every address in this protocol is a BIT address, the way the TMS34010 sees
@@ -128,7 +132,7 @@ def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("cmd", choices=["stats", "read", "poke", "jump",
"reboot", "bench"])
"reboot", "osboot", "bench"])
ap.add_argument("--iface", default="vt1200")
ap.add_argument("--addr", type=lambda v: int(v, 0), default=0)
ap.add_argument("--len", type=int, default=32)
@@ -191,7 +195,7 @@ def main():
% (len(blob), a.addr, a.addr >> 3, status))
return 0
if a.cmd in ("jump", "reboot"):
if a.cmd in ("jump", "reboot", "osboot"):
if a.cmd == "reboot" and not a.force:
print("refusing reboot without --force.")
print(" It has NEVER worked on the real VT1200: 0 of 2 attempts.")
@@ -202,7 +206,7 @@ def main():
return 2
if a.cmd == "reboot":
print("rebooting: it may take ~90 s and it may never come back.")
cmd = 4 if a.cmd == "jump" else 5
cmd = 4 if a.cmd == "jump" else (5 if a.cmd == "reboot" else 6)
status, data = request(sock, a.iface, cmd, a.addr, 0, a.timeout)
if status is None:
print("no reply")