review: iteration-2 fixes (fragment assertion, evidence paths, hardening)
- build-kernel.sh fragment assertion: survives a missing trailing newline (the read footgun, reproduced) and treats an absent symbol on a disable line as FATAL, symmetric with the enable arm. - fetch-kernel-tarball.sh checks the pin before downloading; both fetchers add --retry-connrefused. - mkimage rejects '.'/'..' state keys. - ui-shot: VM liveness checked before every QMP call, console.log preserved as evidence on every failure path, repaint deadline widened to 90s with the contended-runner rationale documented. - rs485-bridge: overflow discards back off one gap and rate-limit their log line, mirroring the accept-loop fix; clippy nit fixed. - .gitignore ignores *.elf/*.map so the untracked artifacts cannot silently return; CI shellcheck glob now covers build/ and the rootfs boot scripts (directives added for the deliberate in-guest source paths). - Docs: NPU parity row matches its sibling verification docs; line-pinned audit cross-references unpinned; CROSS_COMPILE documented in the build header; payload README lists warden-ui; ci-cd tense settled. Verified: guards negative-tested (bad state keys, no-newline fragment); boot smoke, portal scenario, ui-shot all PASS; 53 tests green; shellcheck clean across the widened glob; clippy zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
This commit is contained in:
co-authored by
Claude Fable 5
parent
2756de0b46
commit
973a414f07
@@ -108,7 +108,9 @@ jobs:
|
||||
- name: shellcheck qemu scripts
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
|
||||
shellcheck -x qemu/*.sh qemu/tests/*.sh
|
||||
shellcheck -x qemu/*.sh qemu/tests/*.sh build/*.sh \
|
||||
qemu/rootfs/etc/warden-lib.sh qemu/rootfs/etc/rc \
|
||||
qemu/rootfs/sbin/init qemu/rootfs/init
|
||||
- name: cache pinned busybox
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
@@ -6,6 +6,8 @@ target/
|
||||
# build artifacts (firmware/kernel objects are rebuilt from source)
|
||||
*.o
|
||||
*.a
|
||||
*.elf
|
||||
*.map
|
||||
|
||||
# scratch / editor
|
||||
*.swp
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
# Env:
|
||||
# KERNEL_TARBALL path to a local linux-6.18.46.tar.xz (skips the download)
|
||||
# SDK_TC dir holding the arm-rockchip830 uclibc cross toolchain bin/
|
||||
# CROSS_COMPILE cross-compiler prefix (default arm-rockchip830-linux-uclibcgnueabihf-;
|
||||
# CI overrides with the generic arm-linux-gnueabihf-)
|
||||
# WORK build scratch dir (default: a mktemp under $TMPDIR)
|
||||
# JOBS parallel make jobs (default: nproc)
|
||||
# WARDEN_KCONFIG_FRAGMENT
|
||||
@@ -119,7 +121,7 @@ make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" olddefconfig >/dev/null
|
||||
# request in the fragment survived into the final .config; fail loud if not.
|
||||
if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
|
||||
frag_fail=0
|
||||
while IFS= read -r line; do
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
CONFIG_*=*)
|
||||
grep -qxF "$line" "$SRC/.config" || {
|
||||
@@ -127,9 +129,11 @@ if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
|
||||
frag_fail=1
|
||||
} ;;
|
||||
"# CONFIG_"*" is not set")
|
||||
opt="${line#\# }"; opt="${opt% is not set}"
|
||||
grep -qE "^$opt=" "$SRC/.config" && {
|
||||
echo "FATAL: fragment disabled '$opt' but it is set in the final .config" >&2
|
||||
# Symmetric with the enable arm: the exact disable line must be
|
||||
# present. A symbol absent entirely means a typo'd/renamed option,
|
||||
# not a successful disable.
|
||||
grep -qxF "$line" "$SRC/.config" || {
|
||||
echo "FATAL: fragment line '$line' not reflected in the final .config" >&2
|
||||
frag_fail=1
|
||||
} ;;
|
||||
esac
|
||||
|
||||
@@ -14,14 +14,16 @@ URL="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$KVER.tar.xz"
|
||||
|
||||
TB="${1:?usage: fetch-kernel-tarball.sh <destination-path>}"
|
||||
|
||||
if [ ! -f "$TB" ]; then
|
||||
echo "== downloading $URL"
|
||||
curl --retry 3 --retry-delay 5 -fSL "$URL" -o "$TB"
|
||||
fi
|
||||
# Pin first: a forgotten pin on a KVER bump should refuse BEFORE burning a
|
||||
# 140MB download it will then reject anyway.
|
||||
[ -f "$SHA_FILE" ] || {
|
||||
echo "FATAL: no pinned sha256 for linux-$KVER (expected $SHA_FILE) — refusing an unverified tarball" >&2
|
||||
exit 1
|
||||
}
|
||||
if [ ! -f "$TB" ]; then
|
||||
echo "== downloading $URL"
|
||||
curl --retry 3 --retry-delay 5 --retry-connrefused -fSL "$URL" -o "$TB"
|
||||
fi
|
||||
want="$(cat "$SHA_FILE")"
|
||||
got="$(sha256sum "$TB" | awk '{print $1}')"
|
||||
[ "$want" = "$got" ] || { echo "FATAL: tarball sha256 mismatch: want $want got $got" >&2; exit 1; }
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
`.github/workflows/ci.yml` — every job runs on GitHub-hosted `ubuntu-latest`.
|
||||
No self-hosted runner is (or may be) reachable from this repo's workflows:
|
||||
the repo is going public, and a fork PR that gets one approved run could
|
||||
the repo is public, and a fork PR that gets one approved run could
|
||||
otherwise execute code on private infrastructure (ADR-0007).
|
||||
|
||||
## Jobs
|
||||
|
||||
@@ -38,7 +38,7 @@ c8a3, not just compiled.
|
||||
| GPIO_SYSFS / crypto / CFG80211 | — | mainline (config) | [x] =y (batch2) |
|
||||
| AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | [x] M5 — wlan0 up, scanned the site AP at −43dBm (modules, `wifi/VERIFIED-on-c8a3.md`) |
|
||||
| AIC8800 BT (btlpm) | aic8800_btlpm | **out-of-tree** | [wip] module built (6.18 vermagic); HCI bring-up not yet exercised |
|
||||
| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | [wip] M6 built, 0 errors/0 warnings, 99 `rknpu`-prefixed symbols in `System.map`, `&npu {status="okay"}` in the dtb — **not yet flashed/probed on hardware** (build-only session; see `npu/PORT-PROGRESS.md`) |
|
||||
| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | [x] open GPL driver VERIFIED on hardware — `/dev/dri/card1`, `rknpu_version_test` PASS (power/clock/reset path exercised); open *compute* (regcmd) remains a from-scratch RE project (`npu/VERIFIED.md`, `npu/OPEN-NPU-PLAN.md`) |
|
||||
| RGA 2D (rga2) | rga2 | ported (vendor char-dev) | [x] /dev/rga, hw 3.3.87975 |
|
||||
| I2S audio (i2s-tdm) | i2s | rv1126 fallback (=y) | [x] cpu DAI registers (part of the card below) |
|
||||
| Audio codec (acodec) | rockchip,rv1106-codec | ported (rv1106_codec.c) | [x] card `rv1106-acodec`, pcmC0D0p/c (`audio/`); audible test @ bench |
|
||||
|
||||
@@ -25,7 +25,7 @@ watchdog exactly as-is (different threat model, different job).
|
||||
- **Controller (Linux side): non-issue.** `drivers/mailbox/rockchip-mailbox.c` is
|
||||
upstream in mainline 6.18 and **already binds on our exact kernel** via the
|
||||
generic `rockchip,rk3368-mailbox` **fallback compatible** with **zero patching**
|
||||
— recorded in `CAPABILITIES-AUDIT.md:30`, confirmed by source read. RV1106's DT
|
||||
— recorded in `CAPABILITIES-AUDIT.md`'s Remaining-blocks table (mailbox row), confirmed by source read. RV1106's DT
|
||||
declares both instances with that fallback string. Gated today only by
|
||||
`status="disabled"` + `CONFIG_ROCKCHIP_MBOX` being absent from the defconfig.
|
||||
- **HPMCU firmware (MCU side): we already do the hard part.** WardenOS has a
|
||||
@@ -241,7 +241,7 @@ adopt rpmsg/virtio unless the payload complexity genuinely demands it.
|
||||
moot, since our `hpmcu.rs` is an independent hardware-validated reimplementation.
|
||||
|
||||
---
|
||||
_Cross-refs: `../CAPABILITIES-AUDIT.md:30`, `../REMAINING-PORTS.md §7`,
|
||||
_Cross-refs: `../CAPABILITIES-AUDIT.md`, `../REMAINING-PORTS.md §7`,
|
||||
`../../luckfox-pico-86-panel/riscv-mcu.md`,
|
||||
`.../raw/followup-riscv-mcu.md`,
|
||||
`flare-edge/major-app-additions/docs/decisions/0002-hpmcu-watchdog.md`,
|
||||
|
||||
@@ -197,7 +197,8 @@ the whole problem.
|
||||
|
||||
`PROVENANCE.md`: the kernel driver is portable GPL; the closed piece is the
|
||||
userspace RKNN runtime + regcmd format (a blob). **Per directive we do not ship
|
||||
that blob.** `CAPABILITIES-AUDIT.md:32` rates NPU "not worth shipping" until an
|
||||
that blob.** `CAPABILITIES-AUDIT.md`'s Remaining-blocks table rates the NPU
|
||||
"open driver VERIFIED; compute deferred" until an
|
||||
open encoder exists.
|
||||
|
||||
### URLs
|
||||
@@ -299,5 +300,5 @@ ever begun; otherwise this is the documented reason open NPU compute is deferred
|
||||
|
||||
---
|
||||
_Cross-refs: `PORT-PLAN.md` (authoritative file-by-file kernel port),
|
||||
`../../docs/npu-graphics-feasibility.md`, `../CAPABILITIES-AUDIT.md:32`,
|
||||
`../../docs/npu-graphics-feasibility.md`, `../CAPABILITIES-AUDIT.md`,
|
||||
`../PROVENANCE.md`, `../DRIVER-PARITY.md:41`, `../REMAINING-PORTS.md §6`._
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ qemu_get_busybox() {
|
||||
BB="${BUSYBOX:-$out/busybox-armv7l}"
|
||||
if [ ! -f "$BB" ]; then
|
||||
qemu_log "downloading $BB_URL"
|
||||
curl --retry 3 --retry-delay 5 -fSL "$BB_URL" -o "$BB"
|
||||
curl --retry 3 --retry-delay 5 --retry-connrefused -fSL "$BB_URL" -o "$BB"
|
||||
fi
|
||||
[ -f "$sha_file" ] || {
|
||||
echo "FATAL: no pinned sha256 for busybox (expected $sha_file) — refusing to build from an unverified binary" >&2
|
||||
|
||||
@@ -36,6 +36,9 @@ while [ $# -gt 0 ]; do
|
||||
*) echo "FATAL: --state needs KEY=VALUE, got '$2'" >&2; exit 1 ;;
|
||||
esac
|
||||
case "${2%%=*}" in
|
||||
.|..)
|
||||
echo "FATAL: --state key cannot be '.' or '..'" >&2
|
||||
exit 1 ;;
|
||||
*[!A-Za-z0-9_.]*|'')
|
||||
echo "FATAL: --state key '${2%%=*}' must match [A-Za-z0-9_.]+ (it becomes a filename)" >&2
|
||||
exit 1 ;;
|
||||
|
||||
@@ -19,6 +19,7 @@ Then:
|
||||
cp <flare-edge>/target/armv7-unknown-linux-musleabihf/release/warden-flared qemu/payload/
|
||||
```
|
||||
|
||||
Stage-2 init starts `warden-flared` and `warden-modbus` automatically when
|
||||
present (logs land in `/tmp/<name>.log` inside the guest). An empty payload is
|
||||
valid — the image boots to a busybox-only userspace.
|
||||
Stage-2 init starts `warden-flared`, `warden-modbus`, and `warden-ui` (the
|
||||
UI additionally 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.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
# exits and the kernel panics; the applet-existence check below catches the
|
||||
# only preventable variant of that.
|
||||
|
||||
# shellcheck source=qemu/rootfs/etc/warden-lib.sh disable=SC1091
|
||||
. /etc/warden-lib.sh
|
||||
|
||||
warden_populate_by_name
|
||||
|
||||
@@ -29,6 +29,7 @@ warden_populate_by_name() {
|
||||
# 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
|
||||
for tok in $(cat /proc/cmdline); do
|
||||
case "$tok" in
|
||||
warden.slot=*) slot="${tok#warden.slot=}" ;;
|
||||
|
||||
@@ -21,6 +21,7 @@ echo "WARDEN-QEMU-BOOT-OK"
|
||||
# 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
|
||||
. /etc/rc
|
||||
fi
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
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
|
||||
|
||||
@@ -61,6 +61,7 @@ pub fn pump_serial(
|
||||
stream.set_read_timeout(Some(gap))?;
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let mut chunk = [0u8; 256];
|
||||
let mut discards: u64 = 0;
|
||||
loop {
|
||||
match (&*stream).read(&mut chunk) {
|
||||
Ok(0) => {
|
||||
@@ -72,12 +73,20 @@ pub fn pump_serial(
|
||||
Ok(n) => {
|
||||
buf.extend_from_slice(&chunk[..n]);
|
||||
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.
|
||||
discards += 1;
|
||||
if discards == 1 || discards.is_multiple_of(256) {
|
||||
eprintln!(
|
||||
"rs485: {} bytes buffered with no inter-frame gap — discarding \
|
||||
(misbehaving master streaming continuously?)",
|
||||
buf.len()
|
||||
"rs485: {} bytes buffered with no inter-frame gap — \
|
||||
discarding (misbehaving master? {} discards so far)",
|
||||
buf.len(),
|
||||
discards
|
||||
);
|
||||
}
|
||||
buf.clear();
|
||||
std::thread::sleep(gap);
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
|
||||
+20
-2
@@ -94,16 +94,29 @@ sys.exit(0 if distinct > 16 else 1)
|
||||
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.
|
||||
vm_alive_or_die() {
|
||||
kill -0 "$QEMU_PID" 2>/dev/null && return 0
|
||||
echo "FATAL: VM exited during the screendump poll" >&2
|
||||
tail -25 "$WORK/console.log" >&2
|
||||
mkdir -p "$OUTDIR"; cp "$WORK/console.log" "$OUTDIR/ui-shot-console.log" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Poll for the first rendered frame (bounded, no guessed sleep).
|
||||
rendered=0
|
||||
deadline=$((SECONDS + 90))
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
vm_alive_or_die
|
||||
qmp screendump "$WORK/shot1.ppm"
|
||||
if frame_rendered "$WORK/shot1.ppm"; then rendered=1; break; fi
|
||||
sleep 3
|
||||
done
|
||||
[ "$rendered" = 1 ] || {
|
||||
echo "FATAL: UI never rendered a non-blank frame within 90s" >&2
|
||||
mkdir -p "$OUTDIR"; cp "$WORK/console.log" "$OUTDIR/ui-shot-console.log" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -112,9 +125,13 @@ done
|
||||
# repaint rather than guessing a delay.
|
||||
qmp tap 16975 1820
|
||||
changed=0
|
||||
deadline=$((SECONDS + 30))
|
||||
# 90s, matching the first-frame budget: TCG repaints are CPU-bound and a
|
||||
# contended CI runner can be arbitrarily slower than this dev box (same
|
||||
# margin reasoning as the rs485 test-gap widening).
|
||||
deadline=$((SECONDS + 90))
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
sleep 2
|
||||
vm_alive_or_die
|
||||
qmp screendump "$WORK/shot2.ppm"
|
||||
if ! cmp -s "$WORK/shot1.ppm" "$WORK/shot2.ppm"; then changed=1; break; fi
|
||||
done
|
||||
@@ -124,7 +141,8 @@ 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 30s — 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
|
||||
}
|
||||
echo "tap on the Metrics tab repainted the frame (touch reached the UI)"
|
||||
|
||||
Reference in New Issue
Block a user