Files
bfe-core1106-sdk/build/mk-bootimg.sh
NoahandClaude Fable 5.1 2b6e8a2098 qemu and build: review fixes across the rig driver, boot script, and fetch helpers
Bounded waits and validated arguments in run.sh and ui-drive.sh, a seeded
settings directory and root-only staged rootfs permissions with their own
tests, qmp.py and imgtools.py hardening, the fetch scripts checking what they
download, and ASCII typography throughout. Each fix carries its test under
qemu/tests or tests/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
2026-09-09 19:17:54 -06:00

243 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# Package a bootable boot.img from a kernel this SDK built.
#
# build-kernel.sh produces a zImage and a dtb; this turns them into the image
# the board's U-Boot will actually boot, which was the last thing keeping the
# device build tied to the vendor SDK. The format is not negotiable and was
# established the hard way during M2 bring-up (kernel/docs/m2-boot-on-c8a3.md):
#
# - EXTERNAL-DATA FIT (`mkimage -E -p 0x800`). The FDT metadata stays tiny and
# the kernel/fdt/resource payloads are appended after it. An ordinary
# embedded-data FIT is rejected outright: "FIT: No fit blob".
# - A `resource` MULTI SUB-IMAGE IS MANDATORY. Rockchip's U-Boot reads the DTB
# over its RESC: path from a resource image holding `rk-kernel.dtb` (plus
# any boot logos). Without one: "Failed to load DTB, ret=-19".
# - SYSMEM SENTINEL LOAD ADDRESSES: fdt load 0xffffff00, kernel load/entry
# 0xffffff01. These tell U-Boot's sysmem to place the images itself. Real
# low addresses collide and come back as "No fit blob".
#
# The board's U-Boot has CONFIG_FIT_SIGNATURE off, so the image is not signed.
#
# Usage:
# mk-bootimg.sh --kernel zImage --dtb rv1106-warden.dtb --out boot.img
# [--logo FILE] [--logo-kernel FILE] [--logo-recovery FILE]
# [--logo-verbose FILE]
# [--resource-tool PATH]
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
KERNEL=""; DTB=""; OUT=""; RTOOL=""
LOGO=""; LOGO_KERNEL=""; LOGO_RECOVERY=""; LOGO_VERBOSE=""
while [ $# -gt 0 ]; do
case "$1" in
--kernel) KERNEL="${2:?}"; shift 2 ;;
--dtb) DTB="${2:?}"; shift 2 ;;
--out) OUT="${2:?}"; shift 2 ;;
--logo) LOGO="${2:?}"; shift 2 ;;
--logo-kernel) LOGO_KERNEL="${2:?}"; shift 2 ;;
--logo-recovery) LOGO_RECOVERY="${2:?}"; shift 2 ;;
# The same image as --logo but with a VERBOSE mark. U-Boot draws it by
# name when the splash is tapped, so it must be packed even though nothing
# displays it on an ordinary boot.
--logo-verbose) LOGO_VERBOSE="${2:?}"; shift 2 ;;
--resource-tool) RTOOL="${2:?}"; shift 2 ;;
# Print the header comment (line 1 is the shebang, so start at 2) and
# stop at the first line of code rather than a hardcoded line count --
# a fixed range silently starts printing code again the next time the
# header comment grows or shrinks (see build/fetch-vendor.sh's own
# --help, which had this exact bug).
-h|--help) awk '/^set /{exit} NR>1{print}' "$0"; exit 0 ;;
*) echo "FATAL: unknown argument '$1'" >&2; exit 1 ;;
esac
done
[ -n "$KERNEL" ] && [ -f "$KERNEL" ] || { echo "FATAL: --kernel: no such file" >&2; exit 1; }
[ -n "$DTB" ] && [ -f "$DTB" ] || { echo "FATAL: --dtb: no such file" >&2; exit 1; }
[ -n "$OUT" ] || { echo "FATAL: --out is required" >&2; exit 1; }
command -v mkimage >/dev/null || {
echo "FATAL: mkimage not on PATH (Debian/Ubuntu: u-boot-tools)" >&2; exit 1; }
# fdtget backs the post-build alignment assertions below (the whole point of
# which is that a misaligned image boots fine in CI and fails on a panel), so
# its absence must fail the build rather than silently skip those checks.
command -v fdtget >/dev/null || {
echo "FATAL: fdtget not on PATH (Debian/Ubuntu: device-tree-compiler)" >&2; exit 1; }
# resource_tool is a Rockchip host tool. It has no free-standing source here, so
# it is taken from the vendor SDK when one is present rather than vendored as a
# binary; --resource-tool overrides. FAILS CLOSED, because a boot.img built
# without a resource image looks fine and does not boot.
if [ -z "$RTOOL" ]; then
for c in \
"$HERE/../tools/resource_tool" \
"${WARDEN_VENDOR_SDK:-$HERE/../../../sdk}/sysdrv/tools/pc/toolkits/resource_tool" \
"$HOME/projects/scada/flare-edge/sdk/sysdrv/tools/pc/toolkits/resource_tool"
do
[ -x "$c" ] && { RTOOL="$c"; break; }
done
fi
[ -n "$RTOOL" ] && [ -x "$RTOOL" ] || {
echo "FATAL: resource_tool not found; pass --resource-tool PATH" >&2; exit 1; }
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
# resource_tool packs files under a root dir by their names, and U-Boot looks
# for the DTB under the fixed name rk-kernel.dtb.
cp "$DTB" "$WORKDIR/rk-kernel.dtb"
RES_FILES=(rk-kernel.dtb)
for pair in "$LOGO:logo.bmp" "$LOGO_KERNEL:logo_kernel.bmp" \
"$LOGO_RECOVERY:logo_recovery.bmp" "$LOGO_VERBOSE:logo_verbose.bmp"; do
src="${pair%%:*}"; dst="${pair##*:}"
[ -n "$src" ] || continue
[ -f "$src" ] || { echo "FATAL: logo '$src' not found" >&2; exit 1; }
cp "$src" "$WORKDIR/$dst"
RES_FILES+=("$dst")
done
echo "== resource.img (${RES_FILES[*]})"
( cd "$WORKDIR" && "$RTOOL" --pack --root=. --image=resource.img "${RES_FILES[@]}" >/dev/null )
[ -s "$WORKDIR/resource.img" ] || { echo "FATAL: resource_tool produced nothing" >&2; exit 1; }
cp "$KERNEL" "$WORKDIR/kernel"
cp "$WORKDIR/resource.img" "$WORKDIR/resource"
cp "$DTB" "$WORKDIR/fdt"
cat > "$WORKDIR/boot.its" <<'ITS'
/dts-v1/;
/ {
description = "FIT image with Linux kernel, FDT blob and resource";
images {
fdt {
data = /incbin/("fdt");
type = "flat_dt";
arch = "arm";
compression = "none";
load = <0xffffff00>;
hash { algo = "sha256"; };
};
kernel {
data = /incbin/("kernel");
type = "kernel";
arch = "arm";
os = "linux";
compression = "none";
entry = <0xffffff01>;
load = <0xffffff01>;
hash { algo = "sha256"; };
};
resource {
data = /incbin/("resource");
type = "multi";
arch = "arm";
compression = "none";
hash { algo = "sha256"; };
};
};
configurations {
default = "conf";
conf {
rollback-index = <0x0>;
kernel = "kernel";
fdt = "fdt";
multi = "resource";
};
};
};
ITS
# - SUB-IMAGE ALIGNMENT (`-B 0x200`). `-p` places only the FIRST payload; the
# rest are packed contiguously. U-Boot reads each with
# `blk_off = (FIT_ALIGN(fdt_totalsize) + offset) / blksz` (fit.c:331), a
# truncating divide by the 512-byte eMMC block, where FIT_ALIGN rounds to
# IMAGE_ALIGN_SIZE = 512 (include/image.h:955-958). So TWO things must hold:
# the metadata size must be a multiple of 512, or FIT_ALIGN(T) != T and
# EVERY payload is read late by the difference; and each data-position must
# be a multiple of 512, or the divide drops the remainder.
#
# Measured: without -B this script emitted the kernel at data-position
# 0x9A94 (% 512 = 148) with metadata 1064 (% 512 = 40) -- both faults at
# once. flare-edge 9387cff hit exactly this on hardware: "resource: sha256
# Bad hash" then "No resource file: logo.bmp", fixed by -B 0x200 and
# confirmed by a clean boot.
#
# 512, NOT 2048. An earlier revision of this comment claimed the vendor
# aligns to 0x800 and gated on it. That was drawn from two payloads in one
# build. Across the vendor's own release images on this machine, every
# data-position is a multiple of 512 and only some are multiples of 2048
# (four of eight RELEASE_TEST boot.img files sit at % 2048 = 1536), so a
# 2048 gate rejects images the vendor shipped and U-Boot boots. 0x800 is the
# vendor's -p value -- the absolute position of the first payload -- not an
# alignment.
#
# A -B-capable mkimage is REQUIRED, not preferred. The SDK vendors 2017.09,
# which has no -B, and project/build.sh:64 puts it first on PATH -- so the
# wrong one is what a build inside the SDK environment picks up. Measured:
# that binary produces metadata of 1064 bytes here, not a multiple of 512,
# so FIT_ALIGN rounds it up and every payload is read late. It does NOT
# 512-align this .its natively. Rather than silently emit an image that
# fails on a panel, pick the first mkimage on PATH that supports -B and say
# plainly when there is none.
# >>> mkimage-probe (extracted verbatim by tests/mk-bootimg/run-probe-tests.sh)
MKIMAGE=""
_IFS_SAVE="$IFS"; IFS=:
for _d in $PATH; do
_c="$_d/mkimage"
[ -x "$_c" ] || continue
# mkimage with no args prints its usage and exits non-zero; under the
# pipefail set at the top of this file that failure would sink the whole
# pipeline, so the probe would reject every mkimage including good ones.
if { "$_c" 2>&1 || true; } | grep -q -- '-B '; then MKIMAGE="$_c"; break; fi
done
IFS="$_IFS_SAVE"
# <<< mkimage-probe
if [ -z "$MKIMAGE" ]; then
echo "FATAL: no mkimage on PATH supports -B (sub-image alignment)." >&2
echo " The SDK vendors mkimage 2017.09, which does not, and its output" >&2
echo " is not 512-aligned here. Install u-boot-tools (2025.01 works)" >&2
echo " and make sure it precedes the SDK tools on PATH." >&2
exit 1
fi
echo "== FIT (external data, -E -p 0x800 -B 0x200) using $MKIMAGE"
( cd "$WORKDIR" && "$MKIMAGE" -f boot.its -E -p 0x800 -B 0x200 boot.img >/dev/null )
# Assert what U-Boot actually requires, on every build: the failure is silent --
# a misread offset does not fail the build, it fails on a panel, and sometimes
# only as a missing logo. Computed once and reused below (the embedded-data-FIT
# check further down needs the same value) so a future fix to how this is read
# cannot land in one check and not the other.
meta="$(od -An -tu4 -j4 -N4 --endian=big "$WORKDIR/boot.img" | tr -d ' ')"
if [ $(( meta % 512 )) -ne 0 ]; then
echo "FATAL: FIT metadata is $meta bytes, not a multiple of 512;" >&2
echo " FIT_ALIGN would round it up and every payload reads late" >&2
exit 1
fi
for _n in fdt kernel resource; do
# fdtget's presence is checked up front; a failure here means the FIT this
# script just built is malformed, not that the field is legitimately
# absent (mkimage -E gives every one of these images a data-position). Fail
# loud rather than treat an empty read as nothing to check.
if ! _pos="$(fdtget -t u "$WORKDIR/boot.img" "/images/$_n" data-position 2>&1)"; then
echo "FATAL: fdtget could not read /images/$_n data-position: $_pos" >&2
exit 1
fi
if [ $(( _pos % 512 )) -ne 0 ]; then
echo "FATAL: /images/$_n data-position $_pos is not 512-aligned;" >&2
echo " U-Boot's truncating block divide would read the wrong offset" >&2
exit 1
fi
done
# A FIT whose metadata swelled to the size of the whole image is an
# embedded-data build, which this U-Boot rejects. Catch it here rather than on
# a panel that will not come back.
total="$(stat -c %s "$WORKDIR/boot.img")"
if [ "${meta:-0}" -ge 4096 ] || [ "${meta:-0}" -ge "$total" ]; then
echo "FATAL: FIT metadata is ${meta} bytes of a ${total}-byte image: that is an" >&2
echo " embedded-data FIT and U-Boot will report 'No fit blob'." >&2
exit 1
fi
install -m 0644 "$WORKDIR/boot.img" "$OUT"
echo "boot.img: $OUT ($(stat -c %s "$OUT") bytes, FIT metadata ${meta} bytes)"