From 40354019fd53e2b65dcc7f60af18b99b2f234d8b Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 1 Oct 2026 14:54:36 -0700 Subject: [PATCH] fdt: boot a FIT with no kernel load/entry and no device tree --- .github/workflows/test-configs.yml | 6 + Makefile | 2 - config/examples/versal_vmk180.config | 27 +- config/examples/versal_vmk180_highddr.config | 170 ++++++++ docs/Targets.md | 35 ++ include/fdt.h | 6 + include/target.h.in | 11 - options.mk | 34 +- src/boot_aarch64.c | 6 +- src/fdt.c | 46 ++- src/update_disk.c | 53 ++- src/update_ram.c | 52 ++- tools/config.mk | 2 + tools/unit-tests/Makefile | 27 +- tools/unit-tests/unit-fdt.c | 42 ++ tools/unit-tests/unit-fit-gzip.c | 131 ++++++ tools/unit-tests/unit-update-disk-fit.c | 91 ++++- tools/unit-tests/unit-update-ram-fit.c | 399 +++++++++++++++++++ 18 files changed, 1086 insertions(+), 54 deletions(-) create mode 100644 config/examples/versal_vmk180_highddr.config create mode 100644 tools/unit-tests/unit-update-ram-fit.c diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 825ec1f000..ba5deabbc4 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -1123,6 +1123,12 @@ jobs: arch: aarch64 config-file: ./config/examples/versal_vmk180.config + versal_vmk180_highddr_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + config-file: ./config/examples/versal_vmk180_highddr.config + versal_vmk180_sdcard_test: uses: ./.github/workflows/test-build-aarch64.yml with: diff --git a/Makefile b/Makefile index ce668f0e84..de8a81399e 100644 --- a/Makefile +++ b/Makefile @@ -855,8 +855,6 @@ include/target.h: $(TARGET_H_TEMPLATE) FORCE sed -e "s/@WOLFBOOT_DTS_UPDATE_ADDRESS@/$(WOLFBOOT_DTS_UPDATE_ADDRESS)/g" | \ sed -e "s/@WOLFBOOT_LOAD_ADDRESS@/$(WOLFBOOT_LOAD_ADDRESS)/g" | \ sed -e "s/@WOLFBOOT_LOAD_DTS_ADDRESS@/$(WOLFBOOT_LOAD_DTS_ADDRESS)/g" | \ - sed -e "s/@WOLFBOOT_LOAD_RAMDISK_ADDRESS@/$(WOLFBOOT_LOAD_RAMDISK_ADDRESS)/g" | \ - sed -e "s/@WOLFBOOT_LOAD_FPGA_ADDRESS@/$(WOLFBOOT_LOAD_FPGA_ADDRESS)/g" | \ sed -e "s|@WOLFBOOT_RAMBOOT_MAX_SIZE_DEFINE@|$(if $(strip $(WOLFBOOT_RAMBOOT_MAX_SIZE)),#define WOLFBOOT_RAMBOOT_MAX_SIZE $(WOLFBOOT_RAMBOOT_MAX_SIZE),/* WOLFBOOT_RAMBOOT_MAX_SIZE undefined */)|g" | \ sed -e "s/@WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS@/$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS)/g" \ > $@ diff --git a/config/examples/versal_vmk180.config b/config/examples/versal_vmk180.config index 70366c2c24..5949f01107 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -67,6 +67,16 @@ GZIP?=1 # "bootm" loads; without this wolfBoot ignores it. FIT_RAMDISK?=1 +# Load address for the FIT kernel sub-image, overriding the FIT's own +# `load`/`entry`. Needed when the ITS omits them (Yocto leaves them out +# unless UBOOT_LOADADDRESS/UBOOT_ENTRYPOINT are set), since a gzip kernel +# has nowhere to decompress to and an uncompressed one would be entered in +# place at whatever alignment the FIT happens to give it. 0 honors the FIT. +#WOLFBOOT_LOAD_KERNEL_ADDRESS?=0x200000 + +# Likewise for the ramdisk: 0 honors the FIT's own `load`. +#WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0x40000000 + # Toolchain USE_GCC=1 CROSS_COMPILE=aarch64-none-elf- @@ -107,14 +117,29 @@ WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x6000000 WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000 # High DDR aperture: for designs whose DDR lives above the default address map -# (e.g. DDR at 0x400_0000_0000). These must stay BELOW the defaults above and +# (e.g. DDR at 0x400_0000_0000). config/examples/versal_vmk180_highddr.config +# is this recipe already applied, with a worked DDR layout. +# These must stay BELOW the defaults above and # use '=' so they override them. Base and size are 512 GB aligned with no # UL/ULL suffix (the assembler consumes them); the FIT ITS must then use # #address-cells = <2> with two-cell load/entry values, and the bootgen BIF # and BL31 BL33 entry must match WOLFBOOT_ORIGIN. +# +# Note the digit count: 0x40000800000 is 0x400_0080_0000 (8 MB into the +# window), not 0x400_0800_0000 (128 MB in). +# +# Every address below is a distinct, non-overlapping region. The kernel and +# ramdisk destinations must clear the FIT staging buffer at +# WOLFBOOT_LOAD_ADDRESS, because the sub-images are read out of it while +# they are being written - a large image whose destination overlaps the +# staging buffer (or wolfBoot itself) hangs partway through the copy. +# Size WOLFBOOT_PARTITION_SIZE for the whole signed FIT: a kernel plus a +# large initramfs easily exceeds the 44 MB default set above. #WOLFBOOT_ORIGIN=0x40000800000 #WOLFBOOT_LOAD_ADDRESS=0x40010000000 #WOLFBOOT_LOAD_DTS_ADDRESS=0x40000001000 +#WOLFBOOT_LOAD_KERNEL_ADDRESS=0x40002000000 +#WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40030000000 #CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_BASE=0x40000000000 #CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_SIZE=0x10000000000 #CFLAGS_EXTRA+=-DVERSAL_NO_DDR_LOW diff --git a/config/examples/versal_vmk180_highddr.config b/config/examples/versal_vmk180_highddr.config new file mode 100644 index 0000000000..d62c7820a7 --- /dev/null +++ b/config/examples/versal_vmk180_highddr.config @@ -0,0 +1,170 @@ +# wolfBoot configuration for AMD Versal - DDR Boot with a high DDR aperture +# Versal with DDR mapped at 0x400_0000_0000 instead of the default map. +# +# This configuration replaces U-Boot in the Versal boot flow: +# PLM -> PSM -> BL31 (EL3) -> wolfBoot (EL2) -> Linux (EL1) +# +# Derived from versal_vmk180.config; see that file for the knobs this one +# does not change. Every address here lives inside the aperture declared by +# VERSAL_DDR_HIGH_BASE/SIZE, which is what generates the translation-table +# entries covering it. All clock, MIO and DDR initialization is done by +# PLM/PSM before wolfBoot starts. +# +# Three things outside this file have to agree with it: +# - the bootgen BIF: wolfboot.elf at WOLFBOOT_ORIGIN, and the raw DTB +# partition at WOLFBOOT_LOAD_DTS_ADDRESS +# - BL31's BL33 entry (PRELOADED_BL33_BASE) = WOLFBOOT_ORIGIN +# - the FIT ITS: #address-cells = <2>, because a single cell cannot hold +# an address above 4 GB + +ARCH?=AARCH64 +TARGET?=versal + +WOLFBOOT_VERSION?=1 + +# ECC-384 with SHA-384 (good balance of security and performance) +SIGN?=ECC384 +HASH?=SHA384 +IMAGE_HEADER_SIZE?=512 + +# RSA 4096-bit with SHA3-384 (alternative) +#SIGN?=RSA4096 +#HASH?=SHA3 +#IMAGE_HEADER_SIZE?=1024 + +# Debug options +DEBUG?=1 +DEBUG_SYMBOLS=1 +DEBUG_UART=1 + +# Boot Benchmarking (optional): +# Enables timing of boot operations (flash read, integrity, signature). +BOOT_BENCHMARK?=1 + +VTOR?=1 +CORTEX_M0?=0 +NO_ASM?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +V?=0 +SPMATH?=1 +RAM_CODE?=0 +DUALBANK_SWAP?=0 +PKA?=0 +WOLFTPM?=0 + +# Flash configuration +EXT_FLASH?=1 +NO_XIP=1 + +# Boot Exception Level +# Choose the exception level for booting applications (mutually exclusive): +# BOOT_EL1: Transition applications to EL1 (default - for Linux, most bare-metal) +# BOOT_EL2: Boot applications at EL2 (for hypervisors, RTOSes that expect EL2) +# Default is BOOT_EL1 if neither is specified. +# Uncomment one of the following to override: +#BOOT_EL1=1 +#BOOT_EL2=1 + +# ELF loading support +ELF?=1 + +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + +# FIT ramdisk (initramfs) extraction plus the /chosen/linux,initrd-{start,end} +# fixup. A stock PetaLinux image.ub carries a ramdisk sub-image that a U-Boot +# "bootm" loads; without this wolfBoot ignores it. +FIT_RAMDISK?=1 + +# WOLFBOOT_LOAD_KERNEL_ADDRESS / WOLFBOOT_LOAD_RAMDISK_ADDRESS override the +# FIT's own `load`/`entry`, which is what lets a FIT built without them boot +# at all (Yocto omits both unless UBOOT_LOADADDRESS/UBOOT_ENTRYPOINT are +# set). Both are set in the DDR layout below. + +# Toolchain +USE_GCC=1 +CROSS_COMPILE=aarch64-none-elf- + +# ============================================================================ +# Boot Memory Layout +# ============================================================================ +# wolfBoot runs from DDR inside the high aperture, at EL2 non-secure. +# WOLFBOOT_ORIGIN is set with the rest of the layout further down - the +# bootgen BIF partition header and BL31's PRELOADED_BL33_BASE must both +# name that same address, not the 0x8000000 the default-map configs use. + +# Optional debugging with OCRAM +# Versal Gen 1 (VMK180): OCM is 256KB at 0xFFFC0000 - 0xFFFFFFFF +# Versal Gen 2: OCM is 2MB at 0xFFE00000 - 0xFFFFFFFF +#WOLFBOOT_ORIGIN=0xFFFC0000 + +# Flash Sector Size (QSPI) +WOLFBOOT_SECTOR_SIZE=0x20000 + +# Application Partition Size (44MB) +WOLFBOOT_PARTITION_SIZE=0x2C00000 + +# Location in Flash for Primary Boot Partition +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x800000 + +# Load Partition to RAM Address (Linux kernel loads here) +WOLFBOOT_LOAD_ADDRESS?=0x10000000 + +# Location in Flash for Secondary Partition (update image) +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x3400000 + +# Location to store wolfBoot state/swap +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x6000000 + +# DTS (Device Tree) - matches addresses from BOOT.BIN analysis +WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000 + +# The aperture itself. Base and size are 512 GB aligned with no UL/ULL +# suffix, because the assembler consumes them when it builds the +# translation tables. VERSAL_NO_DDR_LOW drops the default low-DDR mapping +# for parts that have none. +CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_BASE=0x40000000000 +CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_SIZE=0x10000000000 +CFLAGS_EXTRA+=-DVERSAL_NO_DDR_LOW + +# DDR layout inside the aperture. These use '=' so they override the '?=' +# defaults above. Each region is distinct: the kernel and ramdisk +# destinations clear the FIT staging buffer at WOLFBOOT_LOAD_ADDRESS, +# because the sub-images are read out of it while they are being written, +# and a destination that overlaps the staging buffer or wolfBoot itself +# hangs partway through the copy. +# +# 0x400_0000_1000 DTB (WOLFBOOT_LOAD_DTS_ADDRESS) +# 0x400_0080_0000 wolfBoot (WOLFBOOT_ORIGIN) +# 0x400_0200_0000 kernel (WOLFBOOT_LOAD_KERNEL_ADDRESS) +# 0x400_1000_0000 FIT staging (WOLFBOOT_LOAD_ADDRESS) +# 0x400_3000_0000 ramdisk (WOLFBOOT_LOAD_RAMDISK_ADDRESS) +WOLFBOOT_ORIGIN=0x40000800000 +WOLFBOOT_LOAD_DTS_ADDRESS=0x40000001000 +WOLFBOOT_LOAD_KERNEL_ADDRESS=0x40002000000 +WOLFBOOT_LOAD_ADDRESS=0x40010000000 +WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40030000000 +WOLFBOOT_DTS_BOOT_ADDRESS?=0x7B0000 +WOLFBOOT_DTS_UPDATE_ADDRESS?=0x39B0000 + +# Speed up reads by using larger blocks +CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096 + +# UART Configuration - UART0 for APU console +CFLAGS_EXTRA+=-DDEBUG_UART_NUM=0 + +# QSPI Reference Clock: Ref (300MHz default for Versal) +#CFLAGS_EXTRA+=-DGQSPI_CLK_REF=300000000 + +# QSPI Bus Divisor: (2 << div) = BUS (0=div2, 1=div4, 2=div8) +# MT25QU01G max: 133MHz Quad Read (0x6C) with 8 dummy cycles +# div=0: 300MHz/2 = 150MHz (above spec but tested working) +# div=1: 300MHz/4 = 75MHz (within spec, default) +# div=2: 300MHz/8 = 37.5MHz (conservative) +#CFLAGS_EXTRA+=-DGQSPI_CLK_DIV=1 + +# QSPI flash options (uncomment to enable) +#CFLAGS_EXTRA+=-DDEBUG_QSPI # Enable QSPI debug logging +#CFLAGS_EXTRA+=-DGQSPI_MODE_IO # Use polling instead of DMA (slower) +#CFLAGS_EXTRA+=-DTEST_EXT_FLASH # Run flash erase/write/read test diff --git a/docs/Targets.md b/docs/Targets.md index 7a7710f767..e90ae200f3 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -5146,6 +5146,41 @@ A stock PetaLinux `image.ub` carries a `ramdisk` sub-image that `bootm` passes t `WOLFBOOT_LOAD_RAMDISK_ADDRESS` defaults to 0, which uses the ramdisk in place inside the staged FIT. Set it to a DDR address clear of the kernel, DTB and staging area if the payload needs a fixed location. +##### FIT images built without `load`/`entry` + +Some producers emit a FIT whose kernel node declares neither `load` nor `entry` - Yocto's `kernel-fitimage` class omits both unless `UBOOT_LOADADDRESS` and `UBOOT_ENTRYPOINT` are set - and leave it to U-Boot to place the image. wolfBoot needs a destination: a `compression = "gzip"` kernel has nowhere to decompress to, and an uncompressed one would be entered in place inside the staged FIT at whatever alignment the FIT happens to give it, which does not satisfy the arm64 boot protocol's 2 MB-aligned base. + +Either add the properties to the ITS, or set `WOLFBOOT_LOAD_KERNEL_ADDRESS` in the config to the address the kernel should be staged at. A nonzero value overrides the FIT's `load`/`entry` for the kernel node and is returned as the entry point; 0 (the default) honors whatever the FIT declares. `WOLFBOOT_LOAD_RAMDISK_ADDRESS` does the same for the `ramdisk` node. + +For a DDR aperture above 4 GB the ITS must use `#address-cells = <2>` and two-cell values, since a single cell cannot hold the address: + +```dts +/ { + #address-cells = <2>; + images { + kernel-1 { + data = /incbin/("Image.gz"); + compression = "gzip"; + load = <0x00000400 0x08000000>; + entry = <0x00000400 0x08000000>; + hash-1 { algo = "sha256"; }; + }; + }; +}; +``` + +##### FIT images with no device tree + +A kernel-only FIT - no `fdt` sub-image, and no `fdt` property on the configuration node - takes its device tree from the boot firmware instead, which is the arrangement U-Boot describes with `fdtcontroladdr`. Point `WOLFBOOT_LOAD_DTS_ADDRESS` at wherever the earlier stage left the blob; for a bootgen raw partition that is the `load` address in the BIF: + +``` +{ type=raw, load=0x40000001000, file=system-top.dtb } +``` + +wolfBoot then relocates and validates that DTB exactly as it does for a non-FIT payload, and hands it to the kernel in `x0`. + +Because such a DTB is outside the FIT, the wolfBoot signature does not cover it, so it is not treated as authenticated: `/chosen/bootargs` from the blob is **not** trusted and `LINUX_BOOTARGS` replaces it (see below). Bind a digest with `sign --dts` to have the DTB authenticated and its own bootargs honored, and set `WOLFBOOT_REQUIRE_SIGNED_DTB=1` to make a missing digest fatal rather than a warning. + **Kernel Command Line (bootargs)** If the FIT's DTB carries `/chosen/bootargs`, wolfBoot keeps them by default - an image boots with the arguments its kernel was validated with. Setting `LINUX_BOOTARGS` or `LINUX_BOOTARGS_ROOT` in the config replaces the DTB's value (the replaced value is logged); `CFLAGS_EXTRA+=-DLINUX_BOOTARGS_OVERRIDE=0` demotes an explicit `LINUX_BOOTARGS` to a fallback used only when the DTB has none. diff --git a/include/fdt.h b/include/fdt.h index d99546cb99..23b71e7955 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -404,6 +404,12 @@ void* fit_load_image_ex(fdt_ctx* ctx, const char* image, int* lenp, void* fit_load_image_to(fdt_ctx* ctx, const char* image, void* dst, uint32_t dst_max, int* lenp); +/* Load the FIT kernel sub-image, relocated to + * WOLFBOOT_LOAD_KERNEL_ADDRESS when that is nonzero (which overrides + * the FIT's `load`/`entry`, so a FIT built without them still boots). + * Returns the kernel entry address, or NULL on failure. */ +void* fit_load_kernel(fdt_ctx* ctx, const char* kernel_node, int* lenp); + #ifdef WOLFBOOT_FIT_RAMDISK /* Load a FIT ramdisk sub-image (optionally relocated to * WOLFBOOT_LOAD_RAMDISK_ADDRESS) and patch /chosen/linux,initrd-* in diff --git a/include/target.h.in b/include/target.h.in index 968d14167a..d8e293c44b 100644 --- a/include/target.h.in +++ b/include/target.h.in @@ -260,15 +260,4 @@ @WOLFBOOT_RAMBOOT_MAX_SIZE_DEFINE@ #define WOLFBOOT_LOAD_DTS_ADDRESS @WOLFBOOT_LOAD_DTS_ADDRESS@ -/* Load address for FIT ramdisk extraction (used when WOLFBOOT_FIT_RAMDISK - * is set; otherwise unused). 0 means "use the FIT image's `load` property - * verbatim — do not relocate". */ -#define WOLFBOOT_LOAD_RAMDISK_ADDRESS @WOLFBOOT_LOAD_RAMDISK_ADDRESS@ - -/* DDR staging address for the (decompressed) FPGA bitstream (used when - * WOLFBOOT_FPGA_BITSTREAM is set; otherwise unused). 0 means "use the FIT - * image's `load` property verbatim". */ -#define WOLFBOOT_LOAD_FPGA_ADDRESS @WOLFBOOT_LOAD_FPGA_ADDRESS@ - - #endif /* !H_TARGETS_TARGET_ */ diff --git a/options.mk b/options.mk index cd8719d03d..5e9d0cd88b 100644 --- a/options.mk +++ b/options.mk @@ -1095,6 +1095,34 @@ FIT_RAMDISK ?= 0 ifeq ($(FIT_RAMDISK),1) CFLAGS+=-DWOLFBOOT_FIT_RAMDISK endif +# Destination for the (decompressed) ramdisk. 0 means "honor the FIT's own +# `load` property instead". +# +# This and the two load addresses below are passed as CFLAGS rather than +# through include/target.h.in, because target.h is generated by three paths +# - this Makefile, CMakeLists.txt and stage1/Makefile - and only the first +# substitutes the optional load addresses. src/fdt.c defaults each of them +# to 0 when undefined, so an unsubstituted placeholder cannot reach the +# compiler. Each is defaulted here as well as in tools/config.mk so the +# guards hold where options.mk is included on its own, as the host tools +# include it. +WOLFBOOT_LOAD_RAMDISK_ADDRESS ?= 0 +ifneq ($(WOLFBOOT_LOAD_RAMDISK_ADDRESS),0) + CFLAGS+=-DWOLFBOOT_LOAD_RAMDISK_ADDRESS=$(WOLFBOOT_LOAD_RAMDISK_ADDRESS) +endif + +# Load address for the FIT kernel sub-image. 0 (the default) means "honor +# the FIT's own `load` property instead". Set this when the FIT was built +# without `load`/`entry` on the kernel node: a producer that omits them +# leaves U-Boot to pick the address, and a gzip kernel cannot be +# decompressed at all without a destination. Pick a region clear of the +# FIT staging area (WOLFBOOT_LOAD_ADDRESS) and of the DTB/ramdisk +# addresses. src/fdt.c reads this one unconditionally, so see the note +# above on why it is a CFLAG. +WOLFBOOT_LOAD_KERNEL_ADDRESS ?= 0 +ifneq ($(WOLFBOOT_LOAD_KERNEL_ADDRESS),0) + CFLAGS+=-DWOLFBOOT_LOAD_KERNEL_ADDRESS=$(WOLFBOOT_LOAD_KERNEL_ADDRESS) +endif # FPGA_BITSTREAM=1 enables loading an "fpga" sub-image from a FIT and # programming the PL before booting (Xilinx ZynqMP/Zynq-7000; Versal is @@ -1108,8 +1136,12 @@ endif # it is decompressed to this address before the PL is programmed. 0 (the # default) means "honor the FIT's own `load` property instead". Pick a # region clear of the FIT staging area (WOLFBOOT_LOAD_ADDRESS) and the -# kernel/DTB/ramdisk load addresses. +# kernel/DTB/ramdisk load addresses. A CFLAG for the same reason as the +# other two; src/fdt.c tests it with #if defined() && != 0. WOLFBOOT_LOAD_FPGA_ADDRESS ?= 0 +ifneq ($(WOLFBOOT_LOAD_FPGA_ADDRESS),0) + CFLAGS+=-DWOLFBOOT_LOAD_FPGA_ADDRESS=$(WOLFBOOT_LOAD_FPGA_ADDRESS) +endif # FPGA_NONFATAL=1 downgrades a failed PL load from fatal (panic) to a # logged warning that continues the boot. FPGA_NONFATAL ?= 0 diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index f60e9805fd..64e86f8b8c 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -155,10 +155,10 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset, const uint32_t* dts_offset) void RAMFUNCTION do_boot(const uint32_t *app_offset) #endif { - wolfBoot_printf("do_boot: entry=0x%08x, EL=%d\n", - (uint32_t)(uintptr_t)app_offset, current_el()); + wolfBoot_printf("do_boot: entry=%p, EL=%d\n", (void*)app_offset, + current_el()); #ifdef MMU - wolfBoot_printf("do_boot: dts=0x%08x\n", (uint32_t)(uintptr_t)dts_offset); + wolfBoot_printf("do_boot: dts=%p\n", (void*)dts_offset); /* WOLFBOOT_DTS_MAX_SIZE is this target's DTS staging-window size * (see include/fdt.h); it bounds the fixups below. */ hal_dts_fixup((uint32_t*)dts_offset, WOLFBOOT_DTS_MAX_SIZE); diff --git a/src/fdt.c b/src/fdt.c index 6e9c25b00b..1712966c03 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -804,7 +804,17 @@ void* fdt_getprop_address(const fdt_ctx* ctx, int nodeoffset, const char* name) return NULL; } if (len == 8) { - return (void*)(uintptr_t)fdt_rd64u(val); + uint64_t addr64 = fdt_rd64u(val); + /* A two-cell value wider than the target's pointer must be + * rejected, not truncated: silently dropping the high cell turns + * an out-of-range address into an unrelated low one that the + * caller would then copy to or branch to. */ +#if UINTPTR_MAX < UINT64_MAX + if (addr64 > (uint64_t)UINTPTR_MAX) { + return NULL; + } +#endif + return (void*)(uintptr_t)addr64; } if (len == 4) { return (void*)(uintptr_t)fdt_rd32(val); @@ -1649,6 +1659,35 @@ const char* fit_get_compatible(fdt_ctx* ctx, const char* image) return NULL; } +/* Defensive fallback: targets that never set a kernel relocation + * address leave WOLFBOOT_LOAD_KERNEL_ADDRESS at 0, in which case the + * FIT's own `load` property is honored. */ +#ifndef WOLFBOOT_LOAD_KERNEL_ADDRESS +#define WOLFBOOT_LOAD_KERNEL_ADDRESS 0 +#endif + +/* Upper bound on the (decompressed) kernel size. Defaults to the + * generic FIT decompression cap. */ +#ifndef WOLFBOOT_FIT_MAX_KERNEL +#define WOLFBOOT_FIT_MAX_KERNEL WOLFBOOT_FIT_MAX_DECOMP +#endif + +/* Load the FIT kernel sub-image. If WOLFBOOT_LOAD_KERNEL_ADDRESS is + * nonzero the kernel is staged there and the FIT's `load`/`entry` are + * bypassed, which is what lets a FIT whose kernel node declares neither + * one boot at all: without a destination a gzip kernel cannot be + * decompressed, and an uncompressed one would be entered in place at + * whatever alignment the FIT happens to give it. */ +void* fit_load_kernel(fdt_ctx* ctx, const char* kernel_node, int* lenp) +{ + if (WOLFBOOT_LOAD_KERNEL_ADDRESS != 0) { + return fit_load_image_to(ctx, kernel_node, + (void*)WOLFBOOT_LOAD_KERNEL_ADDRESS, + (uint32_t)WOLFBOOT_FIT_MAX_KERNEL, lenp); + } + return fit_load_image(ctx, kernel_node, lenp); +} + #ifdef WOLFBOOT_FIT_RAMDISK /* Defensive fallback: targets without a fixed relocation address * leave WOLFBOOT_LOAD_RAMDISK_ADDRESS at 0, in which case the @@ -1916,7 +1955,10 @@ static void* fit_load_image_inner(fdt_ctx* ctx, const char* image, int* lenp, wolfBoot_printf("FIT: subimage '%s' declares " "compression=\"%s\" but has no distinct load " "destination (load=%p, data=%p); refusing to pass " - "compressed bytes through as raw\n", + "compressed bytes through as raw. Add `load` to the " + "FIT node; the kernel, ramdisk and fpga loaders can " + "take WOLFBOOT_LOAD_{KERNEL,RAMDISK,FPGA}_ADDRESS " + "instead\n", image, compstr, load, data); return NULL; } diff --git a/src/update_disk.c b/src/update_disk.c index 4c1264165b..fae4f3a525 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -611,6 +611,12 @@ void RAMFUNCTION wolfBoot_start(void) uint32_t dts_size = 0; /* Validated view of the FIT staged at load_address. */ fdt_ctx fit_ctx; + #ifdef WOLFBOOT_FIT_RAMDISK + /* FIT ramdisk node name, non-NULL only once the FIT has parsed. The + * initrd fixup is deferred until dts_addr is final, because the DTB + * may come from the boot firmware instead of from the FIT. */ + const char *fit_ramdisk = NULL; + #endif #endif #endif #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) @@ -687,7 +693,7 @@ void RAMFUNCTION wolfBoot_start(void) load_address = (uint32_t *)((((uintptr_t)_end_wb) + 0xf) & ~0xf); #endif - wolfBoot_printf("Load address 0x%x\r\n", load_address); + wolfBoot_printf("Load address %p\r\n", (void*)load_address); /* Upper bound on anything the media may claim about the image size. * The payload is copied into the load region before its signature is @@ -1068,7 +1074,7 @@ void RAMFUNCTION wolfBoot_start(void) (void)fpga; #endif if (kernel != NULL) { - void *new_load = fit_load_image(fit, kernel, NULL); + void *new_load = fit_load_kernel(fit, kernel, NULL); if (new_load == NULL) { wolfBoot_printf("FIT: failed to load kernel '%s'\r\n", kernel); @@ -1119,20 +1125,17 @@ void RAMFUNCTION wolfBoot_start(void) wolfBoot_panic(); } } - } -#ifdef WOLFBOOT_FIT_RAMDISK - if (ramdisk != NULL) { - fdt_ctx dts_ctx; - fdt_ctx* dts_for_initrd = NULL; - - /* The relocated DTB sits in the staging window, so that is - * the capacity the initrd fixup may grow into. */ - if (dts_addr != NULL && - fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { - dts_for_initrd = &dts_ctx; + else { + /* Say so rather than falling through to the boot + * firmware's DTB silently: that would hand the kernel a + * different device tree than the FIT names. */ + wolfBoot_printf("FIT: '%s' is not a usable DTB (%d); " + "falling back to the boot firmware's\r\n", flat_dt, + parsed); } - (void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd); } +#ifdef WOLFBOOT_FIT_RAMDISK + fit_ramdisk = ramdisk; #else (void)ramdisk; #endif @@ -1148,7 +1151,7 @@ void RAMFUNCTION wolfBoot_start(void) } #endif - wolfBoot_printf("Booting at %08lx\r\n", load_address); + wolfBoot_printf("Booting at %p\r\n", (void*)load_address); #ifdef WOLFBOOT_ENABLE_WOLFHSM_CLIENT (void)hal_hsm_disconnect(); @@ -1181,6 +1184,26 @@ void RAMFUNCTION wolfBoot_start(void) if (dts_addr == NULL) { dts_addr = (uint8_t*)hal_get_boot_dts(); } +#endif +#if defined(WOLFBOOT_FDT) && defined(WOLFBOOT_FIT_RAMDISK) + /* Run after the DTB source is settled, so the initrd fixup lands on + * the tree the kernel will actually be handed - including a DTB that + * came from the boot firmware rather than from the FIT. Outside the + * #ifdef MMU above on purpose: WOLFBOOT_FDT without MMU is a real + * configuration (the MPFS E51 M-mode DDR boot) and it still needs the + * fixup. */ + if (fit_ramdisk != NULL) { + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The relocated DTB sits in the staging window, so that is + * the capacity the initrd fixup may grow into. */ + if (dts_addr != NULL && + fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { + dts_for_initrd = &dts_ctx; + } + (void)fit_load_ramdisk(&fit_ctx, fit_ramdisk, dts_for_initrd); + } #endif /* Deferred from just after verification (see NOTE above): close the boot * disk now that all env / DTB reads and writes are done, before handoff. */ diff --git a/src/update_ram.c b/src/update_ram.c index cd7cf8bcdb..4f7937147a 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -302,6 +302,12 @@ void RAMFUNCTION wolfBoot_start(void) uint32_t dts_size = 0; /* Validated view of the FIT staged at load_address. */ fdt_ctx fit_ctx; +#ifdef WOLFBOOT_FIT_RAMDISK + /* FIT ramdisk node name, non-NULL only once the FIT has parsed. The + * initrd fixup is deferred until dts_addr is final, because the DTB + * may come from outside the FIT. */ + const char *fit_ramdisk = NULL; +#endif /* HDR_DEVICE_TREE_DIGEST snapshot, taken before the raw DTB is loaded. */ uint8_t dts_digest[WOLFBOOT_SHA_DIGEST_SIZE]; uint8_t *dts_tlv = NULL; @@ -642,7 +648,7 @@ void RAMFUNCTION wolfBoot_start(void) (void)fpga; #endif if (kernel != NULL) { - void *new_load = fit_load_image(fit, kernel, NULL); + void *new_load = fit_load_kernel(fit, kernel, NULL); if (new_load == NULL) { wolfBoot_printf("FIT: failed to load kernel '%s'\n", kernel); wolfBoot_panic(); @@ -687,25 +693,26 @@ void RAMFUNCTION wolfBoot_start(void) fdt_set_dtb_authenticated(1); memcpy(dts_addr, dts_ptr, dts_size); } - } -#ifdef WOLFBOOT_FIT_RAMDISK - if (ramdisk != NULL) { - fdt_ctx dts_ctx; - fdt_ctx* dts_for_initrd = NULL; - - /* The relocated DTB sits in the staging window, so that is - * the capacity the initrd fixup may grow into. */ - if (dts_addr != NULL && - fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { - dts_for_initrd = &dts_ctx; + else { + /* Say so rather than falling through to the boot + * firmware's DTB silently: that would hand the kernel a + * different device tree than the FIT names. */ + wolfBoot_printf("FIT: '%s' is not a usable DTB (%d); " + "falling back to the boot firmware's\n", flat_dt, + parsed); } - (void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd); } +#ifdef WOLFBOOT_FIT_RAMDISK + fit_ramdisk = ramdisk; #else (void)ramdisk; #endif } - else { + /* Also reached when the payload parsed as a FIT but carried no usable + * `fdt` sub-image: a kernel-only FIT takes its device tree from the + * boot firmware (e.g. a bootgen raw partition at + * WOLFBOOT_LOAD_DTS_ADDRESS) exactly as a non-FIT payload does. */ + if (dts_addr == NULL) { /* Prefer the HAL's memory-mapped DTB (unchanged for XIP targets); fall * back to external flash at WOLFBOOT_DTS_BOOT_ADDRESS when the HAL has * no usable address (NULL, or a flash offset on NO_XIP targets). */ @@ -786,6 +793,23 @@ void RAMFUNCTION wolfBoot_start(void) } } } +#ifdef WOLFBOOT_FIT_RAMDISK + /* Run after the DTB source is settled, so the initrd fixup lands on + * the tree the kernel will actually be handed - including a DTB that + * came from the boot firmware rather than from the FIT. */ + if (fit_ramdisk != NULL) { + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The relocated DTB sits in the staging window, so that is + * the capacity the initrd fixup may grow into. */ + if (dts_addr != NULL && + fdt_open(&dts_ctx, dts_addr, WOLFBOOT_DTS_MAX_SIZE) == 0) { + dts_for_initrd = &dts_ctx; + } + (void)fit_load_ramdisk(&fit_ctx, fit_ramdisk, dts_for_initrd); + } +#endif #endif /* MMU */ #ifdef WOLFBOOT_UBOOT_LEGACY diff --git a/tools/config.mk b/tools/config.mk index 1fad0cbab0..55c38f4534 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -104,6 +104,7 @@ endif # Defaulted globally (outside the CI ifeq block above) so FIT_RAMDISK=1 # can be toggled on any target without forcing an explicit address. WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0 +WOLFBOOT_LOAD_KERNEL_ADDRESS?=0 CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO_DRIVERS \ MCUXPRESSO_CMSIS FREEDOM_E_SDK STM32CUBE CYPRESS_PDL CYPRESS_CORE_LIB CYPRESS_TARGET_LIB DEBUG VTOR \ @@ -125,6 +126,7 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO WOLFBOOT_PARTITION_BOOT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS \ WOLFBOOT_PARTITION_SWAP_ADDRESS WOLFBOOT_LOAD_ADDRESS \ WOLFBOOT_LOAD_DTS_ADDRESS WOLFBOOT_LOAD_RAMDISK_ADDRESS \ + WOLFBOOT_LOAD_KERNEL_ADDRESS \ WOLFBOOT_DTS_BOOT_ADDRESS WOLFBOOT_DTS_UPDATE_ADDRESS \ WOLFBOOT_SMALL_STACK DELTA_UPDATES DELTA_BLOCK_SIZE WOLFBOOT_IMG_HASH_ONESHOT \ WOLFBOOT_HUGE_STACK FORCE_32BIT\ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index d65816f77a..95f7b7afec 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -67,8 +67,8 @@ TESTS:=unit-parser unit-parser-large-header unit-fdt unit-extflash unit-string \ unit-update-flash-hook \ unit-update-flash-self-update \ unit-nsc-update \ - unit-update-flash-enc unit-update-flash-enc-full unit-update-ram unit-update-ram-uboot unit-update-ram-enc unit-update-ram-enc-nopart unit-update-ram-nofixed unit-update-ram-nofixed-noramboot unit-update-ram-noramboot unit-update-ram-custom-trailer unit-custom-trailer-nopart unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \ - unit-update-disk unit-update-disk-confirm unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ + unit-update-flash-enc unit-update-flash-enc-full unit-update-ram unit-update-ram-fit unit-update-ram-uboot unit-update-ram-enc unit-update-ram-enc-nopart unit-update-ram-nofixed unit-update-ram-nofixed-noramboot unit-update-ram-noramboot unit-update-ram-custom-trailer unit-custom-trailer-nopart unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \ + unit-update-disk unit-update-disk-confirm unit-update-disk-fsp unit-update-disk-oob unit-update-disk-fit unit-update-disk-fit-ramdisk unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-image-dts \ unit-image-dts-sha384 unit-image-dts-sha3-384 unit-store-sbrk \ unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \ @@ -329,6 +329,13 @@ unit-update-flash-self-update:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNI -DRAM_CODE -DARCH_SIM -DUNIT_TEST_SELF_UPDATE_ONLY \ -DARCH_FLASH_OFFSET=MOCK_ADDRESS_BOOT -DWOLFBOOT_VERSION=7 \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-ram-fit:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ + -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ + -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP \ + -DMMU -DWOLFBOOT_FDT -DWOLFBOOT_FIT_RAMDISK \ + -DWOLFBOOT_DTS_BOOT_ADDRESS=0xCF000000 \ + -DWOLFBOOT_DTS_UPDATE_ADDRESS=0xCF100000 \ + -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE unit-update-ram:CFLAGS+=-DMOCK_PARTITIONS -DWOLFBOOT_NO_SIGN -DUNIT_TEST_AUTH \ -DWOLFBOOT_HASH_SHA256 -DPRINTF_ENABLED -DEXT_FLASH -DPART_UPDATE_EXT \ -DPART_SWAP_EXT -DPART_BOOT_EXT -DWOLFBOOT_DUALBOOT -DNO_XIP \ @@ -425,9 +432,11 @@ unit-update-disk-oob:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED \ # FIT (flattened uImage tree) exits of the encrypted disk loader. The panic hook # is what lets the test observe the key material at the instant wolfBoot_panic() # is entered, since on target that call never returns. -unit-update-disk-fit:CFLAGS+=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_FDT \ +UNIT_DISK_FIT_CFLAGS:=-DMOCK_PARTITIONS -DPRINTF_ENABLED -DWOLFBOOT_FDT \ -DWOLFBOOT_HOOK_PANIC -DWOLFBOOT_RAMBOOT_MAX_SIZE=0x40 \ -DWOLFBOOT_ORIGIN=MOCK_ADDRESS_BOOT -DBOOTLOADER_PARTITION_SIZE=WOLFBOOT_PARTITION_SIZE +unit-update-disk-fit:CFLAGS+=$(UNIT_DISK_FIT_CFLAGS) +unit-update-disk-fit-ramdisk:CFLAGS+=$(UNIT_DISK_FIT_CFLAGS) # Regression coverage for wolfBoot_check_flash_image_elf() (scattered-ELF # integrity check). WOLFBOOT_NO_SIGN keeps this to the hashing path only (no # signature verification is exercised by that function). @@ -937,6 +946,7 @@ unit-gzip: ../../include/target.h unit-gzip.c # failure paths) and once without (compile-time fail-closed path). unit-fit-gzip: ../../include/target.h unit-fit-gzip.c gcc -o $@ unit-fit-gzip.c $(CFLAGS) -DWOLFBOOT_FDT -DWOLFBOOT_GZIP \ + -DWOLFBOOT_FIT_RAMDISK \ -DWOLFBOOT_NO_PRINTF \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections @@ -1031,6 +1041,12 @@ unit-update-flash-enc-full: ../../include/target.h unit-update-flash.c unit-update-ram: ../../include/target.h unit-update-ram.c gcc -o $@ unit-update-ram.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) +# Loader-level coverage for the kernel-only-FIT device-tree fallback and the +# deferred initrd fixup. MMU + WOLFBOOT_FDT put update_ram.c on the FIT path; +# the FDT/FIT API is stubbed inside the test. +unit-update-ram-fit: ../../include/target.h unit-update-ram-fit.c + gcc -o $@ unit-update-ram-fit.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) + unit-update-ram-uboot: ../../include/target.h unit-update-ram-uboot.c gcc -o $@ unit-update-ram-uboot.c ../../src/image.c ../../src/gpt.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) @@ -1098,6 +1114,11 @@ unit-update-disk-fs-enc: ../../include/target.h unit-update-disk-fs.c unit-update-disk-fit: ../../include/target.h unit-update-disk-fit.c gcc -o $@ unit-update-disk-fit.c $(CFLAGS) $(LDFLAGS) +# Same source with FIT_RAMDISK on, so the deferred initrd fixup has +# loader-level coverage: it must run after the device tree is selected. +unit-update-disk-fit-ramdisk: ../../include/target.h unit-update-disk-fit.c + gcc -o $@ unit-update-disk-fit.c $(CFLAGS) -DWOLFBOOT_FIT_RAMDISK $(LDFLAGS) + unit-pkcs11_store: ../../include/target.h unit-pkcs11_store.c gcc -o $@ $(WOLFCRYPT_SRC) unit-pkcs11_store.c $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) diff --git a/tools/unit-tests/unit-fdt.c b/tools/unit-tests/unit-fdt.c index 3784a25eff..dc177dcda3 100644 --- a/tools/unit-tests/unit-fdt.c +++ b/tools/unit-tests/unit-fdt.c @@ -1267,6 +1267,47 @@ START_TEST(test_fdt_get_reg_two_address_two_size_cells) } END_TEST +START_TEST(test_fdt_getprop_address_two_cells) +{ + /* A two-cell value is the only way a FIT `load`/`entry` can express + * an address above 4 GB, so the 8-byte form has to decode whole + * instead of collapsing to the low cell. */ + static uint8_t buf[sizeof(chassis_dtb) + 256] __attribute__((aligned(8))); + fdt_ctx ctx; + uint8_t val[8]; + int off; + + memcpy(buf, chassis_dtb, sizeof(chassis_dtb)); + ck_assert_int_eq(fdt_open(&ctx, buf, sizeof(buf)), 0); + + off = fdt_get_alias(&ctx, "Chassis_Manager"); + ck_assert_int_ge(off, 0); + + /* 0x00000400_08000000, big-endian, as mkimage emits it from + * load = <0x00000400 0x08000000>; under #address-cells = <2> */ + val[0] = 0x00; val[1] = 0x00; val[2] = 0x04; val[3] = 0x00; + val[4] = 0x08; val[5] = 0x00; val[6] = 0x00; val[7] = 0x00; + ck_assert_int_eq(fdt_setprop(&ctx, off, "load", val, (int)sizeof(val)), 0); + +#if UINTPTR_MAX > 0xFFFFFFFFUL + ck_assert_ptr_eq(fdt_getprop_address(&ctx, off, "load"), + (void*)(uintptr_t)0x40008000000ULL); +#else + /* A 32-bit target cannot represent it, and truncating to the low cell + * would hand the caller an unrelated address it would copy to or + * branch to. It has to come back NULL. */ + ck_assert_ptr_null(fdt_getprop_address(&ctx, off, "load")); +#endif + + /* A single cell still decodes, so existing one-cell ITS files are + * unaffected. */ + val[0] = 0x00; val[1] = 0x20; val[2] = 0x00; val[3] = 0x00; + ck_assert_int_eq(fdt_setprop(&ctx, off, "entry", val, 4), 0); + ck_assert_ptr_eq(fdt_getprop_address(&ctx, off, "entry"), + (void*)(uintptr_t)0x200000UL); +} +END_TEST + START_TEST(test_fdt_get_reg_one_cell_and_index) { static uint8_t buf[sizeof(chassis_dtb) + 16] __attribute__((aligned(8))); @@ -1477,6 +1518,7 @@ static Suite *fdt_suite(void) tcase_add_test(tc, test_fdt_get_alias_resolves_named_node); tcase_add_test(tc, test_fdt_get_alias_rejects_bad_input); tcase_add_test(tc, test_fdt_get_reg_two_address_two_size_cells); + tcase_add_test(tc, test_fdt_getprop_address_two_cells); tcase_add_test(tc, test_fdt_get_reg_one_cell_and_index); tcase_add_test(tc, test_fdt_get_reg_rejects_partial_entry); tcase_add_test(tc, test_fdt_get_reg_uses_spec_default_cells); diff --git a/tools/unit-tests/unit-fit-gzip.c b/tools/unit-tests/unit-fit-gzip.c index 2b6ac863ae..63cf806a2c 100644 --- a/tools/unit-tests/unit-fit-gzip.c +++ b/tools/unit-tests/unit-fit-gzip.c @@ -44,6 +44,15 @@ void wolfBoot_printf(const char *fmt, ...) (void)fmt; } +/* WOLFBOOT_LOAD_KERNEL_ADDRESS is a build-time constant in production. + * Here it resolves to a variable so one test binary can exercise both + * branches of fit_load_kernel() and point the override at a real buffer + * in this process rather than a device address. */ +static uintptr_t kernel_override_addr = 0; +#define WOLFBOOT_LOAD_KERNEL_ADDRESS kernel_override_addr +static uintptr_t ramdisk_override_addr = 0; +#define WOLFBOOT_LOAD_RAMDISK_ADDRESS ramdisk_override_addr + /* Pull in the production code under test. fdt.c's body is gated on * WOLFBOOT_FDT; the Makefile defines that for both build variants. * gzip.c is only needed for the WOLFBOOT_GZIP build. */ @@ -65,6 +74,18 @@ void wolfBoot_printf(const char *fmt, ...) static const char fit_plain_payload[] = "hello fit test payload\n"; #define FIT_PLAIN_LEN 23 +/* Smallest valid flat device tree: an empty root with no properties. + * Stands in for a DTB that came from the boot firmware (e.g. a bootgen + * raw partition) rather than from the FIT. */ +static const uint8_t empty_dtb[] = { + 0xd0,0x0d,0xfe,0xed, 0x00,0x00,0x00,0x48, 0x00,0x00,0x00,0x38, + 0x00,0x00,0x00,0x48, 0x00,0x00,0x00,0x28, 0x00,0x00,0x00,0x11, + 0x00,0x00,0x00,0x10, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x10, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x01, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x02, 0x00,0x00,0x00,0x09 +}; + /* gzip-compressed kernel, load=0xC0001000 */ static const uint8_t fit_with_gzip_kernel[] = { 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x38, @@ -263,6 +284,112 @@ START_TEST(test_fit_ex_gzip_no_load_returns_null) } END_TEST +START_TEST(test_fit_kernel_no_override_gzip_no_load_fails) +{ + /* The shape a producer emits when the kernel node carries neither + * `load` nor `entry`: with no override configured there is nowhere + * to decompress to, so the load must fail rather than hand back + * compressed bytes. */ + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_gzip_no_load)] __attribute__((aligned(4))); + memcpy(fit_scratch, fit_gzip_no_load, sizeof(fit_scratch)); + + kernel_override_addr = 0; + ret = fit_load_kernel(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), + "kernel-1", &len); + ck_assert_ptr_null(ret); +} +END_TEST + +START_TEST(test_fit_kernel_override_loads_gzip_no_load) +{ + /* Same FIT, but with WOLFBOOT_LOAD_KERNEL_ADDRESS set: the override + * supplies the destination the FIT does not, so the kernel inflates + * and the override address comes back as the entry point. */ + static uint8_t dst[64 * 1024] __attribute__((aligned(16))); + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_gzip_no_load)] __attribute__((aligned(4))); + memcpy(fit_scratch, fit_gzip_no_load, sizeof(fit_scratch)); + + kernel_override_addr = (uintptr_t)dst; + ret = fit_load_kernel(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), + "kernel-1", &len); + kernel_override_addr = 0; + + ck_assert_ptr_eq(ret, dst); + ck_assert_int_eq(len, FIT_PLAIN_LEN); + ck_assert_int_eq(memcmp(dst, fit_plain_payload, FIT_PLAIN_LEN), 0); +} +END_TEST + +START_TEST(test_fit_initrd_fixup_on_dtb_outside_the_fit) +{ + /* A kernel-only FIT carries no `fdt` sub-image, so the device tree + * comes from the boot firmware instead. The ramdisk still has to be + * staged and /chosen/linux,initrd-{start,end} still has to be written + * into THAT tree - which is why the loaders defer the fixup until the + * device-tree source is settled. The sub-image is named "kernel-1" + * only because that is what the fixtures provide; fit_load_ramdisk() + * takes the node name from the caller. */ + static uint8_t rd_dst[64 * 1024] __attribute__((aligned(16))); + static uint8_t dtb[4096] __attribute__((aligned(8))); + static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4))); + fdt_ctx dts; + const uint8_t *start, *end; + int off; + + memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch)); + memset(dtb, 0, sizeof(dtb)); + memcpy(dtb, empty_dtb, sizeof(empty_dtb)); + + /* Opened at the full staging capacity, as the loaders do, so the + * fixup has room to add /chosen. */ + ck_assert_int_eq(fdt_open(&dts, dtb, (uint32_t)sizeof(dtb)), 0); + + ramdisk_override_addr = (uintptr_t)rd_dst; + ck_assert_int_eq(fit_load_ramdisk( + fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), + "kernel-1", &dts), 0); + ramdisk_override_addr = 0; + + ck_assert_int_eq(memcmp(rd_dst, fit_plain_payload, FIT_PLAIN_LEN), 0); + + off = fdt_path_offset(&dts, "/chosen"); + ck_assert_int_ge(off, 0); + start = (const uint8_t*)fdt_getprop(&dts, off, "linux,initrd-start", NULL); + end = (const uint8_t*)fdt_getprop(&dts, off, "linux,initrd-end", NULL); + ck_assert_ptr_nonnull(start); + ck_assert_ptr_nonnull(end); + ck_assert_uint_eq(fdt_rd64u(start), (uint64_t)(uintptr_t)rd_dst); + ck_assert_uint_eq(fdt_rd64u(end), + (uint64_t)(uintptr_t)rd_dst + FIT_PLAIN_LEN); +} +END_TEST + +START_TEST(test_fit_kernel_override_beats_fit_load) +{ + /* The override wins over a FIT that does declare `load` (0xC0001000 + * here, an address this process cannot touch), so a build can place + * the kernel without editing the FIT. */ + static uint8_t dst[64 * 1024] __attribute__((aligned(16))); + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)] __attribute__((aligned(4))); + memcpy(fit_scratch, fit_with_gzip_kernel, sizeof(fit_scratch)); + + kernel_override_addr = (uintptr_t)dst; + ret = fit_load_kernel(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)), + "kernel-1", &len); + kernel_override_addr = 0; + + ck_assert_ptr_eq(ret, dst); + ck_assert_int_eq(len, FIT_PLAIN_LEN); + ck_assert_int_eq(memcmp(dst, fit_plain_payload, FIT_PLAIN_LEN), 0); +} +END_TEST + #else /* !WOLFBOOT_GZIP */ START_TEST(test_fit_to_gzip_disabled_returns_null) @@ -368,6 +495,10 @@ static Suite *fit_gzip_suite(void) tcase_add_test(tc, test_fit_to_gzip_corrupt_returns_null); tcase_add_test(tc, test_fit_to_none_compression_copies_plain); tcase_add_test(tc, test_fit_ex_gzip_no_load_returns_null); + tcase_add_test(tc, test_fit_kernel_no_override_gzip_no_load_fails); + tcase_add_test(tc, test_fit_kernel_override_loads_gzip_no_load); + tcase_add_test(tc, test_fit_kernel_override_beats_fit_load); + tcase_add_test(tc, test_fit_initrd_fixup_on_dtb_outside_the_fit); #else tcase_add_test(tc, test_fit_to_gzip_disabled_returns_null); #endif diff --git a/tools/unit-tests/unit-update-disk-fit.c b/tools/unit-tests/unit-update-disk-fit.c index 5bd06a4336..0388f1a37f 100644 --- a/tools/unit-tests/unit-update-disk-fit.c +++ b/tools/unit-tests/unit-update-disk-fit.c @@ -78,6 +78,18 @@ static int mock_do_boot_called; static int mock_fit_memcpy_ret; static int mock_fit_memcpy_called; static int mock_panic_hook_called; +/* Which sub-images fit_find_images() reports. Defaults keep the existing + * tests on the "FIT supplies its own fdt, no ramdisk" path. */ +static const char *mock_flat_dt = "fdt"; +static const char *mock_ramdisk; +/* Ordering witnesses for the deferred initrd fixup. hal_flash_protect() + * runs between the FIT block and the deferred block, so a fixup that + * drifted back inside the FIT block would be seen here with + * mock_flash_protect_calls still at 0. */ +static int mock_flash_protect_calls; +static int mock_ramdisk_calls; +static int mock_ramdisk_saw_flash_protect; +static fdt_ctx *mock_ramdisk_dts; /* Snapshot of the key material taken from inside wolfBoot_panic() */ static uint8_t panic_key_snapshot[ENCRYPT_KEY_SIZE]; static uint8_t panic_nonce_snapshot[ENCRYPT_NONCE_SIZE]; @@ -121,6 +133,12 @@ static void reset_mocks(void) mock_fit_memcpy_ret = 0; mock_fit_memcpy_called = 0; mock_panic_hook_called = 0; + mock_flat_dt = "fdt"; + mock_ramdisk = NULL; + mock_flash_protect_calls = 0; + mock_ramdisk_calls = 0; + mock_ramdisk_saw_flash_protect = 0; + mock_ramdisk_dts = NULL; memset(panic_key_snapshot, 0xFF, sizeof(panic_key_snapshot)); memset(panic_nonce_snapshot, 0xFF, sizeof(panic_nonce_snapshot)); wolfBoot_panicked = 0; @@ -267,9 +285,9 @@ const char* fit_find_images(fdt_ctx* ctx, const char** pkernel, if (pkernel != NULL) *pkernel = NULL; if (pflat_dt != NULL) - *pflat_dt = "fdt"; + *pflat_dt = mock_flat_dt; if (pramdisk != NULL) - *pramdisk = NULL; + *pramdisk = mock_ramdisk; if (pfpga != NULL) *pfpga = NULL; return "conf"; @@ -284,6 +302,13 @@ void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp) return fit_dts_image; } +/* This suite's fit_find_images() reports no kernel node, so the call is + * never reached; the stub only satisfies the link. */ +void* fit_load_kernel(fdt_ctx* ctx, const char* kernel_node, int* lenp) +{ + return fit_load_image(ctx, kernel_node, lenp); +} + int wolfBoot_fit_memcpy(void *dst, const void *src, uint32_t len) { mock_fit_memcpy_called++; @@ -308,8 +333,23 @@ int hal_flash_protect(haladdr_t address, int len) { (void)address; (void)len; + mock_flash_protect_calls++; + return 0; +} + +#ifdef WOLFBOOT_FIT_RAMDISK +/* Records when it ran relative to hal_flash_protect(), and which device + * tree it was handed. */ +int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts) +{ + (void)ctx; + (void)ramdisk_node; + mock_ramdisk_calls++; + mock_ramdisk_saw_flash_protect = mock_flash_protect_calls; + mock_ramdisk_dts = dts; return 0; } +#endif #include "update_disk.c" @@ -393,6 +433,49 @@ START_TEST(test_update_disk_fit_dts_below_min_rejected) } END_TEST +#ifdef WOLFBOOT_FIT_RAMDISK +/* A kernel-only FIT - a ramdisk sub-image but no `fdt` - must still get + * /chosen/linux,initrd-* written, into whichever device tree is finally + * selected. The fixup therefore has to run AFTER the fallback that picks + * that tree, which sits past hal_flash_protect(). While it lived inside + * the FIT block it ran before that, and a FIT with no fdt got no initrd + * at all. */ +START_TEST(test_update_disk_fit_ramdisk_fixup_runs_after_dtb_selection) +{ + reset_mocks(); + mock_flat_dt = NULL; /* kernel-only FIT: no fdt sub-image */ + mock_ramdisk = "ramdisk-1"; + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 1); + /* It ran at all ... */ + ck_assert_int_eq(mock_ramdisk_calls, 1); + /* ... and only once the device tree had been selected, rather than + * back inside the FIT block. */ + ck_assert_int_gt(mock_ramdisk_saw_flash_protect, 0); +} +END_TEST + +/* With a FIT that does carry its own fdt the fixup still runs exactly + * once and is handed a tree, so deferring it has not cost the ordinary + * path its initrd. */ +START_TEST(test_update_disk_fit_ramdisk_fixup_gets_the_fit_dtb) +{ + reset_mocks(); + mock_ramdisk = "ramdisk-1"; /* mock_flat_dt stays "fdt" */ + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_ramdisk_calls, 1); + ck_assert_int_gt(mock_ramdisk_saw_flash_protect, 0); + ck_assert_ptr_nonnull(mock_ramdisk_dts); +} +END_TEST +#endif /* WOLFBOOT_FIT_RAMDISK */ + Suite *wolfboot_suite(void) { Suite *s = suite_create("wolfBoot"); @@ -401,6 +484,10 @@ Suite *wolfboot_suite(void) tcase_add_test(tc, test_update_disk_fit_dts_copy_failure_zeroizes_key_material); tcase_add_test(tc, test_update_disk_fit_dts_oversized_rejected); tcase_add_test(tc, test_update_disk_fit_dts_below_min_rejected); +#ifdef WOLFBOOT_FIT_RAMDISK + tcase_add_test(tc, test_update_disk_fit_ramdisk_fixup_runs_after_dtb_selection); + tcase_add_test(tc, test_update_disk_fit_ramdisk_fixup_gets_the_fit_dtb); +#endif tcase_add_test(tc, test_update_disk_fit_dts_copy_success_boots); suite_add_tcase(s, tc); diff --git a/tools/unit-tests/unit-update-ram-fit.c b/tools/unit-tests/unit-update-ram-fit.c new file mode 100644 index 0000000000..2fea31af59 --- /dev/null +++ b/tools/unit-tests/unit-update-ram-fit.c @@ -0,0 +1,399 @@ +/* unit-update-ram-fit.c + * + * loader-level tests for the FIT device-tree fallback and the deferred + * initrd fixup in update_ram.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#ifndef WOLFBOOT_HASH_SHA256 + #define WOLFBOOT_HASH_SHA256 +#endif +#define IMAGE_HEADER_SIZE 256 +#define MOCK_ADDRESS_UPDATE 0xCC000000 +#define MOCK_ADDRESS_BOOT 0xCD000000 +#define MOCK_ADDRESS_SWAP 0xCE000000 +#include "target.h" +static __thread unsigned char wolfboot_ram[2 * WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE]; + +#define WOLFBOOT_LOAD_ADDRESS (((uintptr_t)wolfboot_ram + IMAGE_HEADER_SIZE)) + +#define TEST_SIZE_SMALL 5300 +#define TEST_SIZE_LARGE 9800 + +#define NO_FORK 1 /* Set to 1 to disable fork mode (e.g. for gdb debugging) */ + +#include +#include +#include "user_settings.h" +#include "wolfboot/wolfboot.h" +#include "libwolfboot.c" +/* FDT/FIT stubs, declared before update_ram.c is pulled in. The payload + * does not have to be a real FIT: fdt_open() below accepts anything, so + * wolfBoot_start() always takes the FIT branch. */ +static const char *mock_kernel = "kernel-1"; +static const char *mock_flat_dt; /* NULL = kernel-only FIT */ +static const char *mock_ramdisk = "ramdisk-1"; +static int mock_get_dts_calls; +static int mock_ramdisk_calls; +static int mock_ramdisk_saw_get_dts; +static void *mock_ramdisk_dts; +/* The device tree the boot firmware hands us, as a bootgen raw partition + * would. Big enough for the staging copy update_ram.c makes. */ +#include "fdt.h" +static uint8_t firmware_dtb[WOLFBOOT_DTS_MAX_SIZE]; +static uint8_t dts_stage[WOLFBOOT_DTS_MAX_SIZE]; +#define WOLFBOOT_LOAD_DTS_ADDRESS ((uintptr_t)dts_stage) + +void* hal_get_dts_address(void) +{ + mock_get_dts_calls++; + return firmware_dtb; +} + +int hal_dts_fixup(void *dts_addr, uint32_t capacity) +{ + (void)dts_addr; + (void)capacity; + return 0; +} + +int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->blob = blob; + ctx->capacity = capacity; + ctx->totalsize = 64; + return 0; +} + +uint32_t fdt_size(const fdt_ctx* ctx) +{ + return (ctx != NULL) ? ctx->totalsize : 0; +} + +int fdt_peek_size(const void* hdr, uint32_t hdr_len, uint32_t* totalsize) +{ + (void)hdr; + (void)hdr_len; + if (totalsize != NULL) + *totalsize = 64; + return 0; +} + +void fdt_set_dtb_authenticated(int authenticated) +{ + (void)authenticated; +} + +const char* fit_find_images(fdt_ctx* ctx, const char** pkernel, + const char** pflat_dt, const char** pramdisk, const char** pfpga) +{ + (void)ctx; + if (pkernel != NULL) + *pkernel = mock_kernel; + if (pflat_dt != NULL) + *pflat_dt = mock_flat_dt; + if (pramdisk != NULL) + *pramdisk = mock_ramdisk; + if (pfpga != NULL) + *pfpga = NULL; + return "conf-1"; +} + +void* fit_load_image(fdt_ctx* ctx, const char* image, int* lenp) +{ + (void)ctx; + (void)image; + if (lenp != NULL) + *lenp = 64; + return firmware_dtb; +} + +void* fit_load_kernel(fdt_ctx* ctx, const char* kernel_node, int* lenp) +{ + (void)ctx; + (void)kernel_node; + if (lenp != NULL) + *lenp = 64; + /* Any in-range address; the test only cares about the DTB path. */ + return (void*)WOLFBOOT_LOAD_ADDRESS; +} + +int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts) +{ + (void)ctx; + (void)ramdisk_node; + mock_ramdisk_calls++; + mock_ramdisk_saw_get_dts = mock_get_dts_calls; + mock_ramdisk_dts = dts; + return 0; +} + +#include "update_ram.c" +#include +#include +#include +#include +#include "unit-mock-flash.c" +#include +#include + +const char *argv0; + +Suite *wolfboot_suite(void); + +int wolfBoot_staged_ok = 0; +const uint32_t *wolfBoot_stage_address = (uint32_t *) 0xFFFFFFFF; + +void do_boot(const uint32_t *address, const uint32_t *dts) +{ + struct wolfBoot_image boot_image; + (void)dts; + /* Mock of do_boot */ + if (wolfBoot_panicked) + return; + wolfBoot_staged_ok++; + wolfBoot_stage_address = address; + ck_assert_uint_eq((uintptr_t)address, WOLFBOOT_LOAD_ADDRESS); + memset(&boot_image, 0, sizeof(boot_image)); + printf("Called do_boot with address %p\n", address); + ck_assert_uint_eq(0,wolfBoot_open_image_address(&boot_image, wolfboot_ram)); + boot_image.hdr = wolfboot_ram; + boot_image.fw_base = (void *)(uintptr_t)WOLFBOOT_LOAD_ADDRESS; + boot_image.part = 0; + boot_image.not_ext = 1; + ck_assert_uint_eq(0,wolfBoot_verify_integrity(&boot_image)); + +} + +static int mock_flash_protect_called = 0; +static haladdr_t mock_flash_protect_addr = 0; +static int mock_flash_protect_len = 0; + +static void reset_mock_stats(void) +{ + wolfBoot_panicked = 0; + wolfBoot_staged_ok = 0; + mock_flash_protect_called = 0; + mock_flash_protect_addr = 0; + mock_flash_protect_len = 0; +} + +int hal_flash_protect(haladdr_t address, int len) +{ + mock_flash_protect_called++; + mock_flash_protect_addr = address; + mock_flash_protect_len = len; + return 0; +} + +uint32_t get_version_ramloaded(void) +{ + return wolfBoot_get_blob_version(wolfboot_ram); +} + +static void assert_part_state(uint8_t part, uint8_t expected) +{ + uint8_t st = 0xBB; + ck_assert_int_eq(wolfBoot_get_partition_state(part, &st), 0); + ck_assert_uint_eq(st, expected); +} + + +static void prepare_flash(void) +{ + int ret; + ret = mmap_file("/tmp/wolfboot-unit-ext-file.bin", (void *)(uintptr_t)MOCK_ADDRESS_UPDATE, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(ret >= 0); + ret = mmap_file("/tmp/wolfboot-unit-int-file.bin", (void *)(uintptr_t)MOCK_ADDRESS_BOOT, + WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE, NULL); + ck_assert(ret >= 0); + ext_flash_unlock(); + ext_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); + ext_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); + ext_flash_lock(); +} + +static void cleanup_flash(void) +{ + munmap((void *)WOLFBOOT_PARTITION_BOOT_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); + munmap((void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE + IMAGE_HEADER_SIZE); +} + + +#define DIGEST_TLV_OFF_IN_HDR 28 +static int add_payload(uint8_t part, uint32_t version, uint32_t size) +{ + uint32_t word; + uint16_t word16; + int i; + uint8_t *base = (uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS; + int ret; + wc_Sha256 sha; + uint8_t digest[SHA256_DIGEST_SIZE]; + + ret = wc_InitSha256_ex(&sha, NULL, INVALID_DEVID); + if (ret != 0) + return ret; + + + if (part == PART_UPDATE) + base = (uint8_t *)WOLFBOOT_PARTITION_UPDATE_ADDRESS; + srandom(part); /* Ensure reproducible "random" image */ + + + ext_flash_unlock(); + ext_flash_write((uintptr_t)base, "WOLF", 4); + printf("Written magic: \"WOLF\"\n"); + + ext_flash_write((uintptr_t)base + 4, (void *)&size, 4); + printf("Written size: %u\n", size); + + /* Headers */ + word = 4 << 16 | HDR_VERSION; + ext_flash_write((uintptr_t)base + 8, (void *)&word, 4); + ext_flash_write((uintptr_t)base + 12, (void *)&version, 4); + printf("Written version: %u\n", version); + + word = 2 << 16 | HDR_IMG_TYPE; + ext_flash_write((uintptr_t)base + 16, (void *)&word, 4); + word16 = HDR_IMG_TYPE_AUTH_NONE | HDR_IMG_TYPE_APP; + ext_flash_write((uintptr_t)base + 20, (void *)&word16, 2); + printf("Written img_type: %04X\n", word16); + + /* Add 28B header to sha calculation */ + ret = wc_Sha256Update(&sha, base, DIGEST_TLV_OFF_IN_HDR); + if (ret != 0) + return ret; + + /* Payload */ + size += IMAGE_HEADER_SIZE; + for (i = IMAGE_HEADER_SIZE; i < size; i+=4) { + uint32_t word = (random() << 16) | random(); + ext_flash_write((uintptr_t)base + i, (void *)&word, 4); + } + for (i = IMAGE_HEADER_SIZE; i < size; i+= WOLFBOOT_SHA_BLOCK_SIZE) { + int len = WOLFBOOT_SHA_BLOCK_SIZE; + if ((size - i) < len) + len = size - i; + ret = wc_Sha256Update(&sha, base + i, len); + if (ret != 0) + return ret; + } + + /* Calculate final digest */ + ret = wc_Sha256Final(&sha, digest); + if (ret != 0) + return ret; + wc_Sha256Free(&sha); + + word = SHA256_DIGEST_SIZE << 16 | HDR_SHA256; + ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR, (void *)&word, 4); + ext_flash_write((uintptr_t)base + DIGEST_TLV_OFF_IN_HDR + 4, digest, + SHA256_DIGEST_SIZE); + printf("SHA digest written\n"); + for (i = 0; i < 32; i++) { + printf("%02x ", digest[i]); + } + printf("\n"); + ext_flash_lock(); + + return 0; +} + +static void reset_fit_mocks(void) +{ + reset_mock_stats(); + mock_kernel = "kernel-1"; + mock_flat_dt = NULL; + mock_ramdisk = "ramdisk-1"; + mock_get_dts_calls = 0; + mock_ramdisk_calls = 0; + mock_ramdisk_saw_get_dts = 0; + mock_ramdisk_dts = NULL; + memset(firmware_dtb, 0, sizeof(firmware_dtb)); + memset(dts_stage, 0, sizeof(dts_stage)); +} + +/* A kernel-only FIT - no `fdt` sub-image - has to fall back to the device + * tree the boot firmware left for us, and the initrd fixup has to be + * applied to THAT tree. Both only happen if the fixup runs after the + * fallback; while it lived inside the FIT block it ran first, and a + * kernel-only FIT reached Linux with neither a device tree nor an + * initrd. */ +START_TEST (test_ram_fit_kernel_only_uses_firmware_dtb_for_initrd) +{ + reset_fit_mocks(); + prepare_flash(); + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + + wolfBoot_start(); + + /* The firmware DTB was consulted ... */ + ck_assert_int_gt(mock_get_dts_calls, 0); + /* ... the fixup ran ... */ + ck_assert_int_eq(mock_ramdisk_calls, 1); + /* ... and it ran after that fallback, not before it. */ + ck_assert_int_gt(mock_ramdisk_saw_get_dts, 0); + /* ... against a real tree rather than NULL. */ + ck_assert_ptr_nonnull(mock_ramdisk_dts); + cleanup_flash(); +} +END_TEST + +/* When the FIT does carry its own fdt, that one wins and the firmware + * fallback is not consulted - but the fixup still runs exactly once. */ +START_TEST (test_ram_fit_own_dtb_skips_firmware_fallback) +{ + reset_fit_mocks(); + mock_flat_dt = "fdt-1"; + prepare_flash(); + add_payload(PART_BOOT, 1, TEST_SIZE_SMALL); + + wolfBoot_start(); + + ck_assert_int_eq(mock_get_dts_calls, 0); + ck_assert_int_eq(mock_ramdisk_calls, 1); + ck_assert_ptr_nonnull(mock_ramdisk_dts); + cleanup_flash(); +} +END_TEST + +Suite *wolfboot_suite(void) +{ + Suite *s = suite_create("wolfBoot"); + TCase *tc = tcase_create("update-ram-fit"); + tcase_set_timeout(tc, 20); + tcase_add_test(tc, test_ram_fit_kernel_only_uses_firmware_dtb_for_initrd); + tcase_add_test(tc, test_ram_fit_own_dtb_skips_firmware_fallback); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + SRunner *sr = srunner_create(wolfboot_suite()); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails ? 1 : 0; +}