From cd30b06245b484b835a85500bf9da3cfb747f2ac Mon Sep 17 00:00:00 2001 From: oxmc <67136658+oxmc@users.noreply.github.com> Date: Fri, 17 Jul 2026 05:11:12 -0700 Subject: [PATCH] rpi5: recovery as a separate on-demand image; A/B on tryboot - mkbootimg.mk: boot.img uses the normal ramdisk again; the recovery.img rule is guarded so it only builds when recovery is enabled (PAWLET_BUILD_RECOVERY=1) and carries the recovery ramdisk. - config.txt [tryboot] back to boot_partition=2 (A/B trial); recovery enters via reboot,3. - vendor.prop reboot reason back to reboot,tryboot. - drop the no-op reserved-size lines (AOSP ignores RESERVED_SIZE for fixed non-dynamic partitions). --- BoardConfig.mk | 20 +++++++++----------- boot/config.txt | 22 ++++++++++------------ mkbootimg.mk | 47 +++++++++++++++++++++-------------------------- vendor.prop | 6 +++--- 4 files changed, 43 insertions(+), 52 deletions(-) diff --git a/BoardConfig.mk b/BoardConfig.mk index f8bbb4f..0e2c554 100644 --- a/BoardConfig.mk +++ b/BoardConfig.mk @@ -28,15 +28,15 @@ TARGET_NO_BOOTLOADER := true # dlsym()s make_device() from whatever this points at). See # vendor/pawlet/recovery_ui/README.md and NOTES-ota-recovery-ab.md. TARGET_NO_RECOVERY := false -# Recovery ramdisk is embedded in the boot partition (mkbootimg.mk copies -# ramdisk-recovery.img in as ramdisk.img). Normal boot sets force_normal_boot=1 -# in cmdline.txt so init boots Android; recovery is entered via the [tryboot] -# cmdline swap (see boot/config.txt). Do NOT use BOARD_USES_RECOVERY_AS_BOOT: it -# makes AOSP seize the boot.img rule and collide with our custom FAT mkbootimg.mk. -# BOARD_USES_FULL_RECOVERY_IMAGE avoids the recovery-from-boot imgdiff / -# recovery-resource.dat path. mkbootimg.mk also builds a standalone recovery.img -# (satisfies INSTALLED_RECOVERYIMAGE_TARGET; a fallback, not flashed by default -# since recovery lives in boot). +# Recovery is a SEPARATE image on the recovery partition, built on demand (see +# PRODUCT_BUILD_RECOVERY_IMAGE in common.mk / the `precovery` helper). It is kept +# out of the normal build so AOSP does not force recovery.img into the vendor +# image size check (RECOVERY_FROM_BOOT_PATCH), which would overflow the padded +# 2GB vendor partition. mkbootimg.mk builds the recovery FAT when enabled; +# pawlet-recoveryboot.sh enters it via reboot,3. BOARD_USES_FULL_RECOVERY_IMAGE +# avoids the recovery-from-boot imgdiff / recovery-resource.dat path. Do NOT use +# BOARD_USES_RECOVERY_AS_BOOT: it makes AOSP seize the boot.img rule and collide +# with our custom FAT mkbootimg.mk. BOARD_USES_FULL_RECOVERY_IMAGE := true # BOARD_RECOVERYIMAGE_PARTITION_SIZE (128M) comes from BoardConfigPawlet.mk. TARGET_RECOVERY_FSTAB := device/pawlet/rpi5/recovery/recovery.rpi5.fstab @@ -77,13 +77,11 @@ PRODUCT_MANIFEST_FILES := $(DEVICE_PATH)/product_manifest.xml BOARD_FLASH_BLOCK_SIZE := 4096 BOARD_BOOTIMAGE_PARTITION_SIZE := 134217728 # 128M BOARD_SYSTEMIMAGE_PARTITION_SIZE := 8589934592 # 8192M (8GB) -BOARD_SYSTEMIMAGE_PARTITION_RESERVED_SIZE := 104857600 # 100MB BOARD_SYSTEM_EXTIMAGE_PARTITION_SIZE := 4294967296 # 4096M (4GB) BOARD_SYSTEM_EXTIMAGE_PARTITION_RESERVED_SIZE := 104857600 # 100MB BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE := ext4 TARGET_COPY_OUT_SYSTEM_EXT := system_ext BOARD_VENDORIMAGE_PARTITION_SIZE := 2147483648 # 2048M (2GB) -BOARD_VENDORIMAGE_PARTITION_RESERVED_SIZE := 104857600 # 100MB BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := ext4 TARGET_COPY_OUT_VENDOR := vendor BOARD_USES_METADATA_PARTITION := true diff --git a/boot/config.txt b/boot/config.txt index fb2c3c9..8d0f959 100644 --- a/boot/config.txt +++ b/boot/config.txt @@ -38,16 +38,14 @@ dtoverlay=dwc2,dr_mode=otg [all] -# Recovery entry. The recovery ramdisk is embedded in this boot partition -# (mkbootimg.mk stages ramdisk-recovery.img as ramdisk.img); the normal -# cmdline.txt sets androidboot.force_normal_boot=1 so first-stage init -# boots Android. pawlet-recoveryboot.sh issues `reboot tryboot` when the misc BCB -# requests recovery — this one-shot swaps in cmdline_recovery.txt (no -# force_normal_boot), so the same boot partition comes up in recovery mode. -# -# A/B slot switching does NOT use tryboot: the updater boots the candidate slot -# via a one-shot `reboot,2` (partition 2 = boot_b). On confirmed success the app -# copies boot_b -> boot_a via /dev/block/by-name/misc to make B permanent. -# See vendor.prop ro.vendor.pawlet.ota.reboot_reason. +# A/B boot slot selection via RPi tryboot. +# Slot A = partition 1 (boot_a) — normal boot, no override needed. +# Slot B = partition 2 (boot_b) — booted once via `reboot tryboot` (the updater) +# to trial an update; the firmware reverts to slot A if the trial fails +# to confirm, so a bad update can't wedge the device. On success the app +# copies boot_b -> boot_a (via /dev/block/by-name/misc) to make it +# permanent. +# Recovery is a separate image on partition 3, entered via `reboot,3` +# (pawlet-recoveryboot.sh) — not tryboot. [tryboot] -cmdline=cmdline_recovery.txt +boot_partition=2 diff --git a/mkbootimg.mk b/mkbootimg.mk index 2a81f5c..028037a 100644 --- a/mkbootimg.mk +++ b/mkbootimg.mk @@ -8,8 +8,9 @@ DEVICE_PATH := device/pawlet/rpi5 KERNEL_PATH := device/brcm/rpi5-kernel -# The recovery ramdisk is embedded in the boot partition (see BoardConfig.mk). -# Inside the FAT it is named ramdisk.img (config.txt: initramfs ramdisk.img). +# Recovery is a separate image, built on demand (PRODUCT_BUILD_RECOVERY_IMAGE, +# via the `precovery` helper) and flashed to the recovery partition. The normal +# build only produces boot.img. RECOVERY_RAMDISK := $(PRODUCT_OUT)/ramdisk-recovery.img # Use a stamp file so ninja tracks the directory contents correctly. @@ -17,48 +18,42 @@ RECOVERY_RAMDISK := $(PRODUCT_OUT)/ramdisk-recovery.img RPI_BOOT_OUT := $(PRODUCT_OUT)/rpiboot RPI_BOOT_STAMP := $(RPI_BOOT_OUT)/.stamp -# Shared boot-partition staging: kernel, DTBs, overlays, config.txt, and the -# recovery ramdisk. cmdline.txt is NOT staged here — each image rule writes its -# own (force_normal_boot for boot, absent for recovery) so the boot and recovery -# images can build in parallel without racing on it. -$(RPI_BOOT_STAMP): $(RECOVERY_RAMDISK) +# Shared boot-partition staging: kernel, DTBs, overlays, config.txt. The ramdisk +# and cmdline.txt are added per-image in the rules below, so a stray +# cmdline/ramdisk in the staging dir can never collide with the per-image mcopy +# (which would hang on mtools' interactive overwrite prompt). +$(RPI_BOOT_STAMP): $(INSTALLED_RAMDISK_TARGET) rm -rf $(RPI_BOOT_OUT) mkdir -p $(RPI_BOOT_OUT)/overlays cp $(DEVICE_PATH)/boot/* $(RPI_BOOT_OUT) cp $(KERNEL_PATH)/Image $(RPI_BOOT_OUT) cp $(KERNEL_PATH)/bcm2712*-rpi-*.dtb $(RPI_BOOT_OUT) cp $(KERNEL_PATH)/overlays/* $(RPI_BOOT_OUT)/overlays - cp $(RECOVERY_RAMDISK) $(RPI_BOOT_OUT)/ramdisk.img - # cmdline.txt is written per-image (boot vs recovery) in the rules below; make - # sure none rides along in the staging dir, or the per-image mcopy would hit - # an interactive overwrite prompt and hang the build. - rm -f $(RPI_BOOT_OUT)/cmdline.txt $(RPI_BOOT_OUT)/cmdline_recovery.txt + rm -f $(RPI_BOOT_OUT)/cmdline*.txt $(RPI_BOOT_OUT)/ramdisk*.img touch $@ -# Normal boot partition. cmdline.txt sets androidboot.force_normal_boot=1 so -# first-stage init boots Android even though ramdisk.img is the recovery -# ramdisk; cmdline_recovery.txt (no force_normal_boot) rides along for the -# [tryboot] recovery swap in config.txt. -$(INSTALLED_BOOTIMAGE_TARGET): $(RPI_BOOT_STAMP) +# Normal boot partition: the standard Android ramdisk + cmdline. +$(INSTALLED_BOOTIMAGE_TARGET): $(RPI_BOOT_STAMP) $(INSTALLED_RAMDISK_TARGET) $(call pretty,"Target boot image: $@") - echo "$(BOARD_KERNEL_CMDLINE) androidboot.force_normal_boot=1" > $@.cmdline - echo "$(BOARD_KERNEL_CMDLINE)" > $@.cmdline_recovery + echo "$(BOARD_KERNEL_CMDLINE)" > $@.cmdline dd if=/dev/zero of=$@ bs=1M count=128 mkfs.fat -F 32 -n "BOOT" $@ mcopy -s -i $@ $(RPI_BOOT_OUT)/* :: + mcopy -i $@ $(INSTALLED_RAMDISK_TARGET) ::ramdisk.img mcopy -i $@ $@.cmdline ::cmdline.txt - mcopy -i $@ $@.cmdline_recovery ::cmdline_recovery.txt - rm -f $@.cmdline $@.cmdline_recovery + rm -f $@.cmdline -# Standalone recovery image. Not flashed by default (recovery lives in the boot -# partition); built to satisfy INSTALLED_RECOVERYIMAGE_TARGET and usable as a -# manual fallback. Its cmdline omits force_normal_boot, so booting it goes -# straight into recovery. -$(INSTALLED_RECOVERYIMAGE_TARGET): $(RPI_BOOT_STAMP) +# Recovery partition image — only defined when recovery is enabled +# (PAWLET_BUILD_RECOVERY=1 -> INSTALLED_RECOVERYIMAGE_TARGET set). Same FAT +# layout but carrying the recovery ramdisk. Flash it to the recovery partition. +ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),) +$(INSTALLED_RECOVERYIMAGE_TARGET): $(RPI_BOOT_STAMP) $(RECOVERY_RAMDISK) $(call pretty,"Target recovery image: $@") echo "$(BOARD_KERNEL_CMDLINE)" > $@.cmdline dd if=/dev/zero of=$@ bs=1M count=128 mkfs.fat -F 32 -n "RECOVERY" $@ mcopy -s -i $@ $(RPI_BOOT_OUT)/* :: + mcopy -i $@ $(RECOVERY_RAMDISK) ::ramdisk.img mcopy -i $@ $@.cmdline ::cmdline.txt rm -f $@.cmdline +endif diff --git a/vendor.prop b/vendor.prop index fb44106..cf15d34 100644 --- a/vendor.prop +++ b/vendor.prop @@ -37,9 +37,9 @@ persist.vendor.hdmi.cec_device=cec0 ro.hdmi.cec_device_types=playback_device ro.hdmi.device_type=4 -# A/B OTA: boot the candidate slot (boot_b = partition 2) via a one-shot RPi -# partition reboot. tryboot is reserved for recovery entry (see config.txt). -ro.vendor.pawlet.ota.reboot_reason=reboot,2 +# A/B OTA: boot the candidate slot via RPi tryboot — a safe trial that reverts +# to the active slot if the candidate fails to boot. +ro.vendor.pawlet.ota.reboot_reason=reboot,tryboot # Chipset ro.soc.manufacturer=Broadcom