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