Files
bfe-core1106-sdk/build/mk-bootimg.sh
T
NoahandClaude Opus 5 f0204f2b53 Fix mkimage -B probe defeated by pipefail
The probe piped the candidate binary into grep to look for -B support.
mkimage with no arguments prints usage and exits non-zero, and this script
runs under `set -euo pipefail`, so the pipeline reported failure even when
grep matched. Every candidate was rejected and the script failed closed with
"no mkimage on PATH supports -B" while a capable mkimage 2025.01 sat first
on PATH, making it impossible to build a boot.img at all.

Neutralise the probed command's exit status before the pipe. Sentinel
comments now bracket the probe so the new regression test extracts and
exercises the shipping code rather than a copy of it.

Closes #17

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
2026-09-04 17:06:54 -06:00

225 lines
9.8 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]
# [--resource-tool PATH]
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
KERNEL=""; DTB=""; OUT=""; RTOOL=""
LOGO=""; LOGO_KERNEL=""; LOGO_RECOVERY=""
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 ;;
--resource-tool) RTOOL="${2:?}"; shift 2 ;;
-h|--help) sed -n '2,25p' "$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; }
# 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"; 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.
#
# The flag is feature-detected because the SDK vendors mkimage 2017.09,
# which has no -B at all and dies with "invalid option -- 'B'". Its packer
# already 512-aligns, so omitting the flag there is correct rather than a
# fallback. project/build.sh prepends the SDK tool dir to PATH, so that
# binary IS what a build inside the SDK environment resolves.
# 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.
_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
_pos="$(fdtget -t u "$WORKDIR/boot.img" "/images/$_n" data-position 2>/dev/null || true)"
[ -n "$_pos" ] || continue
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.
meta="$(od -An -tu4 -j4 -N4 --endian=big "$WORKDIR/boot.img" | tr -d ' ')"
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)"