Files
bfe-core1106-sdk/qemu
NoahandClaude Fable 5.1 8a57057053 Bridge warden-ui's debug channel out of the qemu VM
The rig could drive the UI and detect one outcome: the process died. Nothing
could ask the UI what page it was on or what a tap would land on, because
that channel is a FIFO inside the guest and the initramfs is busybox-only
with no sshd. Scenarios therefore asserted nothing and screenshots went
unread.

run.sh --ctl exposes a second pci-serial port as a unix socket, the same
device the RS485 bridge already rides, listed first so it is always ttyS0.
It also puts warden.ctl on the kernel command line, and init bridges only
when that marker is present: a VM launched with --rs485 alone has a ttyS0
too, and that one is the Modbus wire. The bridge relays one command line in
and the FIFO's reply out, then a sentinel so the reader needs no timeout.

qmp.py gains the channel verbs (nav, page, stats, hit, assert_page,
assert_hit), records every step to results.jsonl as ok/fail/fatal, continues
past an assertion mismatch so one run reports every broken expectation, and
checks the console after EVERY step for the stage-2 init's EXITED line so a
crash is pinned to the step that caused it. assert_hit matches the widget's
bounding box: an icon has no usable caption and two list rows share a class,
but the geometry the UI itself resolved is exact.

The vocabulary is what tools/warden-ctl already speaks over SSH to a real
panel, so a script that runs here runs there. Verified end to end on the rig
(11/11 verbs round-tripped) and against the bench panel, where the same
commands returned byte-identical results.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n
2026-09-07 21:54:52 -06:00
..

The Device Simulator

A QEMU virtual machine that boots the real forward-ported kernel and real userspace: the 86 Panel (init, daemons, networking, OTA, watchdog, display) developed and tested with no board attached. The third simulator in the stack (three-way split: root README), deliberately not named "sim": it runs the whole machine above the kernel entry point on real binaries: bring your own, or drop prebuilt payloads in payload/. Decision record: ADR-0006.

The Boundary

There is no RV1106 machine model in QEMU and everything below the kernel is closed rkbin blobs plus mask ROM, so the VM enters at -kernel zImage on -M virt,highmem=off (single Cortex-A7, 256M: the RV1106G3's shape).

Emulated / substituted Not emulated (stays bench / sim/ territory)
Kernel boot, init ordering, switch_root BootROM, idblock/DDR-init, SPL, U-Boot
A/B outcome (warden.slot= cmdline) Real BCB A/B selection, bootcount auto-revert
Storage: virtio-blk with the device's exact blkdevparts= layout + /dev/block/by-name/ contract eMMC controller itself
Network: virtio-net (slirp, hostfwd 22/80/28443) GMAC, AIC8800 wifi, usb0 gadget
Display: virtio-gpu 720x720 via fbdev emulation VOP/RGB666 pipeline, CH32V003 panel init, RGA blits
Touch: virtio-tablet (QMP input-send-event) GT911 on I2C3
Watchdog: i6300esb (PCI), -action watchdog=reset DW watchdog @0xff5a0000, HPMCU supervisor
RS485: pci-serial chardev bridged to sim/'s ModbusSlave Real UART4 timing/electrical behavior
RTC: PL031 (--rtc reproduces the no-RTC 2021-clock incident class) The unpopulated backup-cell reality

"Boots/works under emulation" is never evidence of "works on silicon." The VM narrows which claims need a panel; on-device claims still need on-device evidence. Conversely, the VM is the first environment that runs production binaries on a non-RV1106 memory map: it found flare-edge #106 (fatal SIGBUS in flared's HPMCU probe) and #107 (Y2038 time_t truncation) on its first two boots of real userspace.

Documented guest deviations from production, set by stage-2 init: WARDEN_FLARE_INSECURE=1 (the desk mock portal is plain HTTP) and WARDEN_HPMCU=0 (no mailbox SRAM on virt; flared >= flare-edge#106 fix required, or the daemon dies of SIGBUS).

The VM's kernel is ahead of the panels' in one way that shows in the UI. It is built from build/warden_defconfig plus configs/virt.fragment, so it has CONFIG_BRIDGE; the vendor 5.10 kernel most panels still run does not. A Client-mode access point is bridged to the wired uplink and needs that, so the Wi-Fi page offers it in the VM and says "Needs Router mode: this kernel cannot bridge" on a 5.10 panel. Both are correct for the kernel underneath. The difference disappears as panels move to the 6.18 build, but until then a screenshot from here is not evidence about a 5.10 panel's Wi-Fi page.

Quick Start

# 1. kernel: canonical build boots the VM as-is; the fragment variant adds
#    the scenario devices (PCI serial, watchdog, WireGuard, virtio-gpu/input)
WORK=$HOME/kbuild-out CROSS_COMPILE=arm-linux-gnueabihf- \
  WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment bash build/build-kernel.sh

# 2. initramfs (sha256-pinned static busybox + qemu/rootfs/) and A/B disk
bash qemu/mkinitramfs.sh
bash qemu/mkimage.sh                  # options: --portal-url --state K=V --fw-version

# 3. run (see run.sh header for all flags)
bash qemu/run.sh --kernel $HOME/kbuild-out/linux-6.18.46/arch/arm/boot/zImage --shell

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.

Scenarios

All take the virt-fragment <zImage>; FLARE_EDGE=<checkout> where noted.

Scenario Needs Proves
boot-smoke.sh - sentinel-asserting boot; runs in CI inside kernel-build
portal-scenario.sh FLARE_EDGE real flared against the desk mock portal: authenticated check-in, desired-state pull, signed tier-1 .wfw download; verify/stage/APPLYING as a dry run (no WARDEN_FW_ALLOW_APPLY)
ota-apply.sh FLARE_EDGE the FULL apply: the .wfw's bootable rootfs payload is written to rootfs_b (run.sh --allow-apply gates it per boot), the AvbABData in misc flips, and slot _b boots the applied version
ui-shot.sh - display+touch, headless: QMP-screendumps the 720x720 UI, taps the Metrics tab via input-send-event, asserts the frame changed (qmp.py is the QMP client)
ui-drive.sh <script> - the same rig for a SEQUENCE: boots once, runs a qmp.py drive script of taps/swipes/screenshots in panel pixels, and FAILS if warden-ui died on the way (stage-2 init announces the exit on the console). tests/scripts/nav-stress.txt is the navigation regression: it reproduces the s_row_left overflow that segfaulted the UI on returning to Settings > Apps
real-image-boot.sh matched rootfs.img + oem.img an ACTUAL flare-edge build (placed by mkimage.sh --rootfs-image/--oem-image) boots its own init chain to getty; binaries predating known fixes reproduce their bugs faithfully, a time machine for field issues
watchdog (run.sh --watchdog) - arm /dev/watchdog, don't pet: the VM resets ~30 s later (verified)

Scenario fine print:

  • OTA: the BCB slot CHOICE and the physical reset stay emulated by the harness (ADR-0006 boundary); the VM exports WARDEN_HARD_RESET=0 so flared's post-apply reset surfaces as a reported error, not a /dev/mem fault.
  • Touch injection holds 200 ms: an instantaneous press+release lands inside one LVGL poll and never clicks.
  • Watchdog + a flared payload don't mix: flared pets only while the UI heartbeat is fresh.

Gotchas

  • AF_UNIX socket paths cap at ~108 chars; keep --rs485/--qmp paths short.
  • A serial port that is closed discards incoming bytes: hold ONE fd open across write and read when scripting the guest side of the RS485 bridge.
  • highmem=off and -global virtio-mmio.force-legacy=false are required (32-bit ECAM reach; virtio-1-only gpu/input); both live ONLY in run.sh, which every script (boot smoke included) delegates to.
  • Never pass earlyprintk: DEBUG_UART_PHYS is the RV1106's 0xff4c0000.

Requirements

qemu-system-arm (Debian 13 ships QEMU 10), curl, cpio, mkfs.ext4, gcc-arm-linux-gnueabihf (kernel build), python3 (+cryptography for the portal scenario's .wfw signing). CI: the hosted qemu-tools job builds the tooling; the boot smoke runs inside the (also hosted, dispatch-only) kernel-build job, which apt-installs its own toolchain and qemu (ADR-0007).