mirror of https://github.com/wolfSSL/wolfBoot.git
225 lines
11 KiB
Bash
Executable File
225 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
# prepare_emmc_rauc.sh - lay out the CM4 eMMC for production RAUC A/B boot under
|
|
# wolfBoot (cm4_emmc_rauc.config). wolfBoot replaces GCX's U-Boot as the RAUC
|
|
# slot arbiter: it reads a raw U-Boot-env partition, runs the BOOT_ORDER state
|
|
# machine, and boots the shared kernel FIT with root= pointing at the active slot.
|
|
#
|
|
# GPT (0-based indices in [brackets]):
|
|
# p1 boot FAT [0] : RPi fw + wolfBoot kernel8.img + config.txt (disable-bt)
|
|
# p2 uboot-env raw [1] : U-Boot env (mkenvimage; RAUC fw_env.config -> here)
|
|
# p3 fitImage raw [2] : wolfBoot-signed kernel FIT (shared)
|
|
# p4 rootfs_A ext4 [3] : RAUC slot A (initial rootfs)
|
|
# p5 rootfs_B ext4 [4] : RAUC slot B (initial rootfs copy)
|
|
# p6 data ext4 [5] : persistent /data
|
|
#
|
|
# Usage:
|
|
# cp config/examples/cm4_emmc_rauc.config .config
|
|
# make wolfboot.bin CROSS_COMPILE=aarch64-none-elf- DEBUG=1
|
|
# bash tools/scripts/cm4/prepare_emmc_rauc.sh (stage only, no root)
|
|
# sudo bash tools/scripts/cm4/prepare_emmc_rauc.sh /dev/sdN (write, root)
|
|
#
|
|
# Copyright (C) 2026 wolfSSL Inc. GPLv3 (see wolfBoot COPYING).
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
HERE="$ROOT/tools/scripts/cm4"
|
|
CROSS="${CROSS_COMPILE:-aarch64-none-elf-}"
|
|
KEY="${PRIVATE_KEY:-$ROOT/wolfboot_signing_private_key.der}"
|
|
IMG_HDR="${IMAGE_HEADER_SIZE:-1024}"
|
|
# Yocto deploy dir - REQUIRED (no default; set GCX_DEPLOY to the
|
|
# deploy/images/<MACHINE> directory of your build).
|
|
GCX_DEPLOY="${GCX_DEPLOY:-}"
|
|
DEV="${1:-}"
|
|
OUT="$HERE/rauc"
|
|
|
|
# Deploy artifact basenames; override per-name for a different recipe / machine.
|
|
KERNEL_NAME="${KERNEL_NAME:-fitImage-linux.bin-raspberrypi4-64}"
|
|
ROOTFS_NAME="${ROOTFS_NAME:-core-image-ironbutterfly-raspberrypi4-64.rootfs.ext4}"
|
|
DTB_NAME="${DTB_NAME:-bcm2711-rpi-cm4.dtb}"
|
|
KERNEL_GZ="$GCX_DEPLOY/$KERNEL_NAME"
|
|
ROOTFS="$GCX_DEPLOY/$ROOTFS_NAME"
|
|
DTB="$GCX_DEPLOY/$DTB_NAME"
|
|
FW_START="$GCX_DEPLOY/bootfiles/start4.elf"
|
|
FW_FIXUP="$GCX_DEPLOY/bootfiles/fixup4.dat"
|
|
|
|
prep_artifacts() {
|
|
local f
|
|
if [ "${FORCE_PREP:-0}" != "1" ] && \
|
|
[ -f "$OUT/cm4-fitImage_v1_signed.bin" ] && [ -f "$OUT/kernel8.img" ] && \
|
|
[ -f "$OUT/config.txt" ] && [ -f "$OUT/uboot.env" ] && \
|
|
[ -f "$OUT/start4.elf" ] && [ -f "$OUT/fixup4.dat" ] && \
|
|
[ -f "$OUT/bcm2711-rpi-cm4.dtb" ]; then
|
|
echo "== artifacts already staged in $OUT (skip prep; FORCE_PREP=1 to rebuild) =="
|
|
return 0
|
|
fi
|
|
[ -f "$ROOT/wolfboot.bin" ] || { echo "!! build wolfboot.bin first (cm4_emmc_rauc.config)"; exit 1; }
|
|
[ -f "$KEY" ] || { echo "!! signing key not found: $KEY"; exit 1; }
|
|
[ -n "$GCX_DEPLOY" ] || { echo "!! set GCX_DEPLOY=<yocto deploy/images/MACHINE dir> (no default)"; exit 1; }
|
|
for f in "$KERNEL_GZ" "$DTB" "$FW_START" "$FW_FIXUP"; do
|
|
[ -f "$f" ] || { echo "!! missing deploy artifact: $f (check GCX_DEPLOY / *_NAME overrides)"; exit 1; }
|
|
done
|
|
command -v mkimage >/dev/null || { echo "!! mkimage not found (u-boot-tools)"; exit 1; }
|
|
command -v mkenvimage >/dev/null || { echo "!! mkenvimage not found (u-boot-tools)"; exit 1; }
|
|
|
|
mkdir -p "$OUT"
|
|
echo "== building + signing the shared kernel FIT =="
|
|
cp -L "$KERNEL_GZ" "$OUT/linux.bin.gz"
|
|
( cd "$OUT" && sed 's#\.\./Image\.gz#linux.bin.gz#' "$ROOT/hal/cm4.its" > cm4.its \
|
|
&& mkimage -f cm4.its cm4-fitImage >/dev/null )
|
|
( cd "$ROOT" && IMAGE_HEADER_SIZE="$IMG_HDR" ./tools/keytools/sign --ecc384 --sha384 \
|
|
"$OUT/cm4-fitImage" "$KEY" 1 >/dev/null )
|
|
[ -f "$OUT/cm4-fitImage_v1_signed.bin" ] || { echo "!! signing failed"; exit 1; }
|
|
|
|
echo "== generating initial U-Boot env (BOOT_ORDER 'A B', tries=3) =="
|
|
printf 'BOOT_ORDER=A B\nBOOT_A_LEFT=3\nBOOT_B_LEFT=3\n' > "$OUT/env.txt"
|
|
# 0x4000 must match wolfBoot UBOOT_ENV_SIZE and RAUC fw_env.config size.
|
|
mkenvimage -s 0x4000 -o "$OUT/uboot.env" "$OUT/env.txt"
|
|
|
|
echo "== assembling boot partition files =="
|
|
cp "$FW_START" "$OUT/start4.elf"
|
|
cp "$FW_FIXUP" "$OUT/fixup4.dat"
|
|
cp "$DTB" "$OUT/bcm2711-rpi-cm4.dtb"
|
|
cp "$ROOT/wolfboot.bin" "$OUT/kernel8.img"
|
|
cat > "$OUT/config.txt" <<'EOF'
|
|
arm_64bit=1
|
|
enable_uart=1
|
|
dtoverlay=disable-bt
|
|
init_uart_clock=48000000
|
|
kernel=kernel8.img
|
|
kernel_address=0x200000
|
|
device_tree=bcm2711-rpi-cm4.dtb
|
|
EOF
|
|
echo "== staged in $OUT: $(ls "$OUT" | tr '\n' ' ')"
|
|
}
|
|
|
|
write_device() {
|
|
# MP is intentionally NOT local: the global EXIT trap below references it, and
|
|
# a function-local would be out of scope (set -u -> "unbound") when the trap
|
|
# fires on a set -e abort.
|
|
local t root_src root_pk P1 P2 P3 P4 P5 P6 p ok
|
|
[ "$(id -u)" -eq 0 ] || { echo "!! writing $DEV requires root"; exit 1; }
|
|
for t in sgdisk partprobe mkfs.vfat mkfs.ext4 lsblk udevadm dd tune2fs; do
|
|
command -v "$t" >/dev/null || { echo "!! missing required tool: $t"; exit 1; }
|
|
done
|
|
[ -b "$DEV" ] || { echo "!! not a block device: $DEV"; exit 1; }
|
|
[ -f "$ROOTFS" ] || { echo "!! rootfs not found: $ROOTFS (set GCX_DEPLOY)"; exit 1; }
|
|
[ -f "$OUT/uboot.env" ] || { echo "!! env not staged; run prep first"; exit 1; }
|
|
root_src="$(findmnt -no SOURCE / 2>/dev/null || true)"
|
|
root_pk="$(lsblk -no PKNAME "$root_src" 2>/dev/null | head -1)"
|
|
if [ "${ALLOW_ANY_DISK:-0}" != "1" ]; then
|
|
# Fail closed: an empty PKNAME (LVM/btrfs/overlay root, empty root_src)
|
|
# would otherwise leave the guard comparing against a bare "/dev/".
|
|
[ -n "$root_pk" ] || { echo "!! cannot determine root disk (lsblk PKNAME empty); set ALLOW_ANY_DISK=1 to override"; exit 1; }
|
|
if [ "$DEV" = "/dev/$root_pk" ]; then
|
|
echo "!! refusing to erase the root disk $DEV (set ALLOW_ANY_DISK=1 to override)"; exit 1
|
|
fi
|
|
fi
|
|
|
|
# Fast iteration path: refresh only wolfBoot (p1 boot) + U-Boot env (p2) +
|
|
# signed FIT (p3) on an already-laid-out RAUC eMMC, WITHOUT repartitioning or
|
|
# re-writing the two 4GB rootfs slots. The root-disk guard above still runs.
|
|
P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"
|
|
[ -b "$P1" ] || { P1="${DEV}p1"; P2="${DEV}p2"; P3="${DEV}p3"; }
|
|
if [ "${KERNEL_ONLY:-0}" = "1" ]; then
|
|
for p in "$P1" "$P2" "$P3"; do
|
|
[ -b "$p" ] || { echo "!! KERNEL_ONLY: $p not found (device not laid out yet)"; exit 1; }
|
|
done
|
|
echo "== KERNEL_ONLY target: $DEV =="; lsblk -o NAME,SIZE,FSTYPE,LABEL "$DEV"
|
|
if [ "${ASSUME_YES:-0}" = "1" ]; then
|
|
echo "ASSUME_YES=1: proceeding without prompt"
|
|
else
|
|
read -rp "Overwrite boot ($P1) + uboot-env ($P2) + FIT ($P3)? [type YES] " ok
|
|
[ "$ok" = "YES" ] || { echo "aborted"; exit 1; }
|
|
fi
|
|
MP=$(mktemp -d)
|
|
trap 'if [ -n "${MP:-}" ]; then mountpoint -q "$MP" && umount "$MP"; rmdir "$MP" 2>/dev/null || true; fi' EXIT
|
|
echo "== KERNEL_ONLY: refresh boot files on $P1 =="
|
|
mount "$P1" "$MP"
|
|
cp "$OUT"/start4.elf "$OUT"/fixup4.dat "$OUT"/bcm2711-rpi-cm4.dtb \
|
|
"$OUT"/config.txt "$OUT"/kernel8.img "$MP"/
|
|
sync; umount "$MP"
|
|
echo "== U-Boot env -> uboot-env ($P2) =="
|
|
dd if="$OUT/uboot.env" of="$P2" bs=4k conv=fsync status=none
|
|
echo "== signed kernel FIT -> fitImage ($P3) =="
|
|
dd if="$OUT/cm4-fitImage_v1_signed.bin" of="$P3" bs=4k conv=fsync status=none
|
|
sync
|
|
echo "== KERNEL_ONLY done. Set BOOT OFF, power-cycle, watch the console. =="
|
|
return 0
|
|
fi
|
|
|
|
echo "== target: $DEV =="; lsblk -o NAME,SIZE,FSTYPE,LABEL "$DEV"
|
|
read -rp "Repartition and ERASE $DEV? [type YES] " ok
|
|
[ "$ok" = "YES" ] || { echo "aborted"; exit 1; }
|
|
|
|
echo "== GPT: boot 128M / uboot-env 8M / fitImage 64M / rootfs_A 4G / rootfs_B 4G / data (rest) =="
|
|
sgdisk --zap-all "$DEV" >/dev/null
|
|
sgdisk -n 1:2048:+128M -t 1:0700 -c 1:boot "$DEV" >/dev/null
|
|
sgdisk -n 2:0:+8M -t 2:8300 -c 2:uboot-env "$DEV" >/dev/null
|
|
sgdisk -n 3:0:+64M -t 3:8300 -c 3:fitImage "$DEV" >/dev/null
|
|
sgdisk -n 4:0:+4G -t 4:8300 -c 4:rootfs_A "$DEV" >/dev/null
|
|
sgdisk -n 5:0:+4G -t 5:8300 -c 5:rootfs_B "$DEV" >/dev/null
|
|
sgdisk -n 6:0:0 -t 6:8300 -c 6:data "$DEV" >/dev/null
|
|
partprobe "$DEV"; udevadm settle
|
|
P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"; P4="${DEV}4"; P5="${DEV}5"; P6="${DEV}6"
|
|
[ -b "$P1" ] || { P1="${DEV}p1"; P2="${DEV}p2"; P3="${DEV}p3"; P4="${DEV}p4"; P5="${DEV}p5"; P6="${DEV}p6"; }
|
|
# Assert every node exists before any dd/mkfs: a missing node makes dd/mkfs
|
|
# silently create a regular file instead of writing the partition.
|
|
for p in "$P1" "$P2" "$P3" "$P4" "$P5" "$P6"; do
|
|
[ -b "$p" ] || { echo "!! partition node $p missing (partprobe/udev race?)"; exit 1; }
|
|
done
|
|
|
|
echo "== FAT boot partition ($P1) =="
|
|
mkfs.vfat -n BOOT "$P1" >/dev/null
|
|
MP=$(mktemp -d)
|
|
# Clean up on exit even if mount/cp fails under set -e. A RETURN trap does NOT
|
|
# fire when set -e aborts inside a function; EXIT does. The mountpoint -q guard
|
|
# makes it idempotent with the explicit umount below. The ${MP:-} guard keeps
|
|
# the trap safe under set -u if it fires before MP is assigned.
|
|
trap 'if [ -n "${MP:-}" ]; then mountpoint -q "$MP" && umount "$MP"; rmdir "$MP" 2>/dev/null || true; fi' EXIT
|
|
mount "$P1" "$MP"
|
|
cp "$OUT"/start4.elf "$OUT"/fixup4.dat "$OUT"/bcm2711-rpi-cm4.dtb \
|
|
"$OUT"/config.txt "$OUT"/kernel8.img "$MP"/
|
|
sync; umount "$MP"
|
|
|
|
echo "== U-Boot env -> uboot-env ($P2) =="
|
|
dd if="$OUT/uboot.env" of="$P2" bs=4k conv=fsync status=none
|
|
echo "== signed kernel FIT -> fitImage ($P3) =="
|
|
dd if="$OUT/cm4-fitImage_v1_signed.bin" of="$P3" bs=4k conv=fsync status=none
|
|
|
|
echo "== rootfs -> slot A ($P4) and slot B ($P5), then grow both =="
|
|
dd if="$ROOTFS" of="$P4" bs=4M conv=fsync status=none
|
|
dd if="$ROOTFS" of="$P5" bs=4M conv=fsync status=none
|
|
for p in "$P4" "$P5"; do
|
|
# e2fsck returns 1/2 when it fixed errors (not a failure), so || true is fine.
|
|
command -v e2fsck >/dev/null && e2fsck -pf "$p" >/dev/null 2>&1 || true
|
|
# Distinguish "tool missing" from "tool ran but failed" - a swallowed
|
|
# resize2fs failure hides a real error behind a silent no-op.
|
|
if command -v resize2fs >/dev/null; then
|
|
resize2fs "$p" >/dev/null 2>&1 || echo " !! resize2fs failed on $p (rootfs left at image size)"
|
|
else
|
|
echo " (resize2fs missing; rootfs stays at image size)"
|
|
fi
|
|
done
|
|
# Slots A and B were dd'd from the same image, so they share ext4 UUID+label.
|
|
# Re-stamp a distinct random UUID + label on each so nothing resolves root by
|
|
# a duplicate UUID/LABEL once RAUC updates a slot.
|
|
if command -v tune2fs >/dev/null; then
|
|
tune2fs -U random -L rootfs_A "$P4" >/dev/null 2>&1 || echo " !! tune2fs restamp failed on $P4 (slot A)"
|
|
tune2fs -U random -L rootfs_B "$P5" >/dev/null 2>&1 || echo " !! tune2fs restamp failed on $P5 (slot B)"
|
|
else
|
|
echo " (tune2fs missing; A/B slots keep duplicate ext4 UUID/label)"
|
|
fi
|
|
echo "== data partition ($P6) =="
|
|
mkfs.ext4 -q -L data "$P6" >/dev/null 2>&1 || echo " !! mkfs.ext4 failed on $P6 (no /data filesystem)"
|
|
sync
|
|
echo "== done. BOOT switch OFF, power-cycle, watch the CM4 PL011 console (ttyAMA0). =="
|
|
echo " wolfBoot picks the RAUC slot from p2 and boots root=/dev/mmcblk0p4|p5."
|
|
}
|
|
|
|
prep_artifacts
|
|
if [ -n "$DEV" ]; then
|
|
write_device
|
|
else
|
|
echo "(no device given; artifacts only. Re-run with: sudo bash $0 /dev/sdN)"
|
|
fi
|