diff --git a/uboot-custom-imx8mp-evk/.gitignore b/uboot-custom-imx8mp-evk/.gitignore new file mode 100644 index 0000000..7f74f63 --- /dev/null +++ b/uboot-custom-imx8mp-evk/.gitignore @@ -0,0 +1,5 @@ +.avocado/ +.avocado-state +.downloads/ +keys/*.key +keys/*.crt diff --git a/uboot-custom-imx8mp-evk/README.md b/uboot-custom-imx8mp-evk/README.md new file mode 100644 index 0000000..6e12f1a --- /dev/null +++ b/uboot-custom-imx8mp-evk/README.md @@ -0,0 +1,49 @@ +--- +language: C +targets: + - imx8mp-evk +topics: + - cross-compilation + - bootloader + - secure-boot +--- + +# Custom U-Boot (HAB + FIT signature) for i.MX 8M Plus EVK + +A reference runtime that demonstrates how to cross-compile a custom +`imx-boot` bundle (TF-A BL31 + U-Boot SPL/proper + DDR firmware) from +source using the Avocado SDK and replace the upstream-installed +bootloader on an NXP i.MX 8M Plus EVK. + +- Cross-compile NXP's `uboot-imx`, `imx-atf`, and `imx-mkimage` for + imx8mp inside the SDK container, bundled with NXP's redistributable + DDR4 firmware. +- Enable `CONFIG_IMX_HAB=y` so the resulting flash.bin is HAB-ready + (parses CSF blobs, can be signed against the SoC's SRK fuses). +- Enable `CONFIG_FIT_SIGNATURE=y` plus a placeholder /signature node in + the U-Boot control DTB. After build, `insert-fit-pubkey.sh` runs + `mkimage -K` to inject your RSA pubkey into the placeholder and + rebuilds flash.bin — letting you rotate FIT signing keys without + rebuilding U-Boot itself. +- Use the `runtimes..packages..{compile,install}` hook (mirrors + the extension `packages..{compile,install}` form) to drop the + built `imx-boot` into the runtime build dir, where stone bundles it + into the os-bundle in place of any upstream artifact. + +## Why custom U-Boot? + +The upstream Avocado-provided `imx-boot` is unsigned and uses a generic +`/signature` configuration. Two reasons you'd build your own: + +1. **HAB closure.** Closing HAB on a production device requires fusing + the SRK hash and shipping a flash.bin signed against that key. You + need control over the bootloader binary to attach the right CSF, and + you typically tie HAB closure to a hardware certification milestone. +2. **FIT image verification.** With `CONFIG_FIT_SIGNATURE=y` and a + pubkey embedded in the U-Boot DTB, the bootloader will refuse to + load a kernel/initramfs that wasn't signed with the matching private + key. The dtsi placeholder lets you ship the same flash.bin to many + units and rotate the pubkey without a U-Boot rebuild. + +See [getting_started.md](getting_started.md) for the build, the +`mkimage -K` post-build flow, and notes on closing HAB. diff --git a/uboot-custom-imx8mp-evk/avocado.yaml b/uboot-custom-imx8mp-evk/avocado.yaml new file mode 100644 index 0000000..1a7c7d0 --- /dev/null +++ b/uboot-custom-imx8mp-evk/avocado.yaml @@ -0,0 +1,85 @@ +default_target: imx8mp-evk +supported_targets: + - imx8mp-evk + +distro: + release: 2024 + channel: edge + +## +## Runtimes +## + +runtimes: + dev: + extensions: + - avocado-ext-dev + - avocado-ext-sshd-dev + - avocado-bsp-{{ avocado.target.board }} + - config-dev + packages: + avocado-runtime: '*' + + # Cross-compile imx-boot from source via the SDK and drop it into + # the runtime build dir, overriding any upstream-provided imx-boot. + # The compile step runs uboot-compile.sh (TF-A + U-Boot + DDR FW + # bundled by imx-mkimage); the install step copies flash.bin to + # $AVOCADO_RUNTIME_BUILD_DIR/imx-boot so stone bundles ours. + uboot: + compile: uboot + install: uboot-install.sh + +## +## Extensions +## + +extensions: + avocado-ext-dev: + source: + type: package + version: '*' + + avocado-ext-sshd-dev: + source: + type: package + version: '*' + + avocado-bsp-{{ avocado.target.board }}: + source: + type: package + version: '*' + + config-dev: + version: 0.1.0 + + users: + root: + password: '' + +## +## SDK +## + +sdk: + image: docker.io/avocadolinux/sdk:{{ avocado.distro.release }}-{{ avocado.distro.channel }} + + container_args: + - --network=host + + compile: + uboot: + compile: uboot-compile.sh + clean: uboot-clean.sh + + # Native build tools for U-Boot, TF-A, and imx-mkimage. nativesdk-* + # packages are installed into the SDK host sysroot (not the target + # sysroot), so they go here at the top level alongside + # avocado-sdk-toolchain — same pattern as the linux-custom-kernel + # reference. + packages: + avocado-sdk-toolchain: '*' + nativesdk-bc: '*' + nativesdk-bison: '*' + nativesdk-flex: '*' + nativesdk-openssl-bin: '*' + nativesdk-dtc: '*' diff --git a/uboot-custom-imx8mp-evk/getting_started.md b/uboot-custom-imx8mp-evk/getting_started.md new file mode 100644 index 0000000..722c2ba --- /dev/null +++ b/uboot-custom-imx8mp-evk/getting_started.md @@ -0,0 +1,175 @@ +# Getting Started — Custom U-Boot for i.MX 8M Plus EVK + +This guide walks you through cross-compiling a custom imx-boot +bundle from source and flashing it onto an i.MX 8M Plus EVK. The +reference matches the pins nxp-imx/meta-imx ships at +`scarthgap-6.6.36-2.1.0` (uboot-imx `lf_v2024.04`, imx-atf `lf_v2.10`, +imx-mkimage `lf-6.6.36_2.1.0`, firmware-imx 8.25-27879f8) with HAB and +FIT signature support enabled, and replaces the bootloader Avocado +would otherwise pull in. + +## Prerequisites + +- Linux host (Ubuntu 22.04+, Fedora 39+) — or macOS with Docker Desktop. +- [Docker](https://www.docker.com/products/docker-desktop/) running. +- Avocado CLI: see [docs.peridio.com](https://docs.peridio.com). +- An i.MX 8M Plus EVK in serial-download (USB-OTG) mode for flashing + via `uuu-emmc`. + +## Initialize + +```bash +avocado init --reference uboot-custom-imx8mp-evk uboot-imx8mp-evk +cd uboot-imx8mp-evk +``` + +## Install + +```bash +avocado install -f +``` + +This pulls the Avocado SDK container and installs `nativesdk-bc`, +`nativesdk-bison`, `nativesdk-flex`, `nativesdk-openssl`, +`nativesdk-dtc`, `nativesdk-util-linux` — the build deps needed by +U-Boot, TF-A, and imx-mkimage. + +## Build + +```bash +avocado build +``` + +The build runs `uboot-compile.sh` inside the SDK container, which: + +1. Saves `CROSS_COMPILE` / `ARCH` from the SDK env, then unsets the + userspace `CC`, `CFLAGS`, `LDFLAGS` exports that fight the U-Boot + and TF-A build systems (same trick the Linux kernel reference uses). +2. Clones `uboot-imx` (branch `lf_v2024.04`, SRCREV + `de16f4f1`), `imx-atf` (branch `lf_v2.10`, SRCREV `28affcae`), + `imx-mkimage` (branch `lf-6.6.36_2.1.0`, SRCREV `4622115c`), and + downloads `firmware-imx-8.25-27879f8.bin` from NXP's mirror — the + exact pins meta-imx ships at scarthgap-6.6.36-2.1.0. +3. Appends `patches/avocado.cfg` + `patches/env-mmc.cfg` onto + `imx8mp_evk_defconfig`, and adds `#include + "avocado-fit-signature.dtsi"` to `imx8mp-evk-u-boot.dtsi` so the + control DTB carries a placeholder /signature node. +4. Builds TF-A BL31, builds U-Boot, generates a redundant `uboot.env` + from `patches/avocado-imx8mp-evk.txt`. +5. Stages the binaries into `iMX8M/` and runs + `make SOC=iMX8MP flash_evk` to produce `flash.bin` (== `imx-boot`). + +`uboot-install.sh` then drops `flash.bin` and `uboot.env` into +`$AVOCADO_RUNTIME_BUILD_DIR`, so the stone bundle assembled by +`avocado build` carries our bootloader. + +You can also rebuild only the bootloader without re-assembling the +runtime: + +```bash +avocado sdk compile uboot +``` + +## Inject a FIT signing pubkey (the "replace later" workflow) + +The bootloader you just built has `/signature/key-rt-prod` as a +placeholder — empty `rsa,modulus` etc. To actually have it verify FIT +images, populate that node with your dev pubkey: + +```bash +avocado sdk run -E -- bash insert-fit-pubkey.sh +avocado build +``` + +`insert-fit-pubkey.sh`: + +1. Generates `keys/dev.key` + `keys/dev.crt` if missing. +2. Builds a throwaway FIT image referencing `key-name-hint = "dev"`. +3. Runs `mkimage -F -K …` so mkimage extracts the pubkey from + `keys/dev.key` and patches it into the placeholder /signature node + *in the already-built U-Boot DTB* — no full U-Boot rebuild. +4. Re-runs `imx-mkimage` to fold the patched DTB back into a fresh + `flash.bin`. + +Re-running `avocado build` then re-stages the updated bootloader in the +runtime build dir. + +To rotate the key, drop a new `keys/dev.key` (or change `KEY_NAME` in +the script) and re-run `insert-fit-pubkey.sh` + `avocado build`. The +core bootloader binary doesn't change — only the pubkey block in the +control DTB. + +To sign your real FIT images (kernel + dtb + initramfs) for this +bootloader to accept: + +```bash +mkimage -F -k keys -r +``` + +## Provision the EVK + +Put the EVK in serial-download mode (set boot DIPs SW4 to +`0011 0010 0010 1000`, the documented serial-download position — see +the EVK user manual; do not press an arbitrary BOOT+RESET combo) and +plug a USB-C cable into the OTG port: + +```bash +avocado provision -r dev --profile uuu-emmc +``` + +uuu hands `flash.bin` to the boot ROM via SDPS, then writes the OS +bundle (rootfs / initramfs / kernel) to eMMC. Reset the board with +DIPs back to eMMC boot — your custom HAB-ready U-Boot runs first. + +## Closing HAB (production-only) + +The build leaves HAB *open* — i.e., this flash.bin will boot on any +imx8mp without signature checks. Closing HAB is a one-way fuse blow: +**do not do this on a dev board you want to recover.** + +The full procedure is documented in NXP's +[i.MX Secure Boot on AHAB and HAB CST](https://www.nxp.com/docs/en/application-note/AN12056.pdf) +guide. Sketch: + +1. Generate SRK / CSF / IMG keys with NXP's CST. +2. Run `cst -i csf-spl.txt` and `cst -i csf-uboot.txt` against the + `flash.bin` produced by this reference. Use the `imx_log` block at + the start of the build output to find the load addresses CST needs. +3. Append the resulting CSF blobs to `flash.bin` at the offsets the + first stage reports. +4. Verify with `hab_status` from the U-Boot prompt — it should report + "No HAB Events Found!" before you close. +5. Blow the SRK_HASH fuse via `fuse prog`. Once closed, only flash.bin + binaries signed by your SRK chain will boot. + +## Customize + +### Different NXP release line + +Bump the branch + SRCREV pairs at the top of `uboot-compile.sh` to +match the meta-imx tag for the release line you want. The canonical +source is the matching `recipes-bsp/{imx-atf,u-boot,imx-mkimage}/*.bb` +and `recipes-bsp/firmware-imx/firmware-imx-*.inc` in +[nxp-imx/meta-imx](https://github.com/nxp-imx/meta-imx) — open the +files at the tag for your release line and copy the +`SRCBRANCH` / `SRCREV` / firmware-imx PV + IMX_SRCREV_ABBREV values +verbatim. + +### Different i.MX 8M variant + +Swap `UBOOT_DEFCONFIG`, `ATF_PLATFORM`, `MKIMAGE_SOC`, and +`MKIMAGE_TARGET` in `uboot-compile.sh`. Check +`imx-mkimage/iMX8M/soc.mak` for the available targets and the staging +filenames each one expects. + +### Customize the boot env + +Edit `patches/avocado-imx8mp-evk.txt`. The format is mkenvimage(1) — +each line is a `key=value` U-Boot env entry. + +### Different supported_targets + +This reference is hard-coded to imx8mp-evk because of the imx-mkimage +target name. To support multiple boards, switch on +`{{ avocado.target.board }}` in `avocado.yaml` and gate the +`uboot-compile.sh` choices off `$AVOCADO_TARGET`. diff --git a/uboot-custom-imx8mp-evk/insert-fit-pubkey.sh b/uboot-custom-imx8mp-evk/insert-fit-pubkey.sh new file mode 100755 index 0000000..50c8b81 --- /dev/null +++ b/uboot-custom-imx8mp-evk/insert-fit-pubkey.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# +# Post-build helper: inject a FIT signing pubkey into the U-Boot +# control DTB and rebuild flash.bin so the bootloader will only verify +# FIT images signed by the matching private key. +# +# Workflow: +# 1. Generate (or reuse) an RSA-2048 dev key under keys/. +# 2. Build a placeholder FIT image referencing key-name-hint=dev. +# 3. mkimage -K populates the placeholder /signature/key-rt-prod node +# in the U-Boot DTB at $AVOCADO_BUILD_DIR/uboot-imx/u-boot.dtb. +# 4. Re-run imx-mkimage to fold the patched DTB back into flash.bin. +# 5. Re-run uboot-install.sh to refresh the runtime build dir. +# +# Run this OUTSIDE of `avocado build` (i.e., from the host shell after +# `avocado sdk compile uboot` has produced the initial flash.bin): +# +# avocado sdk run -E -- bash insert-fit-pubkey.sh +# +# `-E` keeps the SDK env so mkimage / openssl from the SDK toolchain +# resolve, and bind-mounts the project dir as /opt/src. +# +set -euo pipefail + +UBOOT_DEFCONFIG="imx8mp_evk_defconfig" +MKIMAGE_SOC="iMX8MP" +MKIMAGE_TARGET="flash_evk" + +KEY_NAME="dev" +KEYS_DIR="$(pwd)/keys" +BUILD_ROOT="${AVOCADO_BUILD_DIR:-$AVOCADO_SDK_PREFIX/build/uboot}" +UBOOT_DIR="${BUILD_ROOT}/uboot-imx" +MKIMAGE_DIR="${BUILD_ROOT}/imx-mkimage" +STAGE="${MKIMAGE_DIR}/iMX8M" + +if [ ! -d "${UBOOT_DIR}" ]; then + echo "[ERROR] No U-Boot build at ${UBOOT_DIR}." >&2 + echo " Run 'avocado sdk compile uboot' first." >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# 1. Generate the dev RSA key if missing. Production keys are HSM-backed; +# this helper only handles the dev workflow. +# --------------------------------------------------------------------------- +mkdir -p "${KEYS_DIR}" +if [ ! -f "${KEYS_DIR}/${KEY_NAME}.key" ]; then + echo "Generating dev RSA-2048 key at ${KEYS_DIR}/${KEY_NAME}.key" + openssl genpkey -algorithm RSA -out "${KEYS_DIR}/${KEY_NAME}.key" \ + -pkeyopt rsa_keygen_bits:2048 + openssl req -batch -new -x509 \ + -key "${KEYS_DIR}/${KEY_NAME}.key" \ + -out "${KEYS_DIR}/${KEY_NAME}.crt" \ + -subj "/CN=Avocado FIT Dev/" +fi + +# --------------------------------------------------------------------------- +# 2. Build a minimal FIT image with a dummy payload referenced by a +# `signed-config` node. mkimage -K reads the FIT, signs the config, +# and copies the resulting pubkey into the U-Boot DTB's signature +# node. The signed FIT itself is throwaway here; the real FIT comes +# later (e.g. a kernel + dtb + ramdisk image). +# --------------------------------------------------------------------------- +ITS="${BUILD_ROOT}/avocado-fit-pubkey.its" +ITB="${BUILD_ROOT}/avocado-fit-pubkey.itb" + +cat > "${ITS}" << EOF +/dts-v1/; + +/ { + description = "Pubkey-injection placeholder"; + #address-cells = <1>; + + images { + blob { + data = /incbin/("/dev/null"); + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x80200000>; + entry = <0x80200000>; + hash-1 { algo = "sha256"; }; + }; + }; + + configurations { + default = "conf-1"; + conf-1 { + description = "Avocado pubkey-injection config"; + kernel = "blob"; + signature { + algo = "sha256,rsa2048"; + key-name-hint = "${KEY_NAME}"; + sign-images = "kernel"; + }; + }; + }; +}; +EOF + +# `mkimage -f` builds the .itb. `mkimage -F -K` re-signs and writes the +# pubkey into the target DTB (-K). +"${UBOOT_DIR}/tools/mkimage" -f "${ITS}" -r "${ITB}" +"${UBOOT_DIR}/tools/mkimage" -F -k "${KEYS_DIR}" -K "${UBOOT_DIR}/u-boot.dtb" -r "${ITB}" + +echo "Patched ${UBOOT_DIR}/u-boot.dtb with pubkey '${KEY_NAME}'." + +# --------------------------------------------------------------------------- +# 3. Re-stage and rebuild flash.bin so the patched DTB takes effect. +# --------------------------------------------------------------------------- +echo "Re-staging u-boot artifacts..." +cp -f "${UBOOT_DIR}/u-boot-nodtb.bin" "${STAGE}/" +cp -f "${UBOOT_DIR}/u-boot.dtb" "${STAGE}/" +cp -f "${UBOOT_DIR}/u-boot.bin" "${STAGE}/" + +echo "Rebuilding flash.bin..." +make -C "${MKIMAGE_DIR}" SOC="${MKIMAGE_SOC}" "${MKIMAGE_TARGET}" +cp -f "${MKIMAGE_DIR}/iMX8M/flash.bin" "${BUILD_ROOT}/flash.bin" + +echo "" +echo "================================================================" +echo "Updated flash.bin: ${BUILD_ROOT}/flash.bin" +echo "" +echo "Next:" +echo " - 'avocado build' to refresh the runtime build dir." +echo " - Sign your real FIT image with:" +echo " mkimage -F -k keys -r " +echo " - The bootloader will only load FITs signed by '${KEY_NAME}'." +echo "================================================================" diff --git a/uboot-custom-imx8mp-evk/keys/.gitkeep b/uboot-custom-imx8mp-evk/keys/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/uboot-custom-imx8mp-evk/patches/avocado-fit-signature.dtsi b/uboot-custom-imx8mp-evk/patches/avocado-fit-signature.dtsi new file mode 100644 index 0000000..0a2ec88 --- /dev/null +++ b/uboot-custom-imx8mp-evk/patches/avocado-fit-signature.dtsi @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Avocado FIT-signature placeholder for imx8mp-evk U-Boot. + * + * This dtsi is appended to imx8mp-evk-u-boot.dtsi (the U-Boot-side DTS + * overlay) so the resulting control DTB carries a /signature node with + * a key-rt-prod placeholder subnode. + * + * At build time this DTB is empty: rsa,modulus / rsa,r-squared / + * rsa,n0-inverse / rsa,exponent are absent. The bootloader is built and + * flashed with the placeholder DTB. + * + * After build, before flashing to a device, run + * + * mkimage -F -k -K \ + * -r -o sha256,rsa2048 \ + * + * + * mkimage -K populates the placeholder node IN PLACE in the U-Boot + * control DTB with the public-key bytes derived from the RSA key in + * /dev.key. This is the "replace later" model: build U-Boot + * once, generate (or rotate) FIT signing keys independently, then patch + * the pubkey into the bootloader DTB without rebuilding U-Boot itself. + * + * Re-pack flash.bin via imx-mkimage after running mkimage -K so the + * patched DTB makes it onto the boot partition. The reference's + * insert-fit-pubkey.sh helper script wraps both steps. + * + * required = "conf" makes signature verification mandatory: any FIT + * config the bootloader is asked to load that doesn't reference a key + * present in this /signature node fails to boot. + */ + +&{/} { + signature { + key-rt-prod { + required = "conf"; + algo = "sha256,rsa2048"; + key-name-hint = "dev"; + }; + }; +}; diff --git a/uboot-custom-imx8mp-evk/patches/avocado-imx8mp-evk.txt b/uboot-custom-imx8mp-evk/patches/avocado-imx8mp-evk.txt new file mode 100644 index 0000000..21a3e27 --- /dev/null +++ b/uboot-custom-imx8mp-evk/patches/avocado-imx8mp-evk.txt @@ -0,0 +1,61 @@ +machine=imx8mp-evk +kernel_file=Image +initramfs_file=avocado-image-initramfs-imx8mp-evk.cpio.zst +devicetree_file=imx8mp-evk.dtb + +console=ttymxc1,115200 +devtype=mmc +devnum=1 +mmcblk=1 +bootpart=3 +rootdev=/dev/mmcblk${mmcblk}p6 + +image_addr=0x80200000 +fdt_addr=0x83000000 +ramdisk_addr=0x84000000 + +# Calculate partition based on avocado_boot_slot (set by stone OTA via +# fw_setenv). Defaults to slot A on a fresh device. +avocado_boot_init=\ + if test -n "${avocado_boot_slot}"; then\ + if test "${avocado_boot_slot}" = "a"; then\ + echo "Booting A";\ + setenv bootpart 3;\ + setenv rootdev /dev/mmcblk${mmcblk}p6;\ + else\ + if test "${avocado_boot_slot}" = "b"; then\ + echo "Booting B";\ + setenv bootpart 4;\ + setenv rootdev /dev/mmcblk${mmcblk}p7;\ + else\ + if test "${avocado_boot_slot}" = "r"; then\ + echo "Booting Recovery";\ + setenv bootpart 5;\ + setenv rootdev;\ + else\ + echo "avocado_boot_slot=${avocado_boot_slot} invalid";\ + echo "Booting Recovery";\ + setenv bootpart 5;\ + setenv rootdev;\ + fi;\ + fi;\ + fi;\ + else\ + echo "avocado_boot_slot undefined";\ + echo "Booting A";\ + setenv bootpart 3;\ + setenv rootdev /dev/mmcblk${mmcblk}p6;\ + fi;\ + if test -n "${rootdev}"; then\ + setenv bootargs audit=0 console=${console} root=${rootdev} rootwait;\ + else\ + setenv bootargs audit=0 console=${console} rootwait;\ + fi; + +load_image=load ${devtype} ${devnum}:${bootpart} ${image_addr} ${kernel_file} +load_devicetree=load ${devtype} ${devnum}:${bootpart} ${fdt_addr} ${devicetree_file} +load_initramfs=load ${devtype} ${devnum}:${bootpart} ${ramdisk_addr} ${initramfs_file} + +avocado_boot=booti ${image_addr} ${ramdisk_addr}:${filesize} ${fdt_addr}; + +bootcmd=run avocado_boot_init load_image load_devicetree load_initramfs avocado_boot diff --git a/uboot-custom-imx8mp-evk/patches/avocado.cfg b/uboot-custom-imx8mp-evk/patches/avocado.cfg new file mode 100644 index 0000000..942794b --- /dev/null +++ b/uboot-custom-imx8mp-evk/patches/avocado.cfg @@ -0,0 +1,53 @@ +# Avocado defconfig overlay for imx8mp_evk_defconfig. +# +# Appended to configs/imx8mp_evk_defconfig before `make`. Only delta +# compared to the upstream NXP defconfig is included here. + +# --- Avocado partition layout (EFI/GPT, GUID-keyed roots). +CONFIG_EFI_PARTITION=y +CONFIG_PARTITION_TYPE_GUID=y + +# --- HAB (i.MX High Assurance Boot, hardware secure boot). +# +# Enables CSF parsing in SPL/u-boot proper so the boot ROM can verify +# this bootloader against fuses (SRK hash) when HAB is closed. After +# this build you still need to: +# 1. Generate SRK keys/certs with NXP's CST. +# 2. Run `cst -i csf-spl.txt` and `cst -i csf-uboot.txt` on the built +# flash.bin (NXP uses the imx_log report to find offsets). +# 3. Append the generated CSF blobs to flash.bin at the right offsets. +# 4. Blow SRK fuses on a target board to lock the chain. +# See README.md "Closing HAB" for the full procedure. +CONFIG_IMX_HAB=y + +# --- FIT image verification with placeholder pubkey in the U-Boot DTB. +# +# The /signature node in the control DTB (added via +# patches/avocado-fit-signature.dtsi) holds a /key-rt-prod placeholder. +# After this build, run `mkimage -K` against the FIT image you intend to +# load to populate the placeholder with your RSA pubkey: +# +# mkimage -F -k -K -r +# +# The dtb is then re-baked into flash.bin (via this same compile pass) +# and the bootloader will only boot FIT images signed by the matching +# private key. +CONFIG_FIT=y +CONFIG_FIT_VERBOSE=y +CONFIG_FIT_SIGNATURE=y +CONFIG_FIT_SIGNATURE_MAX_SIZE=0x10000000 +CONFIG_RSA=y + +# --- Required for FIT verification on the SPL side too (eMMC/SD boot). +CONFIG_SPL_FIT=y +CONFIG_SPL_FIT_SIGNATURE=y +CONFIG_SPL_RSA=y +CONFIG_SPL_RSA_VERIFY=y +CONFIG_SPL_OF_CONTROL=y +CONFIG_SPL_OF_LIBFDT=y + +# --- Verbose boot logs help when debugging signed boot. Drop these for +# production builds to shave a few dozen KB off SPL. +CONFIG_LOG=y +CONFIG_LOG_MAX_LEVEL=7 +CONFIG_LOG_DEFAULT_LEVEL=7 diff --git a/uboot-custom-imx8mp-evk/patches/env-mmc.cfg b/uboot-custom-imx8mp-evk/patches/env-mmc.cfg new file mode 100644 index 0000000..d0adcce --- /dev/null +++ b/uboot-custom-imx8mp-evk/patches/env-mmc.cfg @@ -0,0 +1,21 @@ +# Redundant U-Boot env in MMC. +# +# CONFIG_USE_BOOTCOMMAND keeps the built-in bootcmd as a fallback when +# the MMC env hasn't been written yet (e.g. fresh-from-factory or after +# an erase). On Avocado's flow, the compiled uboot.env supplies the A/B +# slot logic at runtime; the built-in bootcmd just keeps fastboot +# reachable for uuu provisioning when there is no env. +CONFIG_USE_BOOTCOMMAND=y + +# Disable other env backends. +# CONFIG_ENV_IS_IN_FLASH is not set +# CONFIG_ENV_IS_IN_SPI_FLASH is not set +CONFIG_ENV_IS_NOWHERE=y +CONFIG_ENV_IS_IN_MMC=y + +# 128KB redundant env, located at 4MB / 4.25MB. Matches the partition +# offsets in stone-imx8mp-evk.json's `uboot-env` partition entry. +CONFIG_SYS_REDUNDAND_ENVIRONMENT=y +CONFIG_ENV_SIZE=0x20000 +CONFIG_ENV_OFFSET=0x400000 +CONFIG_ENV_OFFSET_REDUND=0x440000 diff --git a/uboot-custom-imx8mp-evk/uboot-clean.sh b/uboot-custom-imx8mp-evk/uboot-clean.sh new file mode 100755 index 0000000..6e985af --- /dev/null +++ b/uboot-custom-imx8mp-evk/uboot-clean.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Clean the imx-boot build directory. +# +# Removes $AVOCADO_BUILD_DIR (uboot-imx, imx-atf, imx-mkimage, +# firmware-imx, intermediate flash.bin). Cached upstream tarballs under +# .downloads/ on the host are left in place so re-runs don't re-fetch +# from the network. +# +set -e + +if [ -z "${AVOCADO_BUILD_DIR:-}" ]; then + echo "[ERROR] AVOCADO_BUILD_DIR is not set." >&2 + exit 1 +fi + +if [ ! -d "${AVOCADO_BUILD_DIR}" ]; then + echo "Build directory ${AVOCADO_BUILD_DIR} does not exist, nothing to clean." + exit 0 +fi + +echo "================================================================" +echo "Cleaning imx-boot build directory: ${AVOCADO_BUILD_DIR}" +echo "================================================================" + +rm -rf "${AVOCADO_BUILD_DIR}" + +echo "Build directory cleaned." diff --git a/uboot-custom-imx8mp-evk/uboot-compile.sh b/uboot-custom-imx8mp-evk/uboot-compile.sh new file mode 100755 index 0000000..25a470b --- /dev/null +++ b/uboot-custom-imx8mp-evk/uboot-compile.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env bash +# +# Cross-compile imx-boot (TF-A BL31 + U-Boot SPL/proper + DDR firmware, +# bundled by imx-mkimage) for the i.MX 8M Plus EVK using the Avocado SDK. +# +# Components match nxp-imx/meta-imx @ scarthgap-6.6.36-2.1.0 (the layer +# avocado-os pulls into its imx8mp-evk build): +# - uboot-imx nxp-imx/uboot-imx branch lf_v2024.04 +# - imx-atf nxp-imx/imx-atf branch lf_v2.10 +# - imx-mkimage nxp-imx/imx-mkimage branch lf-6.6.36_2.1.0 +# - firmware-imx NXP redistributable 8.25-27879f8 +# +# SRCREVs are pinned for reproducibility — bump together when bumping the +# avocado-os scarthgap pin. +# +# The SDK entrypoint sources the OE environment, so CROSS_COMPILE / ARCH / +# OECORE_NATIVE_SYSROOT / OECORE_TARGET_SYSROOT are already set. As with +# the Linux kernel build, the userspace CC/CFLAGS/LDFLAGS that the SDK +# also exports collide with U-Boot's and TF-A's internal build systems — +# we save what we need and unset the rest. +# +# Output: $AVOCADO_BUILD_DIR/flash.bin (the i.MX boot bundle, == imx-boot) +# +set -euo pipefail + +UBOOT_BRANCH="lf_v2024.04" +UBOOT_SRCREV="de16f4f17221b2ff72b8cb18c28cd8a29f3c2710" +ATF_BRANCH="lf_v2.10" +ATF_SRCREV="28affcae957cb8194917b5246276630f9e6343e1" +MKIMAGE_BRANCH="lf-6.6.36_2.1.0" +MKIMAGE_SRCREV="4622115cbc037f79039c4522faeced4aabea986b" + +FIRMWARE_IMX_VERSION="8.25" +FIRMWARE_IMX_SRCREV_ABBREV="27879f8" +FIRMWARE_IMX_TARBALL="firmware-imx-${FIRMWARE_IMX_VERSION}-${FIRMWARE_IMX_SRCREV_ABBREV}.bin" +FIRMWARE_IMX_DIR_NAME="firmware-imx-${FIRMWARE_IMX_VERSION}-${FIRMWARE_IMX_SRCREV_ABBREV}" +FIRMWARE_IMX_URL="https://www.nxp.com/lgfiles/NMG/MAD/YOCTO/${FIRMWARE_IMX_TARBALL}" + +UBOOT_DEFCONFIG="imx8mp_evk_defconfig" +ATF_PLATFORM="imx8mp" +MKIMAGE_SOC="iMX8MP" +MKIMAGE_TARGET="flash_evk" + +echo "================================================================" +echo "Compiling imx-boot for i.MX 8M Plus EVK" +echo "================================================================" + +# --------------------------------------------------------------------------- +# Validate AVOCADO_BUILD_DIR (per-section dir set by `sdk compile`). +# --------------------------------------------------------------------------- +if [ -z "${AVOCADO_BUILD_DIR:-}" ]; then + echo "[ERROR] AVOCADO_BUILD_DIR is not set." >&2 + exit 1 +fi +mkdir -p "${AVOCADO_BUILD_DIR}" + +# Cache downloads under the source dir so they survive `sdk clean` and +# don't re-fetch on every run. Source-tree clones live inside +# AVOCADO_BUILD_DIR so `sdk clean uboot` discards them. +HOST_SRC="$(pwd)" +DOWNLOADS="${HOST_SRC}/.downloads" +mkdir -p "${DOWNLOADS}" + +UBOOT_DIR="${AVOCADO_BUILD_DIR}/uboot-imx" +ATF_DIR="${AVOCADO_BUILD_DIR}/imx-atf" +MKIMAGE_DIR="${AVOCADO_BUILD_DIR}/imx-mkimage" +FIRMWARE_DIR="${AVOCADO_BUILD_DIR}/${FIRMWARE_IMX_DIR_NAME}" +PATCHES_DIR="${HOST_SRC}/patches" + +# --------------------------------------------------------------------------- +# Cross-compile environment. +# +# As with the kernel reference, the SDK exports a userspace toolchain +# (CC, CFLAGS, LDFLAGS, ...). U-Boot and TF-A derive their own toolchain +# from CROSS_COMPILE; the userspace exports fight that and produce +# linker errors. Save CROSS_COMPILE + a target sysroot for HOSTCC, then +# clear the rest. +# --------------------------------------------------------------------------- +_CROSS_COMPILE="${CROSS_COMPILE:-}" +if [ -z "${_CROSS_COMPILE}" ]; then + echo "[ERROR] CROSS_COMPILE not exported by SDK env." >&2 + exit 1 +fi +_TARGET_SYSROOT="${OECORE_TARGET_SYSROOT:-}" + +unset CC CXX CPP LD AR AS NM STRIP OBJCOPY OBJDUMP READELF RANLIB +unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS + +export CROSS_COMPILE="${_CROSS_COMPILE}" +export ARCH=arm64 + +# Derive HOSTCC from the SDK env. The Avocado SDK only ships the +# target-prefixed cross-canadian compiler (${CROSS_COMPILE}gcc); there +# is no separate native host gcc. The container has qemu-user +# registered via binfmt_misc, so the aarch64 host tools that U-Boot / +# TF-A produce (fixdep, mkimage, etc.) execute transparently on the +# x86_64 build host. +# +# This is the same trick the kernel reference uses, except there it +# works because qemux86-64 is same-arch. Here it works because of +# qemu-user emulation. +# +# The bare cross-compiler has no default sysroot; pass the SDK's +# target sysroot so #include and friends resolve. +HOSTCC="${CROSS_COMPILE}gcc --sysroot=${_TARGET_SYSROOT}" +HOSTCXX="${CROSS_COMPILE}g++ --sysroot=${_TARGET_SYSROOT}" +HOSTLD="${CROSS_COMPILE}ld" +HOSTAR="${CROSS_COMPILE}ar" + +echo "ARCH=${ARCH}" +echo "CROSS_COMPILE=${CROSS_COMPILE}" +echo "HOSTCC=${HOSTCC}" +echo "HOSTCXX=${HOSTCXX}" + +# --------------------------------------------------------------------------- +# Fetch sources. +# --------------------------------------------------------------------------- +# Pin to a specific SRCREV on the named branch. shallow-clone with +# --revision avoids fetching unrelated history while keeping the build +# reproducible — `--branch` alone would float on whatever HEAD is. +clone_pinned() { + local url="$1" branch="$2" srcrev="$3" dest="$4" + if [ ! -d "${dest}/.git" ]; then + echo "Cloning ${url} (${branch} @ ${srcrev}) -> ${dest}" + git -c advice.detachedHead=false clone --branch "${branch}" "${url}" "${dest}" + git -C "${dest}" -c advice.detachedHead=false checkout "${srcrev}" + else + local current + current="$(git -C "${dest}" rev-parse HEAD)" + if [ "${current}" != "${srcrev}" ]; then + echo "Updating ${dest} from ${current} to pinned ${srcrev}" + git -C "${dest}" fetch --tags origin "${branch}" + git -C "${dest}" -c advice.detachedHead=false checkout "${srcrev}" + else + echo "Reusing ${dest} (already at ${srcrev})" + fi + fi +} + +clone_pinned "https://github.com/nxp-imx/uboot-imx.git" "${UBOOT_BRANCH}" "${UBOOT_SRCREV}" "${UBOOT_DIR}" +clone_pinned "https://github.com/nxp-imx/imx-atf.git" "${ATF_BRANCH}" "${ATF_SRCREV}" "${ATF_DIR}" +clone_pinned "https://github.com/nxp-imx/imx-mkimage.git" "${MKIMAGE_BRANCH}" "${MKIMAGE_SRCREV}" "${MKIMAGE_DIR}" + +# firmware-imx is a self-extracting NXP installer. Cache the tarball on +# the host side; extract once into AVOCADO_BUILD_DIR. +if [ ! -f "${DOWNLOADS}/${FIRMWARE_IMX_TARBALL}" ]; then + echo "Downloading ${FIRMWARE_IMX_URL}..." + curl -fL --retry 3 -o "${DOWNLOADS}/${FIRMWARE_IMX_TARBALL}" "${FIRMWARE_IMX_URL}" +fi +if [ ! -d "${FIRMWARE_DIR}" ]; then + echo "Extracting ${FIRMWARE_IMX_TARBALL}..." + # The .bin is a shell-script-prefixed cpio archive. --auto-accept skips + # the EULA prompt; the user is responsible for accepting NXP terms. + (cd "${AVOCADO_BUILD_DIR}" && \ + sh "${DOWNLOADS}/${FIRMWARE_IMX_TARBALL}" --auto-accept --force >/dev/null) +fi + +# --------------------------------------------------------------------------- +# Layer Avocado patches onto the U-Boot defconfig and DTS. +# +# patches/avocado.cfg — adds CONFIG_IMX_HAB=y, FIT signature support, +# Avocado partition recognition. +# patches/env-mmc.cfg — wires ENV_IS_IN_MMC + redundant env layout. +# patches/avocado-imx8mp-evk.txt +# — A/B boot env script (matches Avocado's +# rootdisk layout). +# patches/avocado-fit-signature.dtsi +# — placeholder /signature node in the U-Boot +# control DTB. mkimage -K populates it +# post-build with the FIT signing pubkey. +# --------------------------------------------------------------------------- +echo "Applying Avocado config overlay to ${UBOOT_DEFCONFIG}..." +cat "${PATCHES_DIR}/avocado.cfg" >> "${UBOOT_DIR}/configs/${UBOOT_DEFCONFIG}" +cat "${PATCHES_DIR}/env-mmc.cfg" >> "${UBOOT_DIR}/configs/${UBOOT_DEFCONFIG}" + +# Inject the FIT signature placeholder into the board DTS via an #include. +# The dtsi declares /signature/key-rt-prod with required-conf=true but +# leaves rsa,n / rsa,e / rsa,modulus empty so mkimage -K fills them in +# from the dev key after the bootloader image is signed. +EVK_DTS="${UBOOT_DIR}/arch/arm/dts/imx8mp-evk-u-boot.dtsi" +if ! grep -q "avocado-fit-signature" "${EVK_DTS}"; then + cp "${PATCHES_DIR}/avocado-fit-signature.dtsi" \ + "${UBOOT_DIR}/arch/arm/dts/avocado-fit-signature.dtsi" + printf '\n#include "avocado-fit-signature.dtsi"\n' >> "${EVK_DTS}" +fi + +# Boot env. The MKENVIMAGE_EXTRA_ARGS=-r flag in Yocto produces a +# redundant env image; we use mkenvimage from the U-Boot tools tree. +cp "${PATCHES_DIR}/avocado-imx8mp-evk.txt" "${UBOOT_DIR}/avocado-boot-env.txt" + +# --------------------------------------------------------------------------- +# Build TF-A (BL31). +# --------------------------------------------------------------------------- +echo "Building TF-A BL31 for ${ATF_PLATFORM}..." +make -C "${ATF_DIR}" -j"$(nproc)" \ + PLAT="${ATF_PLATFORM}" \ + IMX_BOOT_UART_BASE=0x30890000 \ + bl31 + +# --------------------------------------------------------------------------- +# Build U-Boot. +# --------------------------------------------------------------------------- +UBOOT_MAKE_ARGS=( + HOSTCC="${HOSTCC}" + HOSTCXX="${HOSTCXX}" + HOSTLD="${HOSTLD}" + HOSTAR="${HOSTAR}" +) + +echo "Configuring U-Boot with ${UBOOT_DEFCONFIG}..." +make -C "${UBOOT_DIR}" -j"$(nproc)" "${UBOOT_MAKE_ARGS[@]}" "${UBOOT_DEFCONFIG}" + +echo "Building U-Boot..." +make -C "${UBOOT_DIR}" -j"$(nproc)" "${UBOOT_MAKE_ARGS[@]}" all + +# Build the redundant env image for the uboot-env partition. fwsetenv +# on-device reads/writes this format. +echo "Generating u-boot redundant env image..." +"${UBOOT_DIR}/tools/mkenvimage" -r -s 0x20000 -o "${AVOCADO_BUILD_DIR}/uboot.env" \ + "${UBOOT_DIR}/avocado-boot-env.txt" + +# --------------------------------------------------------------------------- +# Stage TF-A, U-Boot, and DDR firmware blobs into imx-mkimage's iMX8M dir. +# +# imx-mkimage's iMX8M/Makefile expects fixed filenames in iMX8M/. See +# https://github.com/nxp-imx/imx-mkimage/blob/master/iMX8M/soc.mak for +# the full list per SoC. +# --------------------------------------------------------------------------- +STAGE="${MKIMAGE_DIR}/iMX8M" +echo "Staging boot blobs into ${STAGE}..." +cp -f "${UBOOT_DIR}/tools/mkimage" "${STAGE}/mkimage_uboot" +cp -f "${UBOOT_DIR}/spl/u-boot-spl.bin" "${STAGE}/" +cp -f "${UBOOT_DIR}/u-boot-nodtb.bin" "${STAGE}/" +cp -f "${UBOOT_DIR}/arch/arm/dts/imx8mp-evk.dtb" "${STAGE}/" +cp -f "${UBOOT_DIR}/u-boot.bin" "${STAGE}/" +cp -f "${ATF_DIR}/build/${ATF_PLATFORM}/release/bl31.bin" "${STAGE}/" +cp -f "${FIRMWARE_DIR}/firmware/ddr/synopsys/lpddr4_pmu_train_1d_imem_202006.bin" "${STAGE}/" +cp -f "${FIRMWARE_DIR}/firmware/ddr/synopsys/lpddr4_pmu_train_1d_dmem_202006.bin" "${STAGE}/" +cp -f "${FIRMWARE_DIR}/firmware/ddr/synopsys/lpddr4_pmu_train_2d_imem_202006.bin" "${STAGE}/" +cp -f "${FIRMWARE_DIR}/firmware/ddr/synopsys/lpddr4_pmu_train_2d_dmem_202006.bin" "${STAGE}/" + +# --------------------------------------------------------------------------- +# Build flash.bin (== imx-boot). +# --------------------------------------------------------------------------- +echo "Building flash.bin via imx-mkimage SOC=${MKIMAGE_SOC} flash_evk..." +make -C "${MKIMAGE_DIR}" \ + SOC="${MKIMAGE_SOC}" \ + "${MKIMAGE_TARGET}" + +cp -f "${MKIMAGE_DIR}/iMX8M/flash.bin" "${AVOCADO_BUILD_DIR}/flash.bin" + +echo "" +echo "================================================================" +echo "imx-boot build complete:" +echo " ${AVOCADO_BUILD_DIR}/flash.bin" +echo " ${AVOCADO_BUILD_DIR}/uboot.env" +echo "" +echo "Bootloader is HAB-enabled. To produce a signed flash.bin you must" +echo "generate a CSF block with NXP's CST and append it; see" +echo "patches/avocado-fit-signature.dtsi and the README for the" +echo "post-build pubkey insertion / signing workflow." +echo "================================================================" diff --git a/uboot-custom-imx8mp-evk/uboot-install.sh b/uboot-custom-imx8mp-evk/uboot-install.sh new file mode 100755 index 0000000..9e3ee0a --- /dev/null +++ b/uboot-custom-imx8mp-evk/uboot-install.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# Install the cross-compiled imx-boot artifact into the runtime build +# directory. +# +# AVOCADO_RUNTIME_BUILD_DIR: set by avocado-cli (the +# `runtimes..packages.uboot.{compile,install}` hook), points to +# $AVOCADO_PREFIX/runtimes// +# +# Stone treats this dir as an input directory when bundling the OS, so +# dropping `imx-boot` here makes our flash.bin land in the os-bundle.aos +# instead of any upstream-provided imx-boot. +# +set -euo pipefail + +if [ -z "${AVOCADO_BUILD_DIR:-}" ]; then + echo "[ERROR] AVOCADO_BUILD_DIR is not set." >&2 + exit 1 +fi +if [ -z "${AVOCADO_RUNTIME_BUILD_DIR:-}" ]; then + echo "[ERROR] AVOCADO_RUNTIME_BUILD_DIR is not set." >&2 + exit 1 +fi + +FLASH_BIN="${AVOCADO_BUILD_DIR}/flash.bin" +UBOOT_ENV="${AVOCADO_BUILD_DIR}/uboot.env" + +if [ ! -f "${FLASH_BIN}" ]; then + echo "[ERROR] flash.bin not found at ${FLASH_BIN}." >&2 + echo " Run 'avocado sdk compile uboot' first." >&2 + exit 1 +fi + +mkdir -p "${AVOCADO_RUNTIME_BUILD_DIR}" + +echo "================================================================" +echo "Installing imx-boot into runtime build directory" +echo "================================================================" + +# stone-imx8mp-evk.json's rootdisk references this image as `imx-boot`. +echo "Copying flash.bin -> ${AVOCADO_RUNTIME_BUILD_DIR}/imx-boot" +cp -f "${FLASH_BIN}" "${AVOCADO_RUNTIME_BUILD_DIR}/imx-boot" + +# Same manifest references `uboot.env` for the redundant uboot-env +# partition. The on-device fw_env.config points at this on MMC. +if [ -f "${UBOOT_ENV}" ]; then + echo "Copying uboot.env -> ${AVOCADO_RUNTIME_BUILD_DIR}/uboot.env" + cp -f "${UBOOT_ENV}" "${AVOCADO_RUNTIME_BUILD_DIR}/uboot.env" +fi + +echo "" +echo "imx-boot installed successfully." +echo "================================================================"