docs: ASCII typography and style normalization across all repo text
Replace every em dash with real punctuation (rewrites, not hyphen swaps) in docs, code comments, scripts, configs, and the port records; convert en dashes, curly quotes, ellipsis glyphs, arrows, and section signs to ASCII; drop machine-writing tell phrases from living docs. ADR titles now use a colon. The M2 bring-up DTS model string carried an em dash into the patch series and its record echoes; fixed at both, and the full series re-verified to apply cleanly onto pristine 6.18.46. One comment in freshness.h deliberately names the em dash glyph the UI renders as the unknown mark; that is data, kept as prose naming it. Verified: cargo tests (sim, config-lint, rs485-bridge), shellcheck, both driver MC/DC harnesses, patches-apply.
This commit is contained in:
+13
-13
@@ -1,17 +1,17 @@
|
||||
# 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 —
|
||||
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,
|
||||
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).
|
||||
`-M virt,highmem=off` (single Cortex-A7, 256M: the RV1106G3's shape).
|
||||
|
||||
| Emulated / substituted | Not emulated (stays bench / `sim/` territory) |
|
||||
|---|---|
|
||||
@@ -28,7 +28,7 @@ closed rkbin blobs plus mask ROM, so the VM **enters at `-kernel zImage`** on
|
||||
**"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
|
||||
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.
|
||||
|
||||
@@ -54,7 +54,7 @@ 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
|
||||
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.
|
||||
|
||||
@@ -64,12 +64,12 @@ 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 |
|
||||
| `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) |
|
||||
| `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) |
|
||||
| `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) |
|
||||
| `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:
|
||||
|
||||
@@ -77,18 +77,18 @@ Scenario fine print:
|
||||
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
|
||||
- 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.
|
||||
- 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 load-bearing
|
||||
(32-bit ECAM reach; virtio-1-only gpu/input) — both live ONLY in run.sh,
|
||||
- `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.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# The device's canonical 12-partition A/B layout, expressed for the VM's
|
||||
# virtio disk (vda). On hardware the same string names mmcblk0 and is baked
|
||||
# into the U-Boot env — source: flare-edge docs/decisions/0003-partition-layout.md.
|
||||
# into the U-Boot env; source: flare-edge docs/decisions/0003-partition-layout.md.
|
||||
# There is no MBR/GPT anywhere: U-Boot and Linux both parse this string, which
|
||||
# is why handing it to the VM kernel on the cmdline reproduces the exact
|
||||
# partition map (vda9 = rootfs_a = hardware mmcblk0p9).
|
||||
#
|
||||
# Sourced by qemu/mkimage.sh (computes byte offsets from it) and qemu/run.sh
|
||||
# (passes it verbatim in -append). Single source of truth — edit only here.
|
||||
# (passes it verbatim in -append). Single source of truth: edit only here.
|
||||
WARDEN_BLKDEVPARTS='vda:32K(env),512K@32K(idblock),512K(uboot),512K(misc),32M(boot_a),32M(boot_b),128M(oem_a),128M(oem_b),1G(rootfs_a),1G(rootfs_b),32M(recovery),1G(userdata)'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# QEMU -M virt kernel variant — merged onto build/warden_defconfig via
|
||||
# QEMU -M virt kernel variant, merged onto build/warden_defconfig via
|
||||
# WARDEN_KCONFIG_FRAGMENT (see build/build-kernel.sh). The RV1106 zImage stays
|
||||
# canonical and byte-identical when the variable is unset.
|
||||
#
|
||||
@@ -18,7 +18,7 @@ CONFIG_PCI_HOST_GENERIC=y
|
||||
# shows up as ttyS0; stage-2 init aliases it to the device's /dev/ttyS4).
|
||||
CONFIG_SERIAL_8250_PCI=y
|
||||
|
||||
# /dev/watchdog for flared's watchdog_loop() — untestable on both existing
|
||||
# /dev/watchdog for flared's watchdog_loop(), untestable on both existing
|
||||
# sims. i6300esb is the watchdog QEMU offers on arm virt (PCI device):
|
||||
# -device i6300esb -action watchdog=reset.
|
||||
CONFIG_WATCHDOG=y
|
||||
@@ -28,7 +28,7 @@ CONFIG_I6300ESB_WDT=y
|
||||
CONFIG_WIREGUARD=y
|
||||
|
||||
# Display + touch: virtio-gpu scanout with fbdev emulation (the VM UI build
|
||||
# uses LVGL's fbdev backend — no libdrm needed in the guest), virtio-tablet
|
||||
# uses LVGL's fbdev backend; no libdrm needed in the guest), virtio-tablet
|
||||
# for absolute-coordinate touch injection via QMP.
|
||||
CONFIG_FB=y
|
||||
CONFIG_DRM_VIRTIO_GPU=y
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ qemu_log() { printf '\033[36m== %s\033[0m\n' "$*"; }
|
||||
|
||||
# Fetch (or accept via $BUSYBOX) the pinned static armv7 busybox and verify it
|
||||
# against qemu/busybox.sha256. FAILS CLOSED: a missing pin refuses to build,
|
||||
# never silently skips verification — mirroring build/build-kernel.sh's
|
||||
# never silently skips verification, mirroring build/build-kernel.sh's
|
||||
# tarball handling. Sets $BB to the verified binary's path.
|
||||
qemu_get_busybox() {
|
||||
local sha_file="$QEMU_DIR/busybox.sha256"
|
||||
@@ -19,7 +19,7 @@ qemu_get_busybox() {
|
||||
# Pin first: a missing pin refuses BEFORE downloading, same ordering as
|
||||
# build/fetch-kernel-tarball.sh.
|
||||
[ -f "$sha_file" ] || {
|
||||
echo "FATAL: no pinned sha256 for busybox (expected $sha_file) — refusing to build from an unverified binary" >&2
|
||||
echo "FATAL: no pinned sha256 for busybox (expected $sha_file): refusing to build from an unverified binary" >&2
|
||||
exit 1
|
||||
}
|
||||
if [ ! -f "$BB" ]; then
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the VM's virtio disk image carrying the device's canonical 12-partition
|
||||
# A/B layout (qemu/blkdevparts.conf — the same string U-Boot and Linux parse on
|
||||
# A/B layout (qemu/blkdevparts.conf: the same string U-Boot and Linux parse on
|
||||
# hardware; there is no MBR/GPT). Every partition is placed at the exact offset
|
||||
# the cmdline string declares; rootfs_a/rootfs_b/oem_a/oem_b/userdata get ext4,
|
||||
# the boot-chain partitions (env/idblock/uboot/misc/boot_a/boot_b/recovery)
|
||||
# stay zeroed — the VM enters at -kernel and never reads them.
|
||||
# stay zeroed: the VM enters at -kernel and never reads them.
|
||||
#
|
||||
# Built entirely UNPRIVILEGED: per-partition mkfs.ext4 -d (no loop mounts, no
|
||||
# sudo), then dd'd into a sparse raw image.
|
||||
@@ -72,7 +72,7 @@ for p in "$QEMU_DIR"/payload/*; do
|
||||
install -m 0755 "$p" "$ROOT/usr/bin/$(basename "$p")"
|
||||
done
|
||||
|
||||
# Firmware version stamp — same path the device build writes; flared reads its
|
||||
# Firmware version stamp: same path the device build writes; flared reads its
|
||||
# running version here (downgrade rules key off it).
|
||||
printf '%s\n' "$FW_VERSION" > "$ROOT/etc/warden-firmware-version"
|
||||
|
||||
@@ -144,7 +144,7 @@ place_partition() {
|
||||
*) echo "FATAL: unknown partition name '$name' in blkdevparts.conf" >&2; exit 1 ;;
|
||||
esac
|
||||
DISK_END_TRACK "$off" "$size"
|
||||
# dd in 4K blocks — every offset in the canonical layout is 4K-aligned;
|
||||
# dd in 4K blocks: every offset in the canonical layout is 4K-aligned;
|
||||
# assert rather than assume, a misaligned write would corrupt a neighbor.
|
||||
if [ $((off % 4096)) -ne 0 ] || [ $((size % 4096)) -ne 0 ]; then
|
||||
echo "FATAL: partition $name not 4K-aligned (off=$off size=$size)" >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the QEMU device-sim initramfs: the pinned static busybox + qemu/rootfs/.
|
||||
# The busybox binary is the ONLY external input (sha256-pinned, fail-closed —
|
||||
# The busybox binary is the ONLY external input (sha256-pinned, fail-closed:
|
||||
# see qemu/lib.sh).
|
||||
#
|
||||
# Env:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Guest Payloads
|
||||
|
||||
Drop **static musl armv7** binaries in this directory (contents are
|
||||
gitignored — binaries are never committed); `qemu/mkimage.sh` copies everything
|
||||
gitignored; binaries are never committed); `qemu/mkimage.sh` copies everything
|
||||
in this directory (except this README) into `/usr/bin/` of both rootfs slots.
|
||||
Static musl is the same target the device uses for its Rust daemons, so the
|
||||
exact production binaries run unmodified in the VM.
|
||||
@@ -21,6 +21,6 @@ cp <flare-edge>/target/armv7-unknown-linux-musleabihf/release/warden-flared qemu
|
||||
```
|
||||
|
||||
Stage-2 init starts `warden-flared`, `warden-modbus`, and `warden-ui` (the
|
||||
UI additionally needs `--display on|headless` + the virt.fragment kernel for
|
||||
UI also needs `--display on|headless` + the virt.fragment kernel for
|
||||
/dev/fb0) automatically when present (logs land in `/tmp/<name>.log` inside
|
||||
the guest). An empty payload is valid — the image boots busybox-only.
|
||||
the guest). An empty payload is valid: the image boots busybox-only.
|
||||
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
#!/bin/busybox sh
|
||||
# shellcheck shell=dash
|
||||
# Stage-1 rc: sourced by /init (still PID 1, initramfs root) when a virtio
|
||||
# disk is present. Emulates U-Boot's slot choice — mount the validated slot's
|
||||
# disk is present. Emulates U-Boot's slot choice: mount the validated slot's
|
||||
# rootfs and switch_root into it. This is an EMULATION of the A/B selection
|
||||
# outcome, not the BCB/bootcount mechanism itself.
|
||||
#
|
||||
# Every guarded failure path `return`s to /init (valid in a sourced script;
|
||||
# /init then falls through to shell/poweroff). The final exec is the one
|
||||
# unguardable step: if switch_root itself fails to launch, the shell — PID 1 —
|
||||
# unguardable step: if switch_root itself fails to launch, the shell (PID 1)
|
||||
# exits and the kernel panics; the applet-existence check below catches the
|
||||
# only preventable variant of that.
|
||||
|
||||
@@ -19,22 +19,22 @@ slot="$(warden_slot)"
|
||||
|
||||
root="/dev/block/by-name/rootfs${slot}"
|
||||
if [ ! -e "$root" ]; then
|
||||
echo "rc: $root missing — staying in initramfs"
|
||||
echo "rc: $root missing: staying in initramfs"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p /mnt
|
||||
if ! mount -t ext4 "$root" /mnt; then
|
||||
echo "rc: mount of $root failed — staying in initramfs"
|
||||
echo "rc: mount of $root failed: staying in initramfs"
|
||||
return 0
|
||||
fi
|
||||
if [ ! -x /mnt/sbin/init ]; then
|
||||
echo "rc: $root has no /sbin/init — staying in initramfs"
|
||||
echo "rc: $root has no /sbin/init: staying in initramfs"
|
||||
umount /mnt
|
||||
return 0
|
||||
fi
|
||||
if ! command -v switch_root >/dev/null; then
|
||||
echo "rc: busybox lacks switch_root — staying in initramfs"
|
||||
echo "rc: busybox lacks switch_root: staying in initramfs"
|
||||
umount /mnt
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# shellcheck shell=sh
|
||||
# Shared helpers for the VM's stage-1 (/init + /etc/rc, initramfs) and stage-2
|
||||
# (/sbin/init, disk rootfs) boot scripts. Present in both filesystems because
|
||||
# both are staged from the same qemu/rootfs/ skeleton. ONE copy of each rule —
|
||||
# both are staged from the same qemu/rootfs/ skeleton. ONE copy of each rule:
|
||||
# the slot-validation drift between two hand-copied parsers was a real
|
||||
# review finding.
|
||||
|
||||
# Populate /dev/block/by-name/<PARTNAME> symlinks from sysfs uevents — the
|
||||
# Populate /dev/block/by-name/<PARTNAME> symlinks from sysfs uevents: the
|
||||
# contract flare-edge's slotctl.rs relies on. blkdevparts= gives every vda
|
||||
# partition a PARTNAME.
|
||||
warden_populate_by_name() {
|
||||
@@ -26,7 +26,7 @@ warden_populate_by_name() {
|
||||
}
|
||||
|
||||
# Parse warden.slot= from the cmdline (whole-token, never substring) and
|
||||
# VALIDATE it — echoes "_a" or "_b", falling back to _a with a warning.
|
||||
# VALIDATE it: echoes "_a" or "_b", falling back to _a with a warning.
|
||||
warden_slot() {
|
||||
slot="_a"
|
||||
# shellcheck disable=SC2013 # cmdline TOKENS are the unit here, not lines
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
# shellcheck shell=dash
|
||||
# WardenOS QEMU device sim: initramfs /init (PID 1).
|
||||
#
|
||||
# Phase-1 duty: prove the kernel booted on -M virt — print the sentinel the
|
||||
# Phase-1 duty: prove the kernel booted on -M virt; print the sentinel the
|
||||
# smoke test greps for, then power off (PSCI SYSTEM_OFF, so qemu exits).
|
||||
# `warden.shell` on the kernel cmdline drops to an interactive shell instead.
|
||||
|
||||
@@ -19,7 +19,7 @@ mount -t sysfs sysfs /sys
|
||||
echo "WARDEN-QEMU-BOOT-OK"
|
||||
|
||||
# With a virtio disk attached, hand over to the stage-1 rc (by-name symlinks,
|
||||
# slot select, switch_root). It only returns on failure — then fall through to
|
||||
# slot select, switch_root). It only returns on failure; then fall through to
|
||||
# the diskless shell/poweroff behavior below.
|
||||
if [ -b /dev/vda ]; then
|
||||
# shellcheck source=qemu/rootfs/etc/rc disable=SC1091
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# shellcheck shell=dash
|
||||
# Stage-2 init: PID 1 on the disk rootfs (rootfs_a or rootfs_b), reached via
|
||||
# switch_root from the initramfs. Brings up the minimum a WardenOS userspace
|
||||
# needs — mounts, by-name symlinks, network, serial alias — then starts any
|
||||
# needs (mounts, by-name symlinks, network, serial alias), then starts any
|
||||
# payload daemons and holds. This stands in for the device's BusyBox SysV
|
||||
# /etc/init.d/S* sequence; it is deliberately tiny, not a model of it.
|
||||
|
||||
@@ -17,7 +17,7 @@ mount -t tmpfs tmpfs /tmp
|
||||
# shellcheck source=qemu/rootfs/etc/warden-lib.sh disable=SC1091
|
||||
. /etc/warden-lib.sh
|
||||
|
||||
# Fresh devtmpfs — repopulate the by-name contract; same VALIDATED slot rule
|
||||
# Fresh devtmpfs: repopulate the by-name contract; same VALIDATED slot rule
|
||||
# as stage 1 (shared helper, so the two can never drift).
|
||||
warden_populate_by_name
|
||||
slot="$(warden_slot)"
|
||||
@@ -45,7 +45,7 @@ mkdir -p /userdata/warden
|
||||
[ -c /dev/ttyS0 ] && ln -sf /dev/ttyS0 /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
|
||||
# The fallback keys off the interface actually having an address: udhcpc
|
||||
# exiting 0 only proves a lease, not that the hook script applied it.
|
||||
ip link set lo up
|
||||
if [ -e /sys/class/net/eth0 ]; then
|
||||
@@ -64,7 +64,7 @@ echo "WARDEN-QEMU-ROOTFS-OK slot=${slot}"
|
||||
|
||||
# Payload daemons (dropped into /usr/bin by qemu/mkimage.sh from qemu/payload/).
|
||||
# WARDEN_FLARE_INSECURE=1: the VM's portal is the desk mock over plain HTTP.
|
||||
# This is a dev instrument — a production device build never sets it.
|
||||
# This is a dev instrument: a production device build never sets it.
|
||||
export WARDEN_FLARE_INSECURE=1
|
||||
# No HPMCU on -M virt: the mailbox SRAM (0xff6fff00) is unmapped bus space
|
||||
# here, and flared's /dev/mem poke dies with an external abort (SIGBUS). The
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "warden-rs485-bridge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Bridges a QEMU serial chardev (unix socket) to the warden-sim Modbus RTU slave, so the VM guest's RS-485 master polls the same simulated field bus the unit tests do — fault injection included."
|
||||
description = "Bridges a QEMU serial chardev (unix socket) to the warden-sim Modbus RTU slave, so the VM guest's RS-485 master polls the same simulated field bus the unit tests do, fault injection included."
|
||||
license = "GPL-2.0-only"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Micro-benchmarks for the RS-485 bridge dispatch path — same dependency-free
|
||||
//! Micro-benchmarks for the RS-485 bridge dispatch path: same dependency-free
|
||||
//! fixed-iteration pattern as sim/benches/sim_bench.rs: human timings to
|
||||
//! stdout, one JSON line per benchmark to stderr for CI trend capture.
|
||||
//!
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//! what it believes is /dev/ttyS4); this bridge is the wire and every slave on
|
||||
//! it. Frames are delimited by an inter-frame gap of silence: RTU's 3.5-char
|
||||
//! rule cannot survive a socket transport, so a wall-clock gap stands in for it.
|
||||
//! A mis-split frame fails CRC inside `handle_frame`, which answers `None` —
|
||||
//! exactly a real slave staying silent — and the master already treats silence
|
||||
//! A mis-split frame fails CRC inside `handle_frame`, which answers `None`
|
||||
//! (exactly a real slave staying silent), and the master already treats silence
|
||||
//! as a timeout, so the failure mode degrades to a dropped poll, never a
|
||||
//! phantom reply.
|
||||
//!
|
||||
@@ -26,7 +26,7 @@ pub const DEFAULT_GAP: Duration = Duration::from_millis(10);
|
||||
|
||||
/// Accumulation cap: a Modbus RTU ADU is at most 256 bytes, so anything past
|
||||
/// 2x that without an inter-frame gap is a misbehaving master streaming
|
||||
/// continuously — drop the buffer instead of growing without bound.
|
||||
/// continuously. Drop the buffer instead of growing without bound.
|
||||
const MAX_PENDING: usize = 512;
|
||||
|
||||
/// The shared bus: the slave plus its declared dimensions. The sim's register
|
||||
@@ -75,11 +75,11 @@ pub fn pump_serial(
|
||||
if buf.len() > MAX_PENDING {
|
||||
// Rate-limit the log and back off for one gap so a master
|
||||
// streaming continuously cannot peg a core and flood
|
||||
// stderr — mirroring the accept-loop backoff.
|
||||
// stderr, mirroring the accept-loop backoff.
|
||||
discards += 1;
|
||||
if discards == 1 || discards.is_multiple_of(256) {
|
||||
eprintln!(
|
||||
"rs485: {} bytes buffered with no inter-frame gap — \
|
||||
"rs485: {} bytes buffered with no inter-frame gap, \
|
||||
discarding (misbehaving master? {} discards so far)",
|
||||
buf.len(),
|
||||
discards
|
||||
@@ -152,7 +152,7 @@ pub fn handle_control_line(line: &str, bus: &Bus) -> String {
|
||||
return format!("err trailing arguments after '{cmd}'");
|
||||
}
|
||||
// Each arm states its own bound (bus.regs for register space, bus.bits for
|
||||
// bit space) INLINE — a previous string-keyed lookup defaulted silently to
|
||||
// bit space) INLINE: a previous string-keyed lookup defaulted silently to
|
||||
// the bit bound, which would have handed a future `get-input` command the
|
||||
// wrong range and reintroduced the out-of-range panic this check prevents.
|
||||
let mut s = bus.slave.lock().unwrap();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! control listener.
|
||||
//!
|
||||
//! Typical use (matches qemu/run.sh --rs485). Put the sockets in a private
|
||||
//! per-run directory (mktemp -d) — short (AF_UNIX caps paths at ~108 chars)
|
||||
//! per-run directory (mktemp -d): short (AF_UNIX caps paths at ~108 chars)
|
||||
//! and not guessable/pre-creatable by other local users, unlike a fixed
|
||||
//! /tmp name:
|
||||
//!
|
||||
@@ -40,7 +40,7 @@ fn main() {
|
||||
eprintln!("{name} needs a value");
|
||||
usage()
|
||||
});
|
||||
// A following flag means the value was omitted — report the real
|
||||
// A following flag means the value was omitted. Report the real
|
||||
// problem instead of swallowing the flag as a bogus value.
|
||||
if v.starts_with("--") {
|
||||
eprintln!("{name} needs a value, got flag '{v}'");
|
||||
@@ -70,7 +70,7 @@ fn main() {
|
||||
if let Some(path) = control {
|
||||
// Clear a stale socket from a previous run. A failure here that is not
|
||||
// "nothing to remove" (e.g. someone else's file behind /tmp's sticky
|
||||
// bit) will make the bind below fail — surface both errors.
|
||||
// bit) will make the bind below fail. Surface both errors.
|
||||
let removed = std::fs::remove_file(&path);
|
||||
let listener = UnixListener::bind(&path).unwrap_or_else(|e| {
|
||||
eprintln!("FATAL: cannot bind control socket {path}: {e}");
|
||||
@@ -89,7 +89,7 @@ fn main() {
|
||||
let conn = match conn {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("rs485: control accept failed: {e} — backing off");
|
||||
eprintln!("rs485: control accept failed: {e}, backing off");
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
continue;
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Launch the WardenOS device VM (qemu-system-arm -M virt, single Cortex-A7,
|
||||
# 256M — the RV1106G3's shape). See qemu/README.md for what this does and does
|
||||
# 256M: the RV1106G3's shape). See qemu/README.md for what this does and does
|
||||
# not emulate.
|
||||
#
|
||||
# Usage: run.sh --kernel <zImage> [options] [-- <extra qemu args>]
|
||||
@@ -10,7 +10,7 @@
|
||||
# when present; pass --no-disk for a diskless initramfs boot)
|
||||
# --no-disk boot without a disk (initramfs shell/smoke behavior)
|
||||
# --slot _a|_b rootfs slot to boot (default _a)
|
||||
# --rtc DATE guest RTC base, e.g. 2021-01-01 — reproduces the no-RTC
|
||||
# --rtc DATE guest RTC base, e.g. 2021-01-01: reproduces the no-RTC
|
||||
# "device boots believing 2021" incident class
|
||||
# --rs485 SOCK unix socket chardev for the RS485/Modbus bridge
|
||||
# (pci-serial: needs the virt.fragment kernel)
|
||||
@@ -23,7 +23,7 @@
|
||||
# --api-port N hostfwd 127.0.0.1:N -> guest :28443 (default 28443; 0 disables)
|
||||
# --shell interactive shell in the guest instead of daemon hold
|
||||
# --allow-apply let flared ACTUALLY apply OTA firmware (writes rootfs_b
|
||||
# inside disk.img — safe in the VM, never the default)
|
||||
# inside disk.img, safe in the VM, never the default)
|
||||
set -euo pipefail
|
||||
|
||||
QEMU_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -63,7 +63,7 @@ if [ -z "$KERNEL" ] || [ ! -f "$KERNEL" ]; then
|
||||
exit 1
|
||||
fi
|
||||
[ -f "$INITRD" ] || {
|
||||
echo "FATAL: initramfs not found at $INITRD — run qemu/mkinitramfs.sh" >&2
|
||||
echo "FATAL: initramfs not found at $INITRD: run qemu/mkinitramfs.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$SLOT" in _a|_b) ;; *) echo "FATAL: --slot must be _a or _b" >&2; exit 1 ;; esac
|
||||
@@ -72,14 +72,14 @@ if [ "$NO_DISK" -eq 0 ] && [ -z "$DISK" ] && [ -f "$OUT/disk.img" ]; then
|
||||
DISK="$OUT/disk.img"
|
||||
fi
|
||||
if [ -n "$DISK" ] && [ ! -f "$DISK" ]; then
|
||||
echo "FATAL: disk image $DISK not found — run qemu/mkimage.sh (or pass --no-disk)" >&2
|
||||
echo "FATAL: disk image $DISK not found: run qemu/mkimage.sh (or pass --no-disk)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NOTE: never add `earlyprintk` — the config's DEBUG_UART_PHYS is the RV1106's
|
||||
# NOTE: never add `earlyprintk`: the config's DEBUG_UART_PHYS is the RV1106's
|
||||
# 0xff4c0000, which does not exist on -M virt.
|
||||
APPEND="console=ttyAMA0 rdinit=/init"
|
||||
# Port 0 disables a forward — a boot smoke needs no host ports and must not
|
||||
# Port 0 disables a forward. A boot smoke needs no host ports and must not
|
||||
# fail on a busy default port.
|
||||
NETDEV="user,id=n0"
|
||||
[ "$SSH_PORT" != 0 ] && NETDEV="$NETDEV,hostfwd=tcp:127.0.0.1:${SSH_PORT}-:22"
|
||||
|
||||
@@ -11,16 +11,16 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
|
||||
|
||||
ZIMAGE="${1:-}"
|
||||
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||
echo "FATAL: usage: $0 <zImage> [initramfs] — zImage missing or not a file: '${ZIMAGE:-}'" >&2
|
||||
echo "FATAL: usage: $0 <zImage> [initramfs]: zImage missing or not a file: '${ZIMAGE:-}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
INITRD="${2:-$QDIR/out/initramfs.cpio.gz}"
|
||||
[ -f "$INITRD" ] || {
|
||||
echo "FATAL: initramfs not found at $INITRD — run qemu/mkinitramfs.sh first" >&2
|
||||
echo "FATAL: initramfs not found at $INITRD: run qemu/mkinitramfs.sh first" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH (apt-get install qemu-system-arm) — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH (apt-get install qemu-system-arm): see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ trap 'rm -f "$LOG"' EXIT
|
||||
|
||||
# Delegate the qemu invocation to run.sh (--no-disk) so the machine shape
|
||||
# (-M virt,highmem=off, cpu, memory, virtio topology) lives in exactly one
|
||||
# place — the two hand-copied invocations had already drifted once.
|
||||
# place: the two hand-copied invocations had already drifted once.
|
||||
# timeout -k: a wedged qemu that ignores SIGTERM gets SIGKILLed 10s later
|
||||
# instead of holding the job until the workflow-level timeout.
|
||||
timeout -k 10 180 bash "$QDIR/run.sh" \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Clock-sanity scenario (issue #3 regression guard): boot the VM, run the
|
||||
# musl-static clockprobe in the guest, and assert the vDSO monotonic RATE
|
||||
# matches the kernel's /proc/uptime within 1%. Under QEMU -M virt this passes
|
||||
# on the current kernel (measured 0.99963) — a regression here means the
|
||||
# on the current kernel (measured 0.99963): a regression here means the
|
||||
# generic vDSO path broke. The RV1106 *board* leg of issue #3 is a separate,
|
||||
# bench-only measurement; this scenario cannot see board-specific CNTFRQ or
|
||||
# CNTVOFF misprogramming.
|
||||
@@ -21,7 +21,7 @@ if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||
exit 1
|
||||
fi
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v arm-linux-gnueabihf-gcc >/dev/null || {
|
||||
@@ -58,7 +58,7 @@ for _attempt in 1 2 3; do
|
||||
sleep 3
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && break
|
||||
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||
echo "== hostfwd port collision on base $PORT — retrying"
|
||||
echo "== hostfwd port collision on base $PORT, retrying"
|
||||
QEMU_PID=""
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# FULL OTA apply scenario — the loop the desk e2e stops short of: the real
|
||||
# FULL OTA apply scenario (the loop the desk e2e stops short of): the real
|
||||
# flared inside the VM downloads a real signed tier-1 .wfw whose payload is a
|
||||
# bootable rootfs, verifies it, and ACTUALLY WRITES rootfs_b (safe: it is a
|
||||
# region inside disk.img); the harness then reboots into slot _b and asserts
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
# Documented emulation gaps (ADR-0006): the BCB slot CHOICE and the physical
|
||||
# reset are performed by the harness (cmdline slot + a fresh qemu boot), not
|
||||
# by U-Boot/CRU — those stay bench territory.
|
||||
# by U-Boot/CRU. Those stay bench territory.
|
||||
#
|
||||
# FAILS CLOSED on missing prerequisites.
|
||||
#
|
||||
@@ -22,7 +22,7 @@ PATH="$PATH:/usr/sbin:/sbin"
|
||||
|
||||
ZIMAGE="${1:-}"
|
||||
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||
echo "FATAL: usage: $0 <zImage> — the virt.fragment kernel variant" >&2
|
||||
echo "FATAL: usage: $0 <zImage>: the virt.fragment kernel variant" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]; then
|
||||
@@ -34,7 +34,7 @@ fi
|
||||
exit 1
|
||||
}
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ API_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(24))')"
|
||||
PORT=$((20000 + RANDOM % 20000))
|
||||
|
||||
# 0. The offer's payload is a REAL bootable rootfs: the same skeleton the
|
||||
# disk uses, stamped with the NEW version — booting it is the proof.
|
||||
# disk uses, stamped with the NEW version: booting it is the proof.
|
||||
QEMU_DIR="$QDIR"
|
||||
OUT="$QDIR/out"
|
||||
# shellcheck source=qemu/lib.sh disable=SC1091
|
||||
@@ -105,7 +105,7 @@ for _attempt in 1 2 3; do
|
||||
sleep 3
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && break
|
||||
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||
echo "== hostfwd port collision on base $VMBASE — retrying"
|
||||
echo "== hostfwd port collision on base $VMBASE, retrying"
|
||||
QEMU_PID=""
|
||||
continue
|
||||
fi
|
||||
@@ -117,7 +117,7 @@ if [ -z "$QEMU_PID" ] || ! kill -0 "$QEMU_PID" 2>/dev/null; then
|
||||
fi
|
||||
|
||||
# 4. wait for the apply to conclude. flared logs to its in-guest file, not
|
||||
# the console — but the next check-in REPORTS the outcome to the portal:
|
||||
# the console, but the next check-in REPORTS the outcome to the portal:
|
||||
# detail "hard reset failed after slot flip" is the exact post-apply state
|
||||
# under the gated reset (write done, AvbABData flipped, reboot refused).
|
||||
deadline=$((SECONDS + 420))
|
||||
@@ -140,7 +140,7 @@ grep -aq "GET /api/v1/devices/$DEVICE_ID/firmware/assets/.* -> 200" "$WORK/mock.
|
||||
echo "FATAL: staged without a portal asset download?!" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "== apply staged (asset downloaded, rootfs_b written, reset gated) — rebooting into _b"
|
||||
echo "== apply staged (asset downloaded, rootfs_b written, reset gated), rebooting into _b"
|
||||
kill "$QEMU_PID" 2>/dev/null || true
|
||||
wait "$QEMU_PID" 2>/dev/null || true
|
||||
QEMU_PID=""
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end device scenario: the REAL warden-flared, running inside the VM,
|
||||
# checks in to flare-edge's mock FLARE portal on the host and pulls its
|
||||
# firmware desired-state — the exact device-initiated HTTPS(-shaped) flow a
|
||||
# firmware desired-state: the exact device-initiated HTTPS(-shaped) flow a
|
||||
# panel performs, with zero flare-edge code changes (the portal URL is a state
|
||||
# file; 10.0.2.2 is slirp's host alias).
|
||||
#
|
||||
# FAILS CLOSED on every missing prerequisite — never a soft skip.
|
||||
# FAILS CLOSED on every missing prerequisite: never a soft skip.
|
||||
#
|
||||
# Usage: portal-scenario.sh <zImage-virt>
|
||||
# Env: FLARE_EDGE path to a flare-edge checkout (provides mock-flare-portal.py)
|
||||
@@ -16,7 +16,7 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
|
||||
|
||||
ZIMAGE="${1:-}"
|
||||
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||
echo "FATAL: usage: $0 <zImage> — the virt.fragment kernel variant" >&2
|
||||
echo "FATAL: usage: $0 <zImage>: the virt.fragment kernel variant" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]; then
|
||||
@@ -24,11 +24,11 @@ if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]
|
||||
exit 1
|
||||
fi
|
||||
[ -x "$QDIR/payload/warden-flared" ] || {
|
||||
echo "FATAL: no qemu/payload/warden-flared — build a static musl armv7 flared (see qemu/payload/README.md)" >&2
|
||||
echo "FATAL: no qemu/payload/warden-flared: build a static musl armv7 flared (see qemu/payload/README.md)" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ FW_SIGNING_KEY_FILE="$FLARE_EDGE/tools/testdata/fw-dev-key.seed" \
|
||||
WARDEN_UBOOT_VERSION=2017.09 \
|
||||
bash "$FLARE_EDGE/tools/mk-wfw.sh" "$WORK/rootfs-payload.img" 1 0.0.2 "$WORK/offer.wfw"
|
||||
|
||||
# 1. mock portal on the host, our device pre-registered (no pairing needed —
|
||||
# 1. mock portal on the host, our device pre-registered (no pairing needed:
|
||||
# the same credential-seeding shortcut fw-e2e-test.sh uses), offering the .wfw.
|
||||
python3 "$FLARE_EDGE/tools/mock-flare-portal.py" \
|
||||
--port "$PORT" --device "$DEVICE_ID:$API_KEY" \
|
||||
@@ -79,7 +79,7 @@ echo "== mock portal on :$PORT, device $DEVICE_ID"
|
||||
|
||||
# 2. image seeded with the portal URL + credentials.
|
||||
bash "$QDIR/mkinitramfs.sh"
|
||||
# All four enrolment keys — flare::enrolment() returns None (and the report
|
||||
# All four enrolment keys: flare::enrolment() returns None (and the report
|
||||
# loop parks forever) unless flare.site is present too.
|
||||
bash "$QDIR/mkimage.sh" \
|
||||
--portal-url "http://10.0.2.2:$PORT" \
|
||||
@@ -88,7 +88,7 @@ bash "$QDIR/mkimage.sh" \
|
||||
--state "flare.site=qemu-devsim"
|
||||
|
||||
# 3. boot the VM headless (daemons run; console log to file). Random hostfwd
|
||||
# ports can collide with another process — detect the early qemu bind
|
||||
# ports can collide with another process. Detect the early qemu bind
|
||||
# failure and retry with a fresh base rather than failing spuriously.
|
||||
QEMU_PID=""
|
||||
for _attempt in 1 2 3; do
|
||||
@@ -103,7 +103,7 @@ for _attempt in 1 2 3; do
|
||||
break
|
||||
fi
|
||||
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||
echo "== hostfwd port collision on base $VMBASE — retrying"
|
||||
echo "== hostfwd port collision on base $VMBASE, retrying"
|
||||
QEMU_PID=""
|
||||
continue
|
||||
fi
|
||||
@@ -116,7 +116,7 @@ if [ -z "$QEMU_PID" ] || ! kill -0 "$QEMU_PID" 2>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. assert: rootfs up, and the portal saw — from OUR device id — an
|
||||
# 4. assert: rootfs up, and the portal saw (from OUR device id) an
|
||||
# authenticated check-in, the firmware desired-state pull, and the signed
|
||||
# .wfw asset download (i.e. flared accepted the offer and fetched it; the
|
||||
# verify+stage+APPLYING that follow are a dry run without
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# RV1106-only init steps degrade on virt (backlight, goodix, npu, the 5.10
|
||||
# /oem modules fail vermagic), and binaries older than the flare-edge #106
|
||||
# fix reproduce that crash faithfully. Interactive login uses the image's own
|
||||
# credentials — deliberately not recorded here.
|
||||
# credentials: deliberately not recorded here.
|
||||
#
|
||||
# FAILS CLOSED on missing prerequisites.
|
||||
#
|
||||
@@ -21,12 +21,12 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
|
||||
ZIMAGE="${1:-}"; ROOTFS="${2:-}"; OEM="${3:-}"
|
||||
for f in "$ZIMAGE" "$ROOTFS" "$OEM"; do
|
||||
if [ -z "$f" ] || [ ! -f "$f" ]; then
|
||||
echo "FATAL: usage: $0 <zImage> <rootfs.img> <oem.img> — '$f' missing" >&2
|
||||
echo "FATAL: usage: $0 <zImage> <rootfs.img> <oem.img>: '$f' missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ for _attempt in 1 2 3; do
|
||||
sleep 3
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && break
|
||||
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||
echo "== hostfwd port collision on base $PORT — retrying"
|
||||
echo "== hostfwd port collision on base $PORT, retrying"
|
||||
QEMU_PID=""
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Display + touch scenario: boot the VM headless with virtio-gpu, wait for the
|
||||
# LVGL UI (fbdev build) to render a real frame, then inject an absolute touch
|
||||
# tap on the Metrics tab (virtio-tablet) and ASSERT the frame changed — the
|
||||
# tap on the Metrics tab (virtio-tablet) and ASSERT the frame changed: the
|
||||
# device-level analogue of flare-edge's Xvfb/xdotool sim-test.sh. Readiness is
|
||||
# polled from screendumps on bounded deadlines, never guessed with fixed
|
||||
# sleeps: TCG renders CPU-bound and a loaded host can be arbitrarily slow.
|
||||
@@ -17,15 +17,15 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
|
||||
ZIMAGE="${1:-}"
|
||||
OUTDIR="${2:-$QDIR/out}"
|
||||
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||
echo "FATAL: usage: $0 <zImage> [out-dir] — the virt.fragment kernel variant" >&2
|
||||
echo "FATAL: usage: $0 <zImage> [out-dir]: the virt.fragment kernel variant" >&2
|
||||
exit 1
|
||||
fi
|
||||
[ -x "$QDIR/payload/warden-ui" ] || {
|
||||
echo "FATAL: no qemu/payload/warden-ui — build it with flare-edge tools/build-ui-vm.sh" >&2
|
||||
echo "FATAL: no qemu/payload/warden-ui: build it with flare-edge tools/build-ui-vm.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v qemu-system-arm >/dev/null || {
|
||||
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ trap cleanup EXIT
|
||||
bash "$QDIR/mkinitramfs.sh"
|
||||
bash "$QDIR/mkimage.sh"
|
||||
|
||||
# Random hostfwd ports can collide — detect qemu's early bind failure and
|
||||
# Random hostfwd ports can collide. Detect qemu's early bind failure and
|
||||
# retry with a fresh base rather than failing spuriously.
|
||||
for _attempt in 1 2 3; do
|
||||
PORT=$((21000 + RANDOM % 20000))
|
||||
@@ -53,7 +53,7 @@ for _attempt in 1 2 3; do
|
||||
sleep 3
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && break
|
||||
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||
echo "== hostfwd port collision on base $PORT — retrying"
|
||||
echo "== hostfwd port collision on base $PORT, retrying"
|
||||
QEMU_PID=""
|
||||
continue
|
||||
fi
|
||||
@@ -96,7 +96,7 @@ EOF
|
||||
|
||||
# The VM can die mid-poll (OOM, crash): check liveness before every QMP
|
||||
# call so the failure is OUR message + console evidence, not a python
|
||||
# traceback — and preserve the console log before the trap removes $WORK.
|
||||
# traceback, and preserve the console log before the trap removes $WORK.
|
||||
vm_alive_or_die() {
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && return 0
|
||||
echo "FATAL: VM exited during the screendump poll" >&2
|
||||
@@ -121,7 +121,7 @@ done
|
||||
}
|
||||
|
||||
# Tap the "Metrics" tab: pixel (373,40) of 720x720 scaled to the QMP absolute
|
||||
# range 0..32767 — switching tabs must repaint the content area. Poll for the
|
||||
# range 0..32767. Switching tabs must repaint the content area. Poll for the
|
||||
# repaint rather than guessing a delay.
|
||||
vm_alive_or_die
|
||||
qmp tap 16975 1820
|
||||
@@ -142,7 +142,7 @@ cp "$WORK/shot1.ppm" "$OUTDIR/ui-shot1.ppm"
|
||||
cp "$WORK/shot2.ppm" "$OUTDIR/ui-shot2.ppm" 2>/dev/null || true
|
||||
|
||||
[ "$changed" = 1 ] || {
|
||||
echo "FATAL: tapping the Metrics tab did not change the frame within 90s — touch is not reaching the UI" >&2
|
||||
echo "FATAL: tapping the Metrics tab did not change the frame within 90s: touch is not reaching the UI" >&2
|
||||
cp "$WORK/console.log" "$OUTDIR/ui-shot-console.log" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user