From 9b6428db473a3424c9d4ecffa6b5f1ee4f40dd19 Mon Sep 17 00:00:00 2001 From: Noah Date: Tue, 8 Sep 2026 00:47:08 -0600 Subject: [PATCH] qemu: start every panel daemon, and keep Modbus off the control channel Stage-2 init started only warden-flared and warden-modbus from the payload, so every daemon-backed screen (Automation, MikroTik, Mining, RS485, Starlink, Services) sat in its "not running" state on the rig while the bench panel showed the daemons up with nothing attached. It now starts every warden-* daemon it finds in the panel's own S-number order (ai, automation, modbus, mikrotik, asic, starlink, stratum, flared), each with no arguments as start-stop-daemon runs them on the panel. flare-edge tools/rig-pull-daemons.sh copies a panel's static binaries in. Doing that exposed a latent alias: init pointed warden-modbus's /dev/ttyS4 at /dev/ttyS0 whenever ttyS0 existed, and with --ctl that port IS the control channel, so Modbus polls arrived inside the wake reply and the bridge hung. run.sh now always attaches an RS485 pci-serial (a null chardev without --rs485), and init aliases ttyS4 to ttyS1 when warden.ctl is on the cmdline, ttyS0 otherwise. Verified on a boot with all eight daemons: status json reports automation.engine, mikrotik.backend, mining.available and rs485.available true with the Modbus scan sweeping the empty UART; six daemon-backed pages matched screenshots of the bench panel structurally (0/256 cells differ). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n --- qemu/README.md | 12 +++++++++--- qemu/payload/README.md | 17 +++++++++++++---- qemu/rootfs/sbin/init | 20 +++++++++++++++++--- qemu/run.sh | 13 +++++++++++-- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/qemu/README.md b/qemu/README.md index e4c3551..a6f38e8 100644 --- a/qemu/README.md +++ b/qemu/README.md @@ -63,9 +63,15 @@ bash qemu/run.sh --kernel $HOME/kbuild-out/linux-6.18.46/arch/arm/boot/zImage -- ``` Payload: drop static musl armv7 binaries into `qemu/payload/` (see its -README); `warden-flared`, `warden-modbus`, and `warden-ui` (the LVGL -fbdev+evdev build from flare-edge `tools/build-ui-vm.sh`) are started by -stage-2 init when present. +README). Stage-2 init starts every panel daemon it finds, in the panel's own +S-number order (`warden-ai`, `warden-automation`, `warden-modbus`, +`warden-mikrotik`, `warden-asic`, `warden-starlink`, `warden-stratum`, +`warden-flared`), then `warden-ui` (the LVGL fbdev+evdev build from +flare-edge `tools/build-ui-vm.sh`). The quickest faithful payload is the +daemons a bench panel already runs: flare-edge `tools/rig-pull-daemons.sh +--host ` copies them in. The RS485 UART is always present (a null +chardev without `--rs485`), so `warden-modbus` polls a port with nothing on +it, as on a panel with an empty header, and never the control channel. The VT cursor is kept off (`vt.global_cursor_default=0`): fbcon shares the virtio-gpu framebuffer with warden-ui and its blinking cursor would otherwise diff --git a/qemu/payload/README.md b/qemu/payload/README.md index ba2fae4..1ef464c 100644 --- a/qemu/payload/README.md +++ b/qemu/payload/README.md @@ -20,7 +20,16 @@ Then: cp /target/armv7-unknown-linux-musleabihf/release/warden-flared qemu/payload/ ``` -Stage-2 init starts `warden-flared`, `warden-modbus`, and `warden-ui` (the -UI also needs `--display on|headless` + the virt.fragment kernel for -/dev/fb0) automatically when present (logs land in `/tmp/.log` inside -the guest). An empty payload is valid: the image boots busybox-only. +Or take them from a panel that already runs them: every daemon is a static +musl binary, so flare-edge `tools/rig-pull-daemons.sh --host ` copies +`/usr/bin/warden-*` straight into this directory (recording the panel's +release and each md5 in `PULLED.txt`), minus `warden-ui`, which the rig +needs as the fbdev+evdev build. + +Stage-2 init starts every `warden-*` daemon it finds here, in the panel's +own S-number order (ai, automation, modbus, mikrotik, asic, starlink, +stratum, flared), then `warden-ui` (the UI also needs `--display +on|headless` + the virt.fragment kernel for /dev/fb0). Logs land in +`/tmp/.log` inside the guest, readable over the control bridge with +`ctl @cat /tmp/.log`. An empty payload is valid: the image boots +busybox-only. diff --git a/qemu/rootfs/sbin/init b/qemu/rootfs/sbin/init index 52f3420..2a52105 100755 --- a/qemu/rootfs/sbin/init +++ b/qemu/rootfs/sbin/init @@ -47,8 +47,12 @@ mount_fatal "oem${slot}" /oem mkdir -p /userdata/warden # RS485: warden-modbus hardcodes /dev/ttyS4 at compile time; alias it to the -# VM's pci-serial UART when one is present (needs the virt.fragment kernel). -[ -c /dev/ttyS0 ] && ln -sf /dev/ttyS0 /dev/ttyS4 +# VM's RS485 pci-serial UART (needs the virt.fragment kernel). run.sh lists +# the control channel's port first when there is one (warden.ctl on the +# cmdline), so the RS485 UART is ttyS1 then and ttyS0 otherwise. Aliasing +# ttyS0 blindly put Modbus polls on the control channel. +if grep -qw warden.ctl /proc/cmdline; then rs485="/dev/ttyS1"; else rs485="/dev/ttyS0"; fi +[ -c "$rs485" ] && ln -sf "$rs485" /dev/ttyS4 # Network: slirp user-mode net on eth0 (DHCP, fallback to QEMU's static map). # The fallback keys off the interface actually having an address: udhcpc @@ -86,7 +90,17 @@ if grep -qw warden.fwapply /proc/cmdline; then export WARDEN_FW_ALLOW_APPLY=1 echo "init: OTA APPLY ENABLED (warden.fwapply)" fi -for d in /usr/bin/warden-flared /usr/bin/warden-modbus; do +# The same daemons the panel's SysV scripts start, in their S-number order +# (S92 ai, S93 automation, S94 modbus, S95 mikrotik, S96 asic/starlink/ +# stratum, S97 flared), each with no arguments and no environment, exactly +# as start-stop-daemon runs them there. A daemon that cannot live on -M virt +# (warden-ai wants the NPU) exits into its log and the UI shows the same +# "not running" it would show on a panel whose daemon died; that is the +# panel's behaviour, not a rig substitute for it. warden-watchdog stays out: +# it and a payload flared do not mix (README). +for d in /usr/bin/warden-ai /usr/bin/warden-automation /usr/bin/warden-modbus \ + /usr/bin/warden-mikrotik /usr/bin/warden-asic /usr/bin/warden-starlink \ + /usr/bin/warden-stratum /usr/bin/warden-flared; do if [ -x "$d" ]; then name="$(basename "$d")" echo "init: starting $name" diff --git a/qemu/run.sh b/qemu/run.sh index 7587b96..934b6e5 100755 --- a/qemu/run.sh +++ b/qemu/run.sh @@ -126,8 +126,17 @@ fi # Modbus wire. [ -n "$CTL" ] && ARGS+=( -chardev "socket,id=ctl,path=$CTL,server=on,wait=off" -device "pci-serial,chardev=ctl" ) -[ -n "$RS485" ] && ARGS+=( -chardev "socket,id=rs485,path=$RS485,server=on,wait=off" - -device "pci-serial,chardev=rs485" ) +# Without --rs485 the UART is still there, wired to nothing: warden-modbus +# then polls a port that never answers, which is what a panel with an empty +# RS485 header looks like, rather than failing to open a port that does not +# exist (a different UI state) or, worse, being aliased onto the control +# channel by init (which happened: Modbus frames in the wake reply). +if [ -n "$RS485" ]; then + ARGS+=( -chardev "socket,id=rs485,path=$RS485,server=on,wait=off" + -device "pci-serial,chardev=rs485" ) +else + ARGS+=( -chardev "null,id=rs485" -device "pci-serial,chardev=rs485" ) +fi [ -n "$QMP" ] && ARGS+=( -qmp "unix:$QMP,server=on,wait=off" ) case "$DISPLAY_MODE" in