diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index 354501a..ffe4627 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -285,9 +285,31 @@ found and fixed: `auth.c` read `X-ForgeFIRM-Token`/`Host`/`Origin`/ `Sec-Fetch-Site` case-sensitively (a title-casing client was refused); now `u_map_get_case`. Bench tooling for the session is committed (`scripts/bench/platform_drills.py`, `live_fire_drills.py` `ircut` / -`expstop` / `ctrlstart`). Session rules, now standing: one live-laser run -per turn with the operator's confirmation before the next; only -observations, never inferences, in live-fire reporting. +`expstop` / `ctrlstart`, `fdscan.sh`). Session rules, now standing: one +live-laser run per turn with the operator's confirmation before the next; +only observations, never inferences, in live-fire reporting. +**Dry drills the same session, all PASS on the board:** decay/microstep +readback per axis (every value reads back, out-of-range `3` refused +`EINVAL`); dead-man trip readback (closing the flock'd fd mid-run → +`closed while locked and driver is running! Emergency stop`, `pic`/`head`/ +`thermal: making safe`; heater and TEC off, measure laser, UV LED and Z +driver off, pump/exhaust/intake/air-assist **unchanged**); three +`rmmod`/`modprobe` cycles with a thread reading state/position/faults/ +hall_sensor throughout (6618 reads served, 14162 refused while unloaded, +no oops/BUG/WARNING); the LED sequence ran (bright/dark/pulse/restore); +the module's probe lines read `EPIT clock 66000000 Hz` and `SDMA channel +26 reserved for pulse playback (script at halfword 7680)` with no bank +warnings; forgectrl's helper children (`curl` during `/update/check`, the +snapshot path) never hold a pulse-device descriptor — only the controller +does; a `$H` gfcloud homing session completed in 56 s with 7 accelerometer +motion windows above the 500-count threshold at the ~100 Hz sampler +(anchor written, `H:1`); a kernel panic (`sysrq c`) mid-move stopped +motion instantly (operator-witnessed) and the board rebooted on `panic=10` +into a healthy state (liveness MOTION OK, controller running, latch +commanded locked). Observed once, cause not established: after the three +module reloads the first liveness probe read NO MOTION (p2p 343/241); the +ladder's rail-off/re-probe recovered it (p2p 3466/2163) — a module reload +resets the analog configuration, and the ladder exists for this. **Phase 11 (licensing, legal, and documentation hygiene — the last phase) is code-complete and host-verified, 2026-08-15.** Licensing: @@ -2264,34 +2286,29 @@ accordingly ("Automatic — AP country, else World"). trip before calling the gate proven, and measure an actual flame signature (a tea light in the closed bed, machine idle) so the 15 is anchored on both sides. - - **Kernel platform-hygiene batch (item 9), on the flashed image:** - panic mid-motion with motors locked and the laser latched (motion - stops, safety lines read safe); decay mode set/readback per axis; - LED behavior and a clean module unload; the two new probe lines in - dmesg with no bank warnings; a dead-man trip with the head - registers read back (measure laser off, UV LED off, lens motor - de-energized). Plus the Phase 8 trio: load/unload under - `CONFIG_DEBUG_MUTEXES` (needs a debug kernel build), a forced - `-EPROBE_DEFER` unwind, and a concurrent `cat` of a state attr - during `rmmod`. - - **Dead-man collateral:** a kernel dead-man trip leaves the pump and - airflow running (heat sources only go off); kill forgectrl during - an update download (no pinned device, no EBUSY respawn storm). - ~~Re-run the armed kill drill on the *expected*-stop path~~ — - **DONE 2026-08-15**: it failed first (5 s of continued fire), the - defect is fixed on both sides, and the re-run passed (see the - session record above). + - ~~**Kernel platform-hygiene batch (item 9), on the flashed + image**~~ — **DONE 2026-08-15** (panic mid-motion, decay/microstep + readback, LED sequence + clean unload, probe lines, dead-man head + readback, concurrent `cat` during `rmmod` — session record above). + Still needing a debug kernel build: load/unload under + `CONFIG_DEBUG_MUTEXES` and a forced `-EPROBE_DEFER` unwind. + - ~~**Dead-man collateral**~~ — **DONE 2026-08-15**: the trip leaves + pump and airflow running (readback drill); helper children never + hold the pulse device (fd-scan during `/update/check` + snapshot); + the armed kill on the *expected*-stop path failed first (5 s of + continued fire), the defect is fixed on both sides, and the re-run + passed. The literal "kill forgectrl mid-download" variant needs a + published `.fw` to download and was covered by the fd-scan instead. - **Physical-evidence negatives:** force a head I²C error and confirm the witnesses report an error, not a plausible value; confirm a failed head capture leaves the measure laser off. - **Cloud mode:** the C-1 cancel-with-a-rejected-settings-action drill (cancel must stop the cut); malformed-frame and DNS-blip injections against a live session; the oversize/bad-header job - rejected before the ring loads (tracked in `CLOUD.md`); re-verify - the homing accelerometer motion-window counts against the - characterized thresholds at the next live homing (the witness now - samples at ~100 Hz); confirm a real print header's fan duties - round-trip to the engine (item 8). + rejected before the ring loads (tracked in `CLOUD.md`); ~~re-verify + the homing accelerometer motion-window counts~~ **DONE 2026-08-15** + (7 windows ≥ 500 at the ~100 Hz sampler on a real `$H`); confirm a + real print header's fan duties round-trip to the engine (item 8). - **Opportunistic:** `STATE_FAULT` recovery via `enable` without a module reload the next time a DRV8825 fault line actually trips. - **Config-dependent, deliberately not gated:** an armed GRBL job diff --git a/scripts/bench/fdscan.sh b/scripts/bench/fdscan.sh new file mode 100644 index 0000000..b096e80 --- /dev/null +++ b/scripts/bench/fdscan.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Runs ON the board. While forgectrl spawns helper children (the update +# check's curl, the snapshot's media-ctl/v4l2-ctl), scan every child for a +# descriptor on the pulse device. Expected: none - only the controller +# inherits /dev/glowforge (F_SETFD cleared for that one spawn); every other +# child gets the O_CLOEXEC default. Usage: fdscan.sh +TOK="$1" +P=$(pidof forgectrl) +[ -n "$P" ] || { echo "forgectrl not running"; exit 1; } +CTRL=$(pidof grblHAL_glowforge) +echo "forgectrl pid $P, controller pid ${CTRL:-none}" +# Kick the helpers off in the background. +( curl -s -X POST -H "X-ForgeFIRM-Token: $TOK" http://127.0.0.1:8080/update/check >/tmp/upd.out 2>&1 ) & +( curl -s -o /tmp/snap.jpg http://127.0.0.1:8080/cam/snapshot ) & +hits=0; seen=0; names="" +i=0 +while [ $i -lt 60 ]; do # ~6 s of scanning at 10 Hz + for c in $(pgrep -P "$P"); do + seen=$((seen+1)) + n=$(cat /proc/$c/comm 2>/dev/null) + names="$names $n" + if ls -l /proc/$c/fd 2>/dev/null | grep -q glowforge; then + if [ "$c" != "$CTRL" ]; then hits=$((hits+1)); echo "HIT: child $c ($n) holds the pulse device"; ls -l /proc/$c/fd | grep glowforge; fi + fi + done + i=$((i+1)); usleep 100000 2>/dev/null || sleep 0.1 +done +wait +echo "children observed: $(echo $names | tr ' ' '\n' | sort | uniq -c | tr '\n' ';')" +echo "controller holds pulse fd: $(ls -l /proc/$CTRL/fd 2>/dev/null | grep -c glowforge)" +echo "update/check reply: $(cat /tmp/upd.out | head -c 200)" +echo "snapshot bytes: $(wc -c < /tmp/snap.jpg 2>/dev/null)" +echo "non-controller children holding the pulse device: $hits ($([ $hits -eq 0 ] && echo PASS || echo FAIL))"