wolfBoot/tools/scripts/cm4/prepare_emmc.sh

156 lines
7.4 KiB
Bash
Executable File

#!/bin/bash
# prepare_emmc.sh - build the eMMC/SD layout for the CM4 wolfBoot disk-boot test.
#
# Creates a GPT with:
# p1 = boot (FAT32) : RPi firmware (start4.elf, fixup4.dat), config.txt,
# kernel8.img (= wolfBoot, built from cm4_emmc.config)
# p2 = image_a (raw) : signed disk_app ELF, version 1 (BOOT_PART_A=1)
# p3 = image_b (raw) : signed disk_app ELF, version 2 (BOOT_PART_B=2)
#
# wolfBoot (loaded to RAM by the RPi firmware) then drives the BCM2711 EMMC2
# controller to read p2/p3, verifies ECC384/SHA384, and boots the higher
# version. Prove-out prints ">>> CM4 DISK APP OK <<<" over the mini-UART (AUX).
#
# Usage:
# # 1) build wolfBoot first:
# # cp config/examples/cm4_emmc.config .config
# # make wolfboot.bin CROSS_COMPILE=aarch64-none-elf- (add DEBUG_SDHCI for bring-up)
# # 2) prep artifacts only (no root):
# # bash tools/scripts/cm4/prepare_emmc.sh
# # 3) with the eMMC presented via rpiboot as /dev/sdN, write it (root):
# # sudo bash tools/scripts/cm4/prepare_emmc.sh /dev/sdN
#
# Copyright (C) 2026 wolfSSL Inc. GPLv3 (see wolfBoot COPYING).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" # wolfboot repo root
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}"
# Pin the RPi firmware to a specific ref for reproducibility - these blobs are
# the first link of the boot chain. Override with FWREF=<tag|sha> (a tag like
# 1.20240529, or a full commit sha).
FWREF="${FWREF:-1.20240529}"
FWBASE="https://raw.githubusercontent.com/raspberrypi/firmware/${FWREF}/boot"
DEV="${1:-}"
# ---- artifact prep (no root) ----------------------------------------------
prep_artifacts() {
# Idempotent: if everything is already staged (e.g. prepped without sudo,
# then re-run under sudo to write the device where the cross-toolchain is
# not in PATH), skip the rebuild. Set FORCE_PREP=1 to force.
if [ "${FORCE_PREP:-0}" != "1" ] && \
[ -f "$HERE/disk_app_v1_signed.bin" ] && \
[ -f "$HERE/disk_app_v2_signed.bin" ] && \
[ -f "$HERE/fw/kernel8.img" ] && [ -f "$HERE/fw/start4.elf" ] && \
[ -f "$HERE/fw/config.txt" ]; then
echo "== artifacts already staged in $HERE (skip prep; FORCE_PREP=1 to rebuild) =="
return 0
fi
[ -f "$ROOT/wolfboot.bin" ] || { echo "!! build wolfboot.bin first (cm4_emmc.config)"; exit 1; }
[ -f "$KEY" ] || { echo "!! signing key not found: $KEY"; exit 1; }
echo "== building + signing disk_app (v1, v2) =="
"${CROSS}gcc" -nostdlib -nostartfiles -Wl,-Ttext=0x10000000 \
-o "$HERE/disk_app.elf" "$HERE/disk_app.S"
( cd "$ROOT" && \
IMAGE_HEADER_SIZE="$IMG_HDR" ./tools/keytools/sign --ecc384 --sha384 \
"$HERE/disk_app.elf" "$KEY" 1 >/dev/null && \
IMAGE_HEADER_SIZE="$IMG_HDR" ./tools/keytools/sign --ecc384 --sha384 \
"$HERE/disk_app.elf" "$KEY" 2 >/dev/null )
ls -la "$HERE"/disk_app_v1_signed.bin "$HERE"/disk_app_v2_signed.bin
echo "== fetching RPi BCM2711 firmware =="
for f in start4.elf fixup4.dat bcm2711-rpi-cm4.dtb; do
[ -f "$HERE/fw/$f" ] || { mkdir -p "$HERE/fw"; curl -fsSL -o "$HERE/fw/$f" "$FWBASE/$f"; }
done
cat > "$HERE/fw/config.txt" <<'EOF'
arm_64bit=1
kernel=kernel8.img
# wolfBoot is linked absolutely at 0x200000; pin the load address to match.
kernel_address=0x200000
enable_uart=1
uart_2ndstage=1
dtoverlay=disable-bt
init_uart_clock=48000000
init_uart_baud=115200
EOF
cp "$ROOT/wolfboot.bin" "$HERE/fw/kernel8.img"
echo "== artifacts ready in $HERE (fw/ + disk_app_v{1,2}_signed.bin) =="
}
# ---- device write (root) --------------------------------------------------
write_device() {
local t root_src root_pk P1 P2 P3 MP ok p
[ "$(id -u)" -eq 0 ] || { echo "!! writing $DEV requires root"; exit 1; }
for t in sgdisk partprobe mkfs.vfat lsblk udevadm; do
command -v "$t" >/dev/null || { echo "!! missing required tool: $t"; exit 1; }
done
[ -b "$DEV" ] || { echo "!! not a block device: $DEV"; exit 1; }
# Refuse the disk currently backing '/' (a real safety check, not a
# bench-specific node name). Override with ALLOW_ANY_DISK=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
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; }
# A/B partitions are 32M - far larger than the signed disk_app test payload
# (~64KB). They intentionally need not match WOLFBOOT_PARTITION_SIZE (0x4000000
# = 64M) in cm4_emmc.config: the disk path reads only the image at the
# partition start, so the partition just has to be big enough to hold it.
echo "== GPT: p1 boot 128M / p2 image_a 32M / p3 image_b 32M =="
sgdisk --zap-all "$DEV" >/dev/null
sgdisk -n 1:2048:+128M -t 1:0700 -c 1:boot "$DEV" >/dev/null
sgdisk -n 2:0:+32M -t 2:8300 -c 2:image_a "$DEV" >/dev/null
sgdisk -n 3:0:+32M -t 3:8300 -c 3:image_b "$DEV" >/dev/null
partprobe "$DEV"; udevadm settle
P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"
# some kernels use pN naming for nvme/mmc; handle 'p' suffix
[ -b "$P1" ] || { P1="${DEV}p1"; P2="${DEV}p2"; P3="${DEV}p3"; }
# Assert every node exists before any dd: a missing node makes dd silently
# create a regular file instead of writing the partition.
for p in "$P1" "$P2" "$P3"; 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 the mount point 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.
trap 'mountpoint -q "$MP" && umount "$MP"; rmdir "$MP" 2>/dev/null || true' EXIT
mount "$P1" "$MP"
cp "$HERE"/fw/start4.elf "$HERE"/fw/fixup4.dat "$HERE"/fw/bcm2711-rpi-cm4.dtb \
"$HERE"/fw/config.txt "$HERE"/fw/kernel8.img "$MP"/
sync; umount "$MP"
echo "== raw signed images -> A ($P2, v1), B ($P3, v2) =="
dd if="$HERE/disk_app_v1_signed.bin" of="$P2" bs=4k conv=fsync status=none
dd if="$HERE/disk_app_v2_signed.bin" of="$P3" bs=4k conv=fsync status=none
sync
echo "== done. Set BOOT switch OFF, power-cycle, and watch the CM4 mini-UART console. =="
}
prep_artifacts
# if/else (not an AND-OR list): calling write_device as the left operand of
# '&&' would disable set -e for its whole body, so a failed sgdisk/dd would be
# silently ignored and a half-written eMMC reported as success.
if [ -n "$DEV" ]; then
write_device
else
echo "(no device given; artifacts only. Re-run with: sudo bash $0 /dev/sdN)"
fi