From baffb8292e74696544b89885b4854f6162c5c466 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Fri, 30 Oct 2026 17:10:45 +0200 Subject: [PATCH] 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. --- harness/mame/NETWORK_REPORT.md | 53 +++++++++++++++++++++++++ harness/udpstream/build/lance_cmd.coff | Bin 7289 -> 7421 bytes harness/udpstream/lance_cmd.c | 12 ++++++ harness/udpstream/vtcmd.py | 12 ++++-- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/harness/mame/NETWORK_REPORT.md b/harness/mame/NETWORK_REPORT.md index 130c789..f58b331 100644 --- a/harness/mame/NETWORK_REPORT.md +++ b/harness/mame/NETWORK_REPORT.md @@ -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. diff --git a/harness/udpstream/build/lance_cmd.coff b/harness/udpstream/build/lance_cmd.coff index 7d6b6f516f7470f346634c60a4b249cf0d16e061..e5aa1bc9010b158b289e061289b278771b17c3e8 100644 GIT binary patch delta 555 zcmYk3O)Nt}5XWbp`e=kERUxkq^x#0^B9V#&7ZOJ&ageu9yF@)2eJ{ePj!1=O-AE8| zkdWXVoKW@sRgIfFHwPynA?&ugOftJWznR_1f9J#h)f`;_2lU=Qg_c!-8UUz*JS@Ts zASoUQn*~TGV+asKl8Rx2MJKFGBFA_>6aOvM{MZoGEuHV;j&P9`J^oueKs@R!{4HT) zYFT`>fW4|kyE|JYxXAiSYjBE1OE;Bz2di_9<1t%vdC_JEeBH1gU2BT#5I6Q$73$vJ zYez`z+cWNUEPRHJ;?M7H*63MrLn6+jkMGu-7iVI+r)|z=Z=Q(#;PERsXct$^BQDuX zl{V(5lK_|ONG(@LiiOlpC7on@sxKS)*t^;kCD$<&QWVf4R1a!g-;E?eYUq<1kV{EM zbi)V-G;$${NevpXG;%9R|IldWol{B7aA277nv)z6f9Bv$y3TS)8;DRU+TTeMFv2EB qgBdiVO@4G7QX&9E`^q98Li_Akf9Yuwh>hBLhPZqr<5l zM!k~MiW0B{OpJly0+^2=6o5>nAVdmu4)Sycb9&Z@$UDkcq#Baq0F2OsPU^ zH#sme94Ot~DYTf8eKCV8_n-d(lZ{2@Zw?c+U}9XkxnI1Wk@4Z9zmsxf+OTi3nY0pyIUS-r!t9!8M+nHb`WQWI0+fu1@J7fDIYNvw=# za5w@N$xY13$xn`VI1CplPR&V8E{S(I0T)Ti%mbTq04`FTpPZcvRI?u@0&*h@5IcAt zgbBr`6{V(vtUn1CNzTbH1`0VGf{Wy27MG;v#XB5@ixih676GFS=&>ppB{fiJLwtiA QMNDA-rNSd^@*x>(0IQ9M-T(jq diff --git a/harness/udpstream/lance_cmd.c b/harness/udpstream/lance_cmd.c index 556ee69..0440443 100644 --- a/harness/udpstream/lance_cmd.c +++ b/harness/udpstream/lance_cmd.c @@ -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 diff --git a/harness/udpstream/vtcmd.py b/harness/udpstream/vtcmd.py index 8d8a4df..d0e59e7 100644 --- a/harness/udpstream/vtcmd.py +++ b/harness/udpstream/vtcmd.py @@ -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")