sysupgrade: arm bootloader boot-count recovery, and disarm it once healthy - #2391
Conversation
…althy A U-Boot that supports boot-count recovery (OpenIPC/u-boot-gk7205v200) can reflash a known-good image when a freshly written one never reaches a working userspace -- the failure mode behind a field brick, where an interrupted upgrade left an image that a soldering iron was the only way back from. But the bootloader half is inert until userspace tells it a flash is in flight and, later, that the flash worked. This adds those two signals: - sysupgrade sets upgrade_available=1 (and bootcount=0) just before the flash, on the still-normal system before the ramfs pivot moves the MTD env out of reach. It also drops a saved verify=n key, so a camera that once ran the pre-verify U-Boot gets its kernel CRC-checked again. - S99bootok, a new init hook, disarms it (upgrade_available=0) once the camera is healthy -- majestic up, or no majestic on the image at all. If majestic never comes up it leaves the flag set, so the bootloader escalates to recovery on the following boots instead of looping on an image that boots but does not stream. It runs its wait in the background so a slow or failing start never holds up boot, and an unarmed (normal) boot costs a single fw_printenv and writes nothing. Safe on cameras with an older OpenIPC or a vendor-supplied U-Boot that has no boot-count logic: upgrade_available and bootcount are then just unused environment variables it ignores, so arming and disarming them changes nothing about how those cameras boot. The recovery only activates where the bootloader knows what the flag means. Hardware tested on: gk7205v200 (lab). Verified the arm -> disarm cycle on the running camera -- fw_setenv upgrade_available 1, then S99bootok with majestic up clears it to 0; an unarmed run is a no-op; the camera boots normally throughout (its U-Boot predates the boot-count logic, so the vars are inert, which is the point). The bootloader escalation the flag drives is verified in QEMU on the u-boot side; the end-to-end on a camera running the boot-count U-Boot is the remaining integration step. Evidence: # armed upgrade_available=1 bootcount=0 # S99bootok start (majestic up) after: upgrade_available=0 bootcount=0 # unarmed re-run: no write still: upgrade_available=0
PR Summary by QodoAdd boot-count recovery arming and healthy-boot disarming
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
Four robustness fixes from review of the arm/disarm pair: - S99bootok required only a single `pidof majestic` to declare the image healthy, but majestic is visible to pidof the moment S95majestic forks, before its pipeline is up -- a streamer that crashed during startup would be seen "healthy" for an instant and disarm recovery. Now it requires majestic to stay up across a sustained window (8 consecutive checks, ~16s); if it dies the window restarts, and if it never holds the flag stays armed so U-Boot escalates. (findings 1) - S99bootok ignored the fw_setenv result and exited 0 regardless, so a camera whose env is readable but not writable would silently leave the flag set on every healthy boot with no diagnostic. It now logs (logger -s) when the disarm write fails, and when majestic never stays up. (finding 4) - sysupgrade armed before the flash but never cleared the flag on an abort that wrote nothing. die() now drops the arm on its no-write path (nothing touched, camera unchanged). A failure after enter_ramfs still reboots, and S99bootok disarms on the next healthy boot -- bootcount advances only one per reboot and the limit is several, so an aborted upgrade never escalates falsely. (finding 2) - the arming fw_setenv calls were silent; they now warn if the recovery arm cannot be set, or if a stale verify=n cannot be cleared, so the operator is not left thinking recovery is in place when it is not. (finding 3) Re-verified on gk7205v200: the sustained-health disarm holds the flag until majestic is stably up, then clears it; an unarmed boot is still a no-op. Shell-parse and strip-comment gates pass.
…sh log Builds the recovery loop on top of the pstore region (#2392) and the bootcount arm/disarm (#2391), converting the counter off the NOR env. - Move the boot counter from the U-Boot env to a single DRAM word at 0x41f20000 (a no-map reserved region, board DTS): S99bootok clears it through /dev/mem once majestic is proven healthy, and sysupgrade arms it the same way. Writing the env every boot is NOR wear and a mid-write brick risk; a healthy boot now writes zero flash. On a U-Boot/kernel without the region the write lands in spare reserved RAM -- harmless. - rcS gains a failsafe branch: when U-Boot appends "failsafe" to the cmdline after bootlimit failed boots, bring up only logging, mdev, networking and SSH -- skip the SDK and the streamer -- so a crashlooping camera lands reachable instead of looping. The overlay stays mounted (claim intact); the counter is reset on entry so failsafe does not re-escalate. - S98crashlog preserves a crashed boot's pstore log into /etc/crash on the next normal boot (gzipped, capped to the latest, pstore ring then freed) and rcS drops a breadcrumb when it fell into failsafe, for the WebUI to offer the owner. Overlay-only, no flash on a clean boot. Proven on hardware, both allocators: gk7205v200 (64M) and gk7205v300 (128M) -- crashloop escalates to failsafe and self-recovers, pstore panic is harvested, counter resets on a healthy boot.
The problem
A U-Boot with boot-count recovery (the gk7205v200 bootloader just gained it) can reflash a known-good image when a freshly-written one never reaches a working userspace — the field brick where an interrupted upgrade left an image only a UART reflash could recover. But the bootloader half is inert until userspace tells it (a) a flash is in flight and (b), later, that the flash actually worked. This adds those two signals.
Change
sysupgradesetsupgrade_available=1(andbootcount=0) just before the flash — on the still-normal system, before the ramfs pivot moves the MTD env out of reach. It also drops any savedverify=nso a camera that once ran the pre-verify U-Boot gets its kernel CRC-checked again.S99bootok(new init hook) disarms it (upgrade_available=0) once the camera is healthy — majestic up, or no majestic on the image at all. If majestic never comes up it leaves the flag set, so the bootloader escalates to recovery on the following boots instead of looping on an image that boots but doesn't stream. The wait runs in the background (never holds up boot); an unarmed boot is a singlefw_printenvwith no write.Safe on older / vendor U-Boot
On a camera whose U-Boot has no boot-count logic,
upgrade_availableandbootcountare just unused env vars it ignores — arming/disarming them changes nothing about how those cameras boot. The recovery only activates where the bootloader knows what the flag means. This is the coupled userspace half of the gk7205v200 bootloader boot-count escalation.Hardware tested on
gk7205v200 (lab). The camera's U-Boot predates the boot-count logic, so the env vars are inert there — which is exactly the "safe on old U-Boot" case. Verified the arm → disarm cycle on the running camera:
The camera booted and streamed normally throughout. The bootloader escalation this flag drives is verified in QEMU on the u-boot side; the full end-to-end on a camera running the boot-count U-Boot is the remaining integration step.
Scope
Shared overlay (
sysupgrade+ a newS99bootok), so it builds for every board;ci-matrix --self-testpasses andtest_shell_parse/test_strip_shell_commentspass (140 scripts, incl. these, parse clean stripped). Behaviour is inert on every board until a boot-count-capable U-Boot is present.