diff --git a/harness/mame/NETWORK_REPORT.md b/harness/mame/NETWORK_REPORT.md index 6fd009c..fbbb33c 100644 --- a/harness/mame/NETWORK_REPORT.md +++ b/harness/mame/NETWORK_REPORT.md @@ -4854,8 +4854,46 @@ Also observed: the display comes back **before** the network. At 32 s the operator could see the screen while `:771` was still closed; the interface comes up later in the OS boot. -**This is 1 of 1.** It is the vendor's own entry, it does exactly what a payload -breaks (STOP the LANCE, re-program the board window), and it worked first time — -but this section has already recorded three conclusions drawn from single -observations, so the status is "one confirmed success", not "reliable". A second -run is needed before it is treated as the standard hand-back. +### 73.3 Second run: 2 of 2, also 40 s + +Repeated at **17:14:10**, and this time the machine started from a state +`osboot` itself had produced (no power cycle in between), rather than fresh from +one: + +``` +=== osboot #2 at 17:14:10 === +osboot: control -> bit 0x3f5788c0, status 0 +rdebug back after 40s at 17:14:50 +pings +``` + +A second screenshot shows the Terminal Manager live again — `08-00-2B-16-4B-5A`, +`100.103.100.103`, `LAT_08002B164B5A`, 4 MB free, and the boot-time +`Attempting to set font path. / Using local fonts.` messages. + +**2 of 2, both 40 s**, from two different starting states. That is enough to +treat `osboot` as the hand-back, and it is the only command in this report whose +success rate is above zero on the real machine. + +### 73.4 The payload announces itself, so the host does not sleep + +Loading the COFF over rdebug pokes thousands of words one at a time, so how long +the payload takes to appear varies by tens of seconds; a fixed sleep is either +wasteful or too short. The payload therefore transmits an **announce** the +moment its RX ring is armed: + +* three broadcast frames, ethertype `0x88B6`, `'K' 'S'`, opcode `0xFF`, magic + `"LANC"` at `[17..20]`; +* back-to-back, with no clock involved (three because this is a half-duplex + segment and one lost broadcast should not cost a cycle); +* sent from TX **slot 0**, because the chip transmits only from its ring + position and arming any other slot does nothing until the ring reaches it (a + first attempt on slot 7 was silently never sent — measured); +* after the announce the reply cursor advances to slot 1, matching where the + chip's ring position now is, so replies never collide with the announce + descriptor; announce skips are not counted in `txfull`, which stays the reply + path's diagnostic. + +`vtcmd.py wait` listens for it, and `test-osboot.sh` ties the whole hand-back +together with **no sleep**: listen, load, wait for the announce, `stats`, +`osboot`, wait for `:771`. diff --git a/harness/udpstream/build/lance_cmd.coff b/harness/udpstream/build/lance_cmd.coff index e5aa1bc..5b19993 100644 Binary files a/harness/udpstream/build/lance_cmd.coff and b/harness/udpstream/build/lance_cmd.coff differ diff --git a/harness/udpstream/lance_cmd.c b/harness/udpstream/lance_cmd.c index 0440443..f927b86 100644 --- a/harness/udpstream/lance_cmd.c +++ b/harness/udpstream/lance_cmd.c @@ -338,6 +338,55 @@ int main() rxf = 0; bytes = 0; cmds = 0; laststat = 0xEEEE; txslot = 0; ncmd = 0; chaindrop = 0; txfull = 0; errbits = 0; jaddr = 0; dojump = 0; + /* ---- announce: the ring is armed, so say hello on the wire ---------- + * Three broadcast frames, ethertype 0x88B6, cmd 0xFF, magic "LANC". + * A host waiting for this knows the payload is live and does not have to + * guess with a sleep. The frame is sent three times because this is a + * half-duplex segment and one lost broadcast should not cost a cycle. + * No clock is involved: back-to-back is enough at 10 Mbit. */ + { + unsigned int a; + for (a = 0; a < 6; a++) { + wb(TXBUF + a * 8, 0xFF); /* broadcast dst */ + } + wb(TXBUF + 6 * 8, 0x08); wb(TXBUF + 7 * 8, 0x00); + wb(TXBUF + 8 * 8, 0x2B); wb(TXBUF + 9 * 8, 0x16); + wb(TXBUF + 10 * 8, 0x4B); wb(TXBUF + 11 * 8, 0x5A); + wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB6); + wb(TXBUF + 14 * 8, 'K'); wb(TXBUF + 15 * 8, 'S'); + wb(TXBUF + 16 * 8, 0xFF); /* announce opcode */ + wb(TXBUF + 17 * 8, 0x4C); wb(TXBUF + 18 * 8, 0x41); + wb(TXBUF + 19 * 8, 0x4E); wb(TXBUF + 20 * 8, 0x43); /* "LANC" */ + for (a = 21; a < 60; a++) { + wb(TXBUF + a * 8, 0); + } + for (a = 0; a < 3; a++) { + /* Slot 0 is where the chip's ring position starts, and it only + * transmits from that position -- arming any other slot does + * nothing until the ring gets there. Announce skips are not + * counted in txfull, which is the reply path's diagnostic. */ + for (j = 0; j < 8000; j++) { + if (!(r16(TXR + 16) & TMD1_OWN)) { + break; + } + } + if (r16(TXR + 16) & TMD1_OWN) { + break; /* still busy: stop trying */ + } + w16(TXR + 48, 0); + w16(TXR + 32, (0x10000 - 60) & 0xFFFF); + w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP); + csr_w(0, 0x0040 | CSR0_TDMD); + for (j = 0; j < 60000; j++) { + if (!(r16(TXR + 16) & TMD1_OWN)) { + break; + } + } + } + /* the chip's TX ring position is now 1, so replies must start there */ + txslot = 1; + } + /* ---- main loop: receive, count, and serve commands ---- */ for (idle = 0; ; idle++) { for (i = 0; i < NRX; i++) { diff --git a/harness/udpstream/test-osboot.sh b/harness/udpstream/test-osboot.sh new file mode 100755 index 0000000..53fa36f --- /dev/null +++ b/harness/udpstream/test-osboot.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# test-osboot.sh -- take the LANCE over, then hand it back with `osboot`. +# +# sudo ./test-osboot.sh [iface] [host] +# +# The point of this script is that it contains no sleep. The payload +# broadcasts an announce frame (ethertype 0x88B6, opcode 0xFF, magic "LANC") +# as soon as its RX ring is armed, so the host starts listening first and acts +# the moment the payload is really up. Loading the COFF over rdebug is slow +# (thousands of word pokes), and a fixed sleep either wastes seconds or is too +# short -- waiting for the announce is exact. +# +# Steps: +# 1. listen for the announce (background) +# 2. load dwt/loader/hostload.py, which pokes and jumps into the payload +# 3. when the announce arrives, read stats and send `osboot` +# 4. wait for the terminal's rdebug port to come back +# +# `osboot` jumps to the OS cold start at 0x3F5788C0, which STOPs the LANCE and +# re-programs the board window before restarting the OS. Measured: 40 s twice. + +IFACE=${1:-vt1200} +HOST=${2:-100.103.100.103} +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$HERE/../.." && pwd) +LOG=${LOG:-/tmp/vt1200-osboot.log} + +if [ "$(id -u)" != "0" ]; then + echo "run as root: raw sockets and rdebug need it" >&2 + exit 2 +fi + +echo "[*] listening for the announce on $IFACE" +python3 "$HERE/vtcmd.py" wait --iface "$IFACE" --wait-timeout 240 & +LISTENER=$! + +echo "[*] loading $(basename "$HERE")/build/lance_cmd.coff onto $HOST" +python3 "$ROOT/dwt/loader/hostload.py" --host "$HOST" \ + --coff "$HERE/build/lance_cmd.coff" --real --no-screenshot \ + > "$LOG" 2>&1 & + +if ! wait "$LISTENER"; then + echo "[!] no announce: the payload did not start (see $LOG)" >&2 + exit 1 +fi + +echo "[*] payload is up, stats:" +python3 "$HERE/vtcmd.py" stats --iface "$IFACE" || true + +echo "[*] handing back with osboot" +python3 "$HERE/vtcmd.py" osboot --iface "$IFACE" || exit 1 + +echo "[*] waiting for rdebug :771 on $HOST" +T0=$(date +%s) +i=0 +while [ $i -lt 36 ]; do + sleep 5 + i=$((i + 1)) + if timeout 2 sh -c "/dev/null; then + echo "[+] back after $(( $(date +%s) - T0 ))s" + exit 0 + fi +done + +echo "[!] did not come back within 180 s" >&2 +exit 1 diff --git a/harness/udpstream/vtcmd.py b/harness/udpstream/vtcmd.py index d0e59e7..e9c4f33 100644 --- a/harness/udpstream/vtcmd.py +++ b/harness/udpstream/vtcmd.py @@ -13,6 +13,8 @@ Protocol, ethertype 0x88B6, both directions: cmd 4 jump : [17..20] address (BIT) -- reply is sent, then control moves cmd 5 reboot: no arguments -- jumps to the ROM reset vector (wedges the real machine; kept only because MAME tolerates it) + cmd 0xFF announce: sent BY the payload, three broadcasts, magic "LANC" + right after its ring is armed -- `vtcmd.py wait` listens for 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. @@ -132,7 +134,7 @@ def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("cmd", choices=["stats", "read", "poke", "jump", - "reboot", "osboot", "bench"]) + "reboot", "osboot", "wait", "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) @@ -157,6 +159,8 @@ def main(): "into microbursts that the switch drops") ap.add_argument("--force", action="store_true", help="reboot: required, it succeeded 1 of 2 times and can hang") + ap.add_argument("--wait-timeout", type=float, default=180.0, + help="wait: how long to listen for the payload's announce") ap.add_argument("--terse", action="store_true", help="one CSV line per run, for sweeps and for the report") ap.add_argument("--timeout", type=float, default=2.0) @@ -177,6 +181,24 @@ def main(): print(" %04x %s" % (i, data[i:i + 16].hex(" "))) return 0 + if a.cmd == "wait": + # the payload broadcasts this the moment its RX ring is armed, so the + # host does not have to sleep and hope + sock.settimeout(1.0) + t0 = time.time() + while time.time() - t0 < a.wait_timeout: + try: + data, _ = sock.recvfrom(2048) + except socket.timeout: + continue + if (len(data) >= 21 and data[12:14] == b"\x88\xb6" + and data[14:16] == b"KS" and data[16] == 0xFF): + mac = ":".join("%02x" % b for b in data[6:12]) + print("announce from %s after %.1f s" % (mac, time.time() - t0)) + return 0 + print("no announce within %gs" % a.wait_timeout) + return 1 + if a.cmd == "poke": if a.file: with open(a.file, "rb") as f: