From 1339b43a3e7a6f2dca999723723c1fc6fb20bef4 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 13 Nov 2026 09:22:44 +0000 Subject: [PATCH] fix(loader): exit on completion and add LOADER_END=0 --- dwt/loader/README.md | 42 +++++++++++++++- dwt/loader/loader.lua | 111 +++++++++++++++++++++++++++++++++--------- 2 files changed, 128 insertions(+), 25 deletions(-) diff --git a/dwt/loader/README.md b/dwt/loader/README.md index 331828c..ba2b32a 100644 --- a/dwt/loader/README.md +++ b/dwt/loader/README.md @@ -56,5 +56,45 @@ The script pokes the loader at `0x01880000` and the COFF at `LOADER_BUF` network, the rdebug service or a firmware transport. Knobs: `LOADER_MODE=1` (callable/return instead of debug-resume), -`LOADER_A8_DESC=1` (descriptor in `A8`), `LOADER_SNAP=1` (PNG at `LOADER_END`), +`LOADER_A8_DESC=1` (descriptor in `A8`), `LOADER_SNAP=1` (PNG at the exit frame), `LOADER_VERBOSE=1`, `LOADER_TRACE=1`. + +### Environment + +| variable | default | meaning | +|---|---|---| +| `LOADER_AT` | `2500` | frame at which the loader and COFF are poked and `PC` is set | +| `LOADER_END` | `2700` | fixed inspection/exit frame; **`0` = run until completion** | +| `LOADER_MAX` | `6000` | `LOADER_END=0` only: give up (with a warning) at this frame | +| `LOADER_BIN` | `dwt/loader/build/loader.bin` | loader payload poked at bit `0x01880000` | +| `LOADER_COFF` | `dwt/loader/build/marker.coff` | app COFF poked at `LOADER_BUF` | +| `LOADER_BUF` | `0x01900000` | COFF buffer bit address | +| `LOADER_OUT` | `dwt/loader/evidence` | directory for the optional PNG | +| `LOADER_SNAP` | `0` | `1` writes `loader_frame.png` at the exit frame | + +### Completion and `LOADER_END=0` + +The harness detects completion when all three hold: + +1. the loader has written `RESULT_STATUS` (it is `0xFFFFFFFF` until then), +2. `PC` is outside `[LOADER_BASE, LOADER_BASE+0x400)`, and +3. the result block has been unchanged for 10 frames. + +On detection it prints one line and sets `done` once: + +``` +LOADER: complete at frame 2510 status=00000000 +``` + +With a **fixed** `LOADER_END` the run still exits at that frame, so the snapshot +filename and every existing fixed-frame capture are unchanged; the completion +line is informational and suppresses the startup warning. With **`LOADER_END=0`** +the run exits as soon as completion is detected, and if it is not detected by +`LOADER_MAX` it prints + +``` +LOADER: warning: app still in startup at LOADER_MAX=6000 +``` + +and exits there. With a fixed `LOADER_END` that is reached first, the warning is +`LOADER: warning: app still in startup at LOADER_END=`. diff --git a/dwt/loader/loader.lua b/dwt/loader/loader.lua index fe87ab4..57e8173 100644 --- a/dwt/loader/loader.lua +++ b/dwt/loader/loader.lua @@ -17,9 +17,10 @@ -- LOADER_COFF app COFF (default dwt/loader/build/marker.coff) -- LOADER_BUF COFF buffer bit address (default 0x01900000) -- LOADER_AT injection frame (default 2500) --- LOADER_END inspection/exit frame (default 2700) +-- LOADER_END inspection/exit frame (default 2700; 0 = wait for completion) +-- LOADER_MAX LOADER_END=0 only: give up after this many frames (default 6000) -- LOADER_OUT evidence directory (default dwt/loader/evidence) --- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0) +-- LOADER_SNAP 1 = write a PNG at the exit frame (default 0) -- LOADER_VERBOSE 1 = print the result block every 25 frames local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end @@ -37,6 +38,9 @@ end local AT = tonumber(os.getenv("LOADER_AT") or "2500") local ENDF = tonumber(os.getenv("LOADER_END") or "2700") +local MAXF = tonumber(os.getenv("LOADER_MAX") or "6000") +local WAIT = ENDF == 0 +local STABLE = 10 local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin" local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff" local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000") @@ -177,6 +181,42 @@ local function result_dump() r32(RESULT + 0x140), r32(RESULT + 0x160))) end +-- The loader writes RESULT_STATUS last (it is 0xFFFFFFFF until then). The +-- whole result block is the completion signal: status written, PC out of the +-- loader's entry page, and the block unchanged for STABLE frames. This keeps +-- a write that is still in progress from looking final. +local RESULT_CELLS = { 0x000, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0C0, + 0x0E0, 0x100, 0x120, 0x140, 0x160, 0x180 } + +local function result_signature() + local t = {} + for i, off in ipairs(RESULT_CELLS) do + t[i] = string.format("%08X", r32(RESULT + off)) + end + return table.concat(t, " ") +end + +local stable_signature = nil +local stable_frames = 0 + +local function complete() + local st = r32(RESULT + 0x000) + local pc = cpu.state['PC'].value + if st == 0xFFFFFFFF or (pc >= LOADER_BASE and pc < LOADER_BASE + 0x400) then + stable_signature = nil + stable_frames = 0 + return false + end + local sig = result_signature() + if sig == stable_signature then + stable_frames = stable_frames + 1 + else + stable_signature = sig + stable_frames = 1 + end + return stable_frames >= STABLE +end + local function inject() -- 1. loader itself local n, bad = 0, 0 @@ -273,6 +313,26 @@ local function inspect() print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value))) end +local finished = false +local function finish() + if finished then return end + finished = true + if injected then + inspect() + if SNAP then + local screen = nil + for _, s in pairs(machine.screens) do screen = s break end + if screen then + local out = OUT .. "/loader_frame" .. frames .. ".png" + local ok, err = pcall(function() return screen:snapshot(out) end) + print(string.format("LOADER: snapshot %s ok=%s %s", + out, tostring(ok), tostring(err or ""))) + end + end + end + machine:exit() +end + emu.register_frame_done(function() frames = frames + 1 if frames == AT and not injected then @@ -283,20 +343,23 @@ emu.register_frame_done(function() dbg:command("trace off") print("LOADER: trace written to /tmp/opencode/ld.tr") end - if injected and not done then + if injected then if TRACE and frames <= AT + 40 then print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s", frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value), hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)), hex(r32(RESULT + 0x020)))) end - local st = r32(RESULT + 0x000) - -- status 0 means OK; nonzero is final too once parsing has begun - if st ~= 0xFFFFFFFF and not done then - -- The loader writes 0 on success. A nonzero status is final - -- only after it has been written once (it is 0 from power-on, - -- so require the loader to have run: PC no longer at entry or - -- the app counter moved). + -- Status 0 means OK; any nonzero status the loader wrote is final too. + -- done is set exactly once, on the frame completion is detected. + if not done and complete() then + done = true + print(string.format("LOADER: complete at frame %d status=%s", + frames, hex(r32(RESULT + 0x000)))) + if WAIT then + finish() + return + end end if VERBOSE and frames % 25 == 0 then result_dump() @@ -312,22 +375,22 @@ emu.register_frame_done(function() end end end - if frames >= ENDF then - if injected then - done = true - inspect() - if SNAP then - local screen = nil - for _, s in pairs(machine.screens) do screen = s break end - if screen then - local out = OUT .. "/loader_frame" .. frames .. ".png" - local ok, err = pcall(function() return screen:snapshot(out) end) - print(string.format("LOADER: snapshot %s ok=%s %s", - out, tostring(ok), tostring(err or ""))) - end + if WAIT then + -- LOADER_END=0: run until completion, but never forever. + if frames >= MAXF then + if not done then + print(string.format("LOADER: warning: app still in startup at LOADER_MAX=%d", MAXF)) end + finish() end - machine:exit() + elseif frames >= ENDF then + -- A fixed LOADER_END still exits at that frame (the snapshot contract; + -- existing tests read loader_frame.png). Completion is reported + -- above but only LOADER_END=0 asks to exit early. + if not done then + print(string.format("LOADER: warning: app still in startup at LOADER_END=%d", ENDF)) + end + finish() end end, "vt1200-loader") -- 2.47.3