diff --git a/.gitignore b/.gitignore index cba758a..a79b0ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ # Generated flash image: 2 MB, rebuilt from calibration.bin by tools/make_flash.py. +# The live image is a build artifact and the emulator writes to it. assets/flash.img +assets/flash.img.bak-* +# ...but assets/pristine/ is tracked on purpose: it is the never-booted +# reference to fall back to when a session leaves the image in a bad state. +!assets/pristine/ # Host build output for the CW timing harness. build/ diff --git a/README.md b/README.md index 92c44bd..cbbd769 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ keypresses silently stop working. Run the test after touching that code; qemu/ QEMU sources to be copied into a QEMU tree py32f071.c the SoC and machine (the bulk of the work) armv7m_systick.*.patched SysTick with the poll-boost property added - assets/ + assets/ flash.img, plus pristine/ as the reference copy calibration.bin 512-byte dump from a real radio deploy/ nginx vhost for the HTTPS front end docs/reverse-proxy.md how https://k6v3.mckero.dn42/ is served @@ -74,6 +74,7 @@ keypresses silently stop working. Run the test after touching that code; keypad_test.py keypad regression test, boots its own instance webui.py web remote control: live LCD plus clickable keypad dn42_firewall.sh restrict the web UI port to DN42 sources + restore_flash.sh roll the flash image back to its pristine state uvk5_qmp.py QMP client uvk5_lcd.py framebuffer decode, PNG encode, frame grabber uvk5_keys.py key names the keypad model accepts @@ -124,6 +125,19 @@ The rest of the tests: ## Running python3 tools/make_flash.py # once, builds assets/flash.img + +The emulator writes to that image, so a session can leave edited settings or a +damaged EEPROM behind. `assets/pristine/` holds a checksummed copy of the image as +first generated, and `tools/restore_flash.sh` puts it back: + + tools/restore_flash.sh --verify # is the reference copy itself intact + tools/restore_flash.sh --diff # has the live image changed, and by how much + tools/restore_flash.sh # restore, saving the current image first + +The reference copy is stored gzipped, which takes 2.3 KiB rather than 2 MiB because +the image is nearly all 0xFF, so it is small enough to keep in git. The live image +stays ignored: it is a build artifact that gets written to. + tools/run.sh # starts the machine tools/where.sh # where the firmware is executing diff --git a/assets/pristine/flash-pristine.img.gz b/assets/pristine/flash-pristine.img.gz new file mode 100644 index 0000000..36fdf60 Binary files /dev/null and b/assets/pristine/flash-pristine.img.gz differ diff --git a/assets/pristine/flash-pristine.img.sha256 b/assets/pristine/flash-pristine.img.sha256 new file mode 100644 index 0000000..07186b7 --- /dev/null +++ b/assets/pristine/flash-pristine.img.sha256 @@ -0,0 +1,2 @@ +933d697426baee836ff15a6eea6ab7c23f98be3d73e3c5ec9957f6818f393d62 flash-pristine.img +d8e7551c75c1f85681d9f98a36b0f6d7f287b8d4c599b1475b271a292cd2d0d8 flash-pristine.img.gz diff --git a/tools/restore_flash.sh b/tools/restore_flash.sh new file mode 100755 index 0000000..fe8316e --- /dev/null +++ b/tools/restore_flash.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Restore assets/flash.img to its pristine, never-booted state. +# +# The emulator writes to the flash image, so a session can leave settings, edited +# frequencies, or a corrupted EEPROM behind. assets/pristine/ holds a checksummed +# copy of the image as first generated, so there is always a known-good state to +# come back to. +# +# Usage: +# tools/restore_flash.sh # restore, backing up the current image +# tools/restore_flash.sh --verify # only check the pristine copy is intact +# tools/restore_flash.sh --diff # show whether the live image has changed +set -euo pipefail + +SIM="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +LIVE="$SIM/assets/flash.img" +PRISTINE_GZ="$SIM/assets/pristine/flash-pristine.img.gz" +SUMS="$SIM/assets/pristine/flash-pristine.img.sha256" + +want_plain() { awk '$2 == "flash-pristine.img" {print $1}' "$SUMS"; } +want_gz() { awk '$2 == "flash-pristine.img.gz" {print $1}' "$SUMS"; } + +verify_pristine() { + [ -f "$PRISTINE_GZ" ] || { echo "missing $PRISTINE_GZ" >&2; exit 1; } + local got_gz got_plain + got_gz=$(sha256sum "$PRISTINE_GZ" | awk '{print $1}') + if [ "$got_gz" != "$(want_gz)" ]; then + echo "pristine archive is CORRUPT" >&2 + echo " expected $(want_gz)" >&2 + echo " actual $got_gz" >&2 + exit 1 + fi + got_plain=$(gzip -dc "$PRISTINE_GZ" | sha256sum | awk '{print $1}') + if [ "$got_plain" != "$(want_plain)" ]; then + echo "pristine contents do not match their checksum" >&2 + exit 1 + fi + echo "pristine copy verified ($(want_plain))" +} + +case "${1:-restore}" in +--verify) + verify_pristine + ;; +--diff) + verify_pristine + if [ ! -f "$LIVE" ]; then + echo "no live image at $LIVE" + exit 0 + fi + live=$(sha256sum "$LIVE" | awk '{print $1}') + if [ "$live" = "$(want_plain)" ]; then + echo "live image is unchanged from pristine" + else + echo "live image HAS CHANGED from pristine" + echo " pristine $(want_plain)" + echo " live $live" + gzip -dc "$PRISTINE_GZ" > /tmp/.pristine.$$ + echo " differing bytes: $(cmp -l /tmp/.pristine.$$ "$LIVE" 2>/dev/null | wc -l)" + rm -f /tmp/.pristine.$$ + fi + ;; +restore) + verify_pristine + if [ -f "$LIVE" ]; then + backup="$LIVE.bak-$(date +%Y%m%d-%H%M%S)" + cp -a "$LIVE" "$backup" + echo "current image saved to $(basename "$backup")" + fi + gzip -dc "$PRISTINE_GZ" > "$LIVE" + got=$(sha256sum "$LIVE" | awk '{print $1}') + [ "$got" = "$(want_plain)" ] || { echo "restore verification FAILED" >&2; exit 1; } + echo "restored $LIVE to pristine state" + echo "note: the emulator reads the image at boot, so power-cycle to pick it up" + ;; +*) + echo "usage: $0 [restore|--verify|--diff]" >&2 + exit 2 + ;; +esac