diff --git a/config/rpi_common.mk b/config/rpi_common.mk index 9501ec4..a32f288 100644 --- a/config/rpi_common.mk +++ b/config/rpi_common.mk @@ -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 diff --git a/recoveryboot/Android.bp b/recoveryboot/Android.bp new file mode 100644 index 0000000..e7e7d90 --- /dev/null +++ b/recoveryboot/Android.bp @@ -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"], +} diff --git a/recoveryboot/pawlet-recoveryboot.rc b/recoveryboot/pawlet-recoveryboot.rc new file mode 100644 index 0000000..706da80 --- /dev/null +++ b/recoveryboot/pawlet-recoveryboot.rc @@ -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 diff --git a/recoveryboot/pawlet-recoveryboot.sh b/recoveryboot/pawlet-recoveryboot.sh new file mode 100644 index 0000000..afaf948 --- /dev/null +++ b/recoveryboot/pawlet-recoveryboot.sh @@ -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