Files
oxmc 8f33626a21 recovery: build as a separate on-demand image; A/B failsafe via tryboot
Recovery is no longer embedded in boot.img -- that forced recovery.img into
the vendor image size check and overflowed the padded 2GB vendor partition.
Instead:

- common.mk gates recovery with PRODUCT_BUILD_RECOVERY_IMAGE (default false),
  so the normal build never touches it. Build it on demand with the new
  `precovery` helper (PAWLET_BUILD_RECOVERY=1 m recoveryimage) and flash the
  result to the recovery partition.
- pawlet-recoveryboot.sh enters recovery via reboot,3 (separate partition)
  again, and gains a boot-loop failsafe: a /metadata counter (cleared on
  boot_completed via the .rc) drops the device into recovery after BOOT_MAX
  consecutive boots that reach early init but never complete.
- BootControl comments restored to A/B-via-tryboot (a failed trial reverts to
  the active slot; recovery is a separate partition via reboot,3).
2026-07-17 05:11:02 -07:00

64 lines
2.5 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) The Android Open Source Project
* Copyright (C) 2025 oxmc / PawletOS
*
* SPDX-License-Identifier: Apache-2.0
*
* On-disk metadata format for PawletOS A/B boot control.
*
* Layout in the misc partition (4 MB, GPT label "misc"):
* 0x0000 0x07FF Android Bootloader Control Block (BCB) — do NOT touch
* 0x0800 0x0FFF Unused padding
* 0x1000 (4096) PawletBootControl — read/written by this HAL
*
* Slot numbering: 0 = slot A (boot_a, cmdline: androidboot.slot_suffix=_a)
* 1 = slot B (boot_b, cmdline: androidboot.slot_suffix=_b)
*
* A/B boot flow on RPi:
* - Normal boot always loads boot_a (GPT partition 1) via the RPi firmware.
* - To boot slot B once: `reboot tryboot` causes the firmware to use
* boot_partition=2 for one attempt (see [tryboot] in config.txt); a failed
* trial reverts to boot_a, so a bad update can't wedge the device.
* - On a confirmed-successful boot from slot B, markBootSuccessful() copies
* boot_b → boot_a so that subsequent normal reboots use the new content.
* - Recovery is a separate image on partition 3, entered via reboot,3
* (see vendor/pawlet/recoveryboot).
*/
#pragma once
#include <stdint.h>
#include <string.h>
// Magic value: "PAWL" in little-endian ASCII.
static constexpr uint32_t PAWLET_MAGIC = 0x4C574150u;
static constexpr uint32_t PAWLET_VERSION = 1u;
// Byte offset inside the misc partition where our struct lives.
static constexpr off_t PAWLET_MISC_OFFSET = 4096;
// How many boot attempts to grant a freshly-activated slot.
static constexpr int PAWLET_MAX_BOOT_ATTEMPTS = 3;
// Per-slot metadata (4 bytes).
struct PawletSlot {
uint8_t priority; // 015. Higher = preferred. 0 = unbootable.
uint8_t tries_remaining; // Remaining attempts before slot is unbootable.
uint8_t successful_boot; // 1 once markBootSuccessful() has been called.
uint8_t reserved;
};
// Complete on-disk structure placed at PAWLET_MISC_OFFSET.
// Total size: 24 bytes.
struct __attribute__((packed)) PawletBootControl {
uint32_t magic; // PAWLET_MAGIC
uint32_t version; // PAWLET_VERSION
uint8_t active_slot; // 0 = A, 1 = B
uint8_t merge_status; // android.hardware.boot.MergeStatus enum value
uint8_t reserved[2];
PawletSlot slots[2]; // [0] = slot A, [1] = slot B
uint32_t crc32; // CRC32 over all preceding bytes
};
static_assert(sizeof(PawletBootControl) == 24,
"PawletBootControl layout changed update tests and partition docs");