mkimage -p only places the FIRST payload. The rest are packed contiguously, and U-Boot reads each by dividing its data-position by the 512-byte block size with a truncating divide -- so a payload that is not block-aligned is read from the wrong offset. Measured: without -B this script emitted the kernel at 0x9A94, data-position % 512 = 148, which is exactly the failing shape. That failure is on record. flare-edge 9387cff (2026-09-01) hit it on a boot_b FIT: "unaligned sub-images fail the RESC loader's sha256 and the logo silently falls back to 'No resource file'". It landed on -B 0x200, inferred from noticing the proven image was "512-aligned" without checking for a larger factor. The proven images are aligned to 0x800, not merely to 512. Measured on the pair that boots this board today: boot.img fdt/kernel/resource at 0x800 / 0x12800 / 0x3A7800, recovery.img at 0x800 / 0x12800 / 0x3F8800 -- every one a multiple of 2048. So 0x800 satisfies everything 0x200 does, since every 0x800 multiple is a 0x200 multiple, and additionally reproduces the vendor layout exactly. The cost is at most 2 KiB of padding. Where a remembered rule and a booting image disagree, match the image. The alignment is now also asserted at build time rather than trusted. It runs on every build because the failure it catches is silent -- a wrong offset does not fail the build, it fails on a panel, and only sometimes visibly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
182 lines
7.6 KiB
Bash
Executable File
182 lines
7.6 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 0x800`). `-p` only places the FIRST payload; the
|
|
# rest are packed contiguously, and U-Boot reads each one by dividing its
|
|
# data-position by the 512-byte block size with a TRUNCATING divide. A
|
|
# payload that is not block-aligned is therefore read from the wrong offset:
|
|
# the RESC loader's sha256 fails and logo.bmp silently does not load
|
|
# (observed 2026-09-01, flare-edge 9387cff).
|
|
#
|
|
# MEASURED, both directions. Without -B, this script produced kernel at
|
|
# 0x9A94 (data-position % 512 = 148) -- misaligned, exactly the failing
|
|
# shape. The vendor images that actually boot this board are aligned to
|
|
# 0x800, not merely to 512: boot.img has fdt/kernel/resource at 0x800,
|
|
# 0x12800, 0x3A7800 and recovery.img at 0x800, 0x12800, 0x3F8800 -- every
|
|
# one a multiple of 2048.
|
|
#
|
|
# 9387cff recorded `-B 0x200` from that failure. It is right but weaker than
|
|
# it needed to be: it was inferred from noticing the proven image was
|
|
# "512-aligned" without checking for a larger factor. 0x800 satisfies
|
|
# everything 0x200 does (every 0x800 multiple is a 0x200 multiple) and
|
|
# additionally reproduces the vendor layout exactly, for at most 2 KiB of
|
|
# padding. When the two disagree, match the image that is known to boot.
|
|
echo "== FIT (external data, -E -p 0x800 -B 0x800)"
|
|
( cd "$WORKDIR" && mkimage -f boot.its -E -p 0x800 -B 0x800 boot.img >/dev/null )
|
|
|
|
# Every payload must land on a 0x800 boundary. This is the check that would have
|
|
# caught the unaligned build before it reached a panel, so it runs on every
|
|
# build rather than living in a separate tool nobody remembers to invoke.
|
|
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 % 2048 )) -ne 0 ]; then
|
|
echo "FATAL: /images/$_n data-position $_pos is not 0x800-aligned;" >&2
|
|
echo " U-Boot's block divide would read it from 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}" -gt 65536 ] || [ "${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)"
|