recoveryboot: chain-load into the recovery partition on BCB request

The RPi firmware can't read the BCB in misc, so 'adb reboot recovery' /
factory reset never reached recovery: init writes boot-recovery and
reboots, the firmware ignores the reboot arg, and Android just boots
normally again.

pawlet-recoveryboot is a vendor oneshot (post-fs-data) that reads the
BCB command field (misc offset 0 — the HAL's PawletBootControl lives at
4096, no overlap) and, on boot-recovery, reboots with arg '3': the
firmware's PM_RSTS partition mechanism (same driver path as the
updater's tryboot) boots GPT partition 3 = recovery once. Stock
recovery reads its args from the untouched BCB, clears it when done,
and the next reboot is normal. A /metadata attempt counter clears the
BCB after 2 failed tries so an unbootable recovery can't wedge the
device.
This commit is contained in:
oxmc
2026-07-15 11:40:46 -07:00
parent 0933d1928d
commit 1e4b73a66c
4 changed files with 81 additions and 0 deletions
+8
View File
@@ -37,3 +37,11 @@ PRODUCT_PACKAGES += \
PawletUpdaterRpiOverlay \
PawletSettings \
SettingsProviderRpiCommonOverlay
# ---------------------------------------------------------------------------
# reboot-to-recovery chain-loader — RPi firmware can't read the BCB in misc,
# so a boot-time oneshot detects "boot-recovery" and reboots into the
# recovery partition (3) via the firmware's PM_RSTS partition mechanism.
# ---------------------------------------------------------------------------
PRODUCT_PACKAGES += \
pawlet-recoveryboot
+9
View File
@@ -0,0 +1,9 @@
// Copyright (C) 2026 oxmc / PawletOS
// SPDX-License-Identifier: Apache-2.0
sh_binary {
name: "pawlet-recoveryboot",
src: "pawlet-recoveryboot.sh",
vendor: true,
init_rc: ["pawlet-recoveryboot.rc"],
}
+11
View File
@@ -0,0 +1,11 @@
# Chain-load into the recovery partition when the BCB asks for it — see
# pawlet-recoveryboot.sh. post-fs-data: by-name symlinks and /metadata are
# guaranteed mounted, and we still reboot before the launcher appears.
service pawlet_recoveryboot /vendor/bin/pawlet-recoveryboot.sh
user root
group root system
oneshot
disabled
on post-fs-data
exec_start pawlet_recoveryboot
+53
View File
@@ -0,0 +1,53 @@
#!/vendor/bin/sh
#
# Copyright (C) 2026 oxmc / PawletOS
# SPDX-License-Identifier: Apache-2.0
#
# Boot-time bootloader-message (BCB) watcher — the RPi firmware cannot read
# the misc partition, so "reboot recovery" needs an Android-side chain-load:
#
# adb reboot recovery / Settings factory reset
# -> framework+init write "boot-recovery" into the BCB command field
# (misc offset 0, 32 bytes) and reboot; the firmware ignores the
# "recovery" reboot arg and boots normal Android
# -> THIS script (oneshot, early in boot) sees the BCB and reboots with
# arg "3": the firmware's PM_RSTS partition mechanism (same driver
# path as the updater's "tryboot") boots GPT partition 3 = recovery
# -> stock recovery reads its args from the BCB, does its work, clears
# the BCB, reboots -> normal boot
#
# The BCB is left intact for recovery (it carries e.g. --wipe_data). A
# bootloop breaker in /metadata clears the BCB after repeated failed
# attempts so an unbootable recovery partition can't wedge the device.
MISC=/dev/block/by-name/misc
GUARD_DIR=/metadata/pawlet
GUARD=$GUARD_DIR/recoveryboot.attempts
TAG=pawlet-recoveryboot
CMD=$(dd if=$MISC bs=1 count=32 2>/dev/null | tr -d '\0')
case "$CMD" in
boot-recovery*)
mkdir -p $GUARD_DIR
N=$(cat $GUARD 2>/dev/null)
[ -z "$N" ] && N=0
if [ "$N" -ge 2 ]; then
# Recovery partition isn't coming up — break the loop: clear the BCB
# command field and stay in Android.
dd if=/dev/zero of=$MISC bs=1 count=32 conv=notrunc 2>/dev/null
rm -f $GUARD
log -p e -t $TAG "recovery boot failed $N times; BCB cleared, staying in Android"
exit 0
fi
echo $((N + 1)) > $GUARD
log -p i -t $TAG "BCB requests recovery — rebooting to partition 3 (attempt $((N + 1)))"
setprop sys.powerctl reboot,3
;;
*)
# Normal boot: reset the attempt counter.
rm -f $GUARD 2>/dev/null
;;
esac
exit 0