#!/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)"