From 7b5c3b2e53d387bcfd28f5200e63839a0d0b69ff Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 2 Oct 2026 20:07:00 -0700 Subject: [PATCH] fdt: boot a FIT with no kernel load/entry and no device tree --- .github/workflows/test-configs.yml | 23 ++ Makefile | 2 - config/examples/versal_vmk180.config | 38 +- config/examples/versal_vmk180_highddr.config | 155 ++++++++ config/examples/versal_vmk180_sdcard.config | 14 + docs/Targets.md | 41 +- hal/hal.c | 9 + hal/versal.h | 45 ++- include/fdt.h | 4 + include/target.h.in | 11 - options.mk | 49 ++- src/boot_aarch64.c | 6 +- src/boot_aarch64_start.S | 53 ++- src/fdt.c | 42 +- src/update_disk.c | 74 +++- src/update_ram.c | 46 ++- tools/config.mk | 3 + tools/unit-tests/Makefile | 36 +- tools/unit-tests/unit-fdt.c | 45 +++ tools/unit-tests/unit-fit-gzip.c | 118 ++++++ tools/unit-tests/unit-update-disk-fit.c | 161 +++++++- tools/unit-tests/unit-update-ram-fit.c | 394 +++++++++++++++++++ 22 files changed, 1261 insertions(+), 108 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 1c87e31f96..2b22e303ec 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -1280,12 +1280,35 @@ 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: arch: aarch64 config-file: ./config/examples/versal_vmk180_sdcard.config + # FIT read from a filesystem with no kernel load/entry and no fdt + # sub-image, so the staging addresses come from the build. + versal_vmk180_sdcard_fit_file_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + config-file: ./config/examples/versal_vmk180_sdcard.config + make-args: DISK_FS=fat32 FIT_RAMDISK=1 WOLFBOOT_LOAD_KERNEL_ADDRESS=0x200000 WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40000000 + + # Same, with every address moved into a high DDR aperture. + versal_vmk180_sdcard_fit_file_highddr_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + config-file: ./config/examples/versal_vmk180_sdcard.config + make-args: DISK_FS=fat32 FIT_RAMDISK=1 DDR_HIGH_BASE=0x40000000000 DDR_HIGH_SIZE=0x10000000000 NO_DDR_LOW=1 WOLFBOOT_ORIGIN=0x40000800000 WOLFBOOT_LOAD_ADDRESS=0x40010000000 WOLFBOOT_LOAD_DTS_ADDRESS=0x40000001000 WOLFBOOT_LOAD_KERNEL_ADDRESS=0x40002000000 WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40030000000 + versal_vmk180_sdcard_fs_test: uses: ./.github/workflows/test-build-aarch64.yml with: diff --git a/Makefile b/Makefile index e7cc4cc424..0fc8677deb 100644 --- a/Makefile +++ b/Makefile @@ -881,8 +881,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..eb531d62e8 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -47,12 +47,11 @@ WOLFTPM?=0 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 Exception Level (mutually exclusive) +# BOOT_EL1: drop to EL1 via ERET before entering the application. +# BOOT_EL2: enter the application at the EL2 wolfBoot runs at (default). +# EL2 is the default: PSCI brings the secondary cores up at EL2, so dropping +# only the boot core leaves Linux with "CPUs started in inconsistent modes". #BOOT_EL1=1 #BOOT_EL2=1 @@ -67,6 +66,13 @@ 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. 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,17 +113,25 @@ 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 -# 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 +# (e.g. DDR at 0x400_0000_0000). config/examples/versal_vmk180_highddr.config +# is this recipe already applied. 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 0x40000800000 is 0x400_0080_0000 (8 MB in), not 0x400_0800_0000. +# The kernel and ramdisk destinations must clear the FIT staging buffer at +# WOLFBOOT_LOAD_ADDRESS, which the sub-images are read out of, and +# WOLFBOOT_PARTITION_SIZE must fit the whole signed FIT. #WOLFBOOT_ORIGIN=0x40000800000 #WOLFBOOT_LOAD_ADDRESS=0x40010000000 #WOLFBOOT_LOAD_DTS_ADDRESS=0x40000001000 -#CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_BASE=0x40000000000 -#CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_SIZE=0x10000000000 -#CFLAGS_EXTRA+=-DVERSAL_NO_DDR_LOW +#WOLFBOOT_LOAD_KERNEL_ADDRESS=0x40002000000 +#WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40030000000 +#DDR_HIGH_BASE=0x40000000000 +#DDR_HIGH_SIZE=0x10000000000 +#NO_DDR_LOW=1 WOLFBOOT_DTS_BOOT_ADDRESS?=0x7B0000 WOLFBOOT_DTS_UPDATE_ADDRESS?=0x39B0000 diff --git a/config/examples/versal_vmk180_highddr.config b/config/examples/versal_vmk180_highddr.config new file mode 100644 index 0000000000..0dd28443f0 --- /dev/null +++ b/config/examples/versal_vmk180_highddr.config @@ -0,0 +1,155 @@ +# 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. +# +# PLM -> PSM -> BL31 (EL3) -> wolfBoot (EL2) -> Linux (EL2) +# +# 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 +# DDR_HIGH_BASE/DDR_HIGH_SIZE. +# +# Three things outside this file have to agree with it: the bootgen BIF +# (wolfboot.elf at WOLFBOOT_ORIGIN, raw DTB at WOLFBOOT_LOAD_DTS_ADDRESS), +# BL31's PRELOADED_BL33_BASE = WOLFBOOT_ORIGIN, and the FIT ITS using +# #address-cells = <2>. + +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 (mutually exclusive) +# BOOT_EL1: drop to EL1 via ERET before entering the application. +# BOOT_EL2: enter the application at the EL2 wolfBoot runs at (default). +# EL2 is the default: PSCI brings the secondary cores up at EL2, so dropping +# only the boot core leaves Linux with "CPUs started in inconsistent modes". +#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`, so a FIT built without them boots. Set 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. + +# 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 - the assembler consumes them. NO_DDR_LOW drops the low mapping. +DDR_HIGH_BASE=0x40000000000 +DDR_HIGH_SIZE=0x10000000000 +NO_DDR_LOW=1 + +# DDR layout inside the aperture, using '=' to override the '?=' defaults +# above. Each region is distinct: the kernel and ramdisk destinations must +# clear the FIT staging buffer the sub-images are read out of. +# +# 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/config/examples/versal_vmk180_sdcard.config b/config/examples/versal_vmk180_sdcard.config index 50db74a57d..34efbaa818 100644 --- a/config/examples/versal_vmk180_sdcard.config +++ b/config/examples/versal_vmk180_sdcard.config @@ -103,6 +103,20 @@ CFLAGS_EXTRA+=-DBOOT_PART_B=2 #DISK_FS=both #CFLAGS_EXTRA+=-DBOOT_FILE_A='"/boot/fitImage_A.itb"' #CFLAGS_EXTRA+=-DBOOT_FILE_B='"/boot/fitImage_B.itb"' +# +# A U-Boot "fatload mmc 0:1 image.ub ; bootm +# ${fdtcontroladdr}" flow maps onto DISK_FS=fat32 plus BOOT_FILE_A, with +# wolfBoot reading the file itself. If that FIT declares no `load`/`entry` +# and carries no `fdt`, add the staging addresses below and point +# WOLFBOOT_LOAD_DTS_ADDRESS at the DTB the bootgen BIF leaves. +#DISK_FS=fat32 +#CFLAGS_EXTRA+=-DBOOT_FILE_A='"/image.ub"' +#FIT_RAMDISK=1 +#WOLFBOOT_LOAD_KERNEL_ADDRESS=0x200000 +#WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40000000 +# +# For DDR above the default address map see the "High DDR aperture" block +# in config/examples/versal_vmk180.config; the ITS then needs 2 cells. # Select the partitions by name instead of by index. The GPT partition label # is matched first, then the filesystem volume label (which is what makes # this work on MBR disks, where partitions have no names). diff --git a/docs/Targets.md b/docs/Targets.md index dd70453cfc..f99b10422a 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -5202,11 +5202,13 @@ AMD Versal Prime Series VMK180 Evaluation Kit - Versal Prime XCVM1802-2MSEVSVA21 wolfBoot replaces U-Boot in the Versal boot flow: ``` -PLM -> PSM -> BL31 (EL3) -> wolfBoot (EL2) -> Linux (EL1) +PLM -> PSM -> BL31 (EL3) -> wolfBoot (EL2) -> Linux (EL2) ``` wolfBoot runs from DDR at `0x8000000` (EL2, non-secure). All clock, MIO, and DDR initialization is handled by PLM/PSM before wolfBoot starts. +The application is entered at **EL2** by default (`BOOT_EL2` in `hal/versal.h`). PSCI brings the secondary cores up at EL2, so dropping only the boot core to EL1 leaves Linux with `CPUs started in inconsistent modes`. Set `BOOT_EL1=1` for a payload that requires EL1 - that was the previous default, so it now has to be stated explicitly. + This target supports **two boot paths**: - **QSPI boot** (primary, production-style): `config/examples/versal_vmk180.config` - **SD card boot** (MBR, A/B images): `config/examples/versal_vmk180_sdcard.config` @@ -5223,7 +5225,7 @@ Note: If using QSPI there are bootgen issues with 2025.1+, so recommend 2024.1 o ### Common Notes - Debugging with OCRAM (OCM): set `WOLFBOOT_ORIGIN=0xFFFC0000` (OCM is 256KB at `0xFFFC0000 - 0xFFFFFFFF`). -- **High DDR apertures**: designs whose DDR window lives above the default map (for example DDR at `0x400_0000_0000`) add `CFLAGS_EXTRA+=-DVERSAL_DDR_HIGH_BASE=...` and `-DVERSAL_DDR_HIGH_SIZE=...` (512 GB aligned, plain hex literals with no `UL` suffix) to map the window in the translation table, plus `-DVERSAL_NO_DDR_LOW` when nothing remains at `0x0`. Move `WOLFBOOT_ORIGIN`, `WOLFBOOT_LOAD_ADDRESS` and `WOLFBOOT_LOAD_DTS_ADDRESS` into the window (`hal/versal.ld` follows `WOLFBOOT_ORIGIN` from the config), update the bootgen BIF load/exec and the BL31 BL33 entry to match, and switch the FIT ITS to `#address-cells = <2>` with two-cell `load`/`entry` values since 32-bit cells cannot hold addresses above 4 GB. A commented recipe is in `config/examples/versal_vmk180.config`; the DDR aperture itself must be routed to the APU by the design's PDI. +- **High DDR apertures**: designs whose DDR window lives above the default map (for example DDR at `0x400_0000_0000`) set `DDR_HIGH_BASE=...` and `DDR_HIGH_SIZE=...` (512 GB aligned, plain hex with no `UL` suffix) to map the window in the translation table, plus `NO_DDR_LOW=1` when nothing remains at `0x0`. These are AArch64-generic, and the assembler rejects a window outside the VA region the target's TCR covers; the older `-DVERSAL_DDR_HIGH_*` spellings are still accepted. Move `WOLFBOOT_ORIGIN`, `WOLFBOOT_LOAD_ADDRESS` and `WOLFBOOT_LOAD_DTS_ADDRESS` into the window (`hal/versal.ld` follows `WOLFBOOT_ORIGIN` from the config), update the bootgen BIF load/exec and the BL31 BL33 entry to match, and switch the FIT ITS to `#address-cells = <2>` with two-cell `load`/`entry` values since 32-bit cells cannot hold addresses above 4 GB. A commented recipe is in `config/examples/versal_vmk180.config`; the DDR aperture itself must be routed to the APU by the design's PDI. - Test application uses generic `boot_arm64_start.S` and `AARCH64.ld` and prints EL + version. - Entry point: `_start` (in `boot_arm64_start.S`) which sets up stack, clears BSS, and calls `main()` @@ -5433,6 +5435,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 `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 gives it. + +Either add the properties to the ITS, or set `WOLFBOOT_LOAD_KERNEL_ADDRESS` to the address the kernel should be staged at; a nonzero value overrides the FIT's `load`/`entry` and is returned as the entry point. `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: + +```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`. + +Such a DTB is outside the FIT, so the signature does not cover it: `/chosen/bootargs` from the blob is **not** trusted and `LINUX_BOOTARGS` replaces it. On the RAM/flash loader (`src/update_ram.c`) a digest bound with `sign --dts` authenticates it and honors its bootargs, and `WOLFBOOT_REQUIRE_SIGNED_DTB=1` makes a missing digest fatal. The disk loader (`src/update_disk.c`) has no DTB-digest machinery, so put the DTB in the FIT as an `fdt` sub-image to bring it inside the root of trust there. + **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/hal/hal.c b/hal/hal.c index cbaaf8c200..dbd39264c1 100644 --- a/hal/hal.c +++ b/hal/hal.c @@ -284,6 +284,15 @@ int hal_flash_test_dualbank(void) #endif /* TEST_FLASH */ +#ifdef MMU +/* Weak default for targets with no configured device tree; some HALs also + * compile their own out under WOLFBOOT_NO_PARTITIONS. */ +WEAKFUNCTION void* hal_get_dts_address(void) +{ + return NULL; +} +#endif /* MMU */ + WEAKFUNCTION int RAMFUNCTION hal_flash_protect(haladdr_t address, int len) { (void)address; diff --git a/hal/versal.h b/hal/versal.h index eddd6d5885..df143b6361 100644 --- a/hal/versal.h +++ b/hal/versal.h @@ -78,19 +78,18 @@ * BL31 handles GIC initialization, so skip gicv2_init_secure(). * * Boot Exception Level Configuration (mutually exclusive): - * BOOT_EL1: wolfBoot runs at EL2, but applications (Linux, most bare-metal) - * are transitioned to EL1 before jumping (default) - * BOOT_EL2: Applications boot at EL2 (for hypervisors/RTOSes that - * expect to run at EL2) + * BOOT_EL1: transition the application to EL1 before jumping + * BOOT_EL2: enter the application at the EL2 wolfBoot runs at (default) * - * Default: BOOT_EL1 (most applications expect EL1) + * EL2 is the default: PSCI brings the secondary cores up at EL2, so dropping + * only the boot core leaves Linux with "CPUs started in inconsistent modes". */ #ifndef SKIP_GIC_INIT #define SKIP_GIC_INIT 1 #endif #if !defined(BOOT_EL1) && !defined(BOOT_EL2) - #define BOOT_EL1 1 + #define BOOT_EL2 1 #endif #if defined(BOOT_EL1) && defined(BOOT_EL2) @@ -123,24 +122,32 @@ /* DDR defines for MMU table setup (used by boot_aarch64_start.S) * These macros enable proper DDR mapping in the page tables. * Without these, the MMU tables would have DDR_0_REG=0 and no DDR mapped! - * VERSAL_NO_DDR_LOW leaves the low window (0x0-0x7FFFFFFF) unmapped, for - * designs whose DDR aperture lives entirely above 4 GB; pair it with - * VERSAL_DDR_HIGH_BASE/VERSAL_DDR_HIGH_SIZE (512 GB aligned, plain hex - * literals with no UL/ULL suffix - they are consumed by the assembler), - * which map an additional Normal-memory window in the translation table. */ -#if defined(VERSAL_DDR_HIGH_BASE) && !defined(VERSAL_DDR_HIGH_SIZE) -#error "VERSAL_DDR_HIGH_BASE requires VERSAL_DDR_HIGH_SIZE" + * Set from a .config with DDR_HIGH_BASE / DDR_HIGH_SIZE / NO_DDR_LOW=1 + * (see options.mk); those are AArch64-generic, not Versal-specific. */ + +/* Deprecated VERSAL_ spellings, kept because they shipped in v2.10.0. */ +#if defined(VERSAL_DDR_HIGH_BASE) && !defined(WOLFBOOT_DDR_HIGH_BASE) +#define WOLFBOOT_DDR_HIGH_BASE VERSAL_DDR_HIGH_BASE +#endif +#if defined(VERSAL_DDR_HIGH_SIZE) && !defined(WOLFBOOT_DDR_HIGH_SIZE) +#define WOLFBOOT_DDR_HIGH_SIZE VERSAL_DDR_HIGH_SIZE +#endif +#if defined(VERSAL_NO_DDR_LOW) && !defined(WOLFBOOT_NO_DDR_LOW) +#define WOLFBOOT_NO_DDR_LOW #endif -#if defined(VERSAL_DDR_HIGH_SIZE) && !defined(VERSAL_DDR_HIGH_BASE) -#error "VERSAL_DDR_HIGH_SIZE requires VERSAL_DDR_HIGH_BASE" + +#if defined(WOLFBOOT_DDR_HIGH_BASE) && !defined(WOLFBOOT_DDR_HIGH_SIZE) +#error "DDR_HIGH_BASE requires DDR_HIGH_SIZE" +#endif +#if defined(WOLFBOOT_DDR_HIGH_SIZE) && !defined(WOLFBOOT_DDR_HIGH_BASE) +#error "DDR_HIGH_SIZE requires DDR_HIGH_BASE" #endif -#if defined(VERSAL_NO_DDR_LOW) && !defined(VERSAL_DDR_HIGH_BASE) -#error "VERSAL_NO_DDR_LOW without VERSAL_DDR_HIGH_BASE maps no DDR at all" +#if defined(WOLFBOOT_NO_DDR_LOW) && !defined(WOLFBOOT_DDR_HIGH_BASE) +#error "NO_DDR_LOW without DDR_HIGH_BASE maps no DDR at all" #endif -#ifndef VERSAL_NO_DDR_LOW +/* WOLFBOOT_NO_DDR_LOW is honored in src/boot_aarch64_start.S. */ #define XPAR_PSU_DDR_0_S_AXI_BASEADDR VERSAL_DDR_0_BASE #define XPAR_PSU_DDR_0_S_AXI_HIGHADDR VERSAL_DDR_0_HIGH -#endif /* ============================================================================ diff --git a/include/fdt.h b/include/fdt.h index 4534baf5d0..d1a392e83f 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -415,6 +415,10 @@ 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, relocated to WOLFBOOT_LOAD_KERNEL_ADDRESS when that + * is nonzero. Returns the 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 77c0af941c..4ca7606026 100644 --- a/options.mk +++ b/options.mk @@ -1116,6 +1116,25 @@ FIT_RAMDISK ?= 0 ifeq ($(FIT_RAMDISK),1) CFLAGS+=-DWOLFBOOT_FIT_RAMDISK endif +# Destination for the (decompressed) ramdisk. 0 honors the FIT's own `load`. +# +# This and the two load addresses below are CFLAGS, not include/target.h.in +# entries: target.h is generated by three paths and only this Makefile +# substitutes them. Defaulted here and in tools/config.mk, since the host +# tools include options.mk on its own. +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) honors the +# FIT's own `load`. Set this when the FIT was built without `load`/`entry` +# on the kernel node; pick a region clear of WOLFBOOT_LOAD_ADDRESS and of +# the DTB/ramdisk addresses. +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 @@ -1129,8 +1148,36 @@ 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 above. WOLFBOOT_LOAD_FPGA_ADDRESS ?= 0 +ifneq ($(WOLFBOOT_LOAD_FPGA_ADDRESS),0) + CFLAGS+=-DWOLFBOOT_LOAD_FPGA_ADDRESS=$(WOLFBOOT_LOAD_FPGA_ADDRESS) +endif +# High DDR aperture (AArch64, shared table in src/boot_aarch64_start.S). +# DDR_HIGH_BASE/DDR_HIGH_SIZE add a Normal-memory window; NO_DDR_LOW=1 +# drops the default low-DDR mapping. Both 512 GB aligned, plain hex with +# no UL/ULL suffix - the assembler consumes them and bounds-checks them. +DDR_HIGH_BASE ?= +DDR_HIGH_SIZE ?= +NO_DDR_LOW ?= 0 +ifneq ($(DDR_HIGH_BASE),) + ifeq ($(DDR_HIGH_SIZE),) + $(error DDR_HIGH_BASE requires DDR_HIGH_SIZE) + endif + CFLAGS+=-DWOLFBOOT_DDR_HIGH_BASE=$(DDR_HIGH_BASE) + CFLAGS+=-DWOLFBOOT_DDR_HIGH_SIZE=$(DDR_HIGH_SIZE) +else + ifneq ($(DDR_HIGH_SIZE),) + $(error DDR_HIGH_SIZE requires DDR_HIGH_BASE) + endif +endif +ifeq ($(NO_DDR_LOW),1) + ifeq ($(DDR_HIGH_BASE),) + $(error NO_DDR_LOW requires DDR_HIGH_BASE, or no DDR is mapped at all) + endif + CFLAGS+=-DWOLFBOOT_NO_DDR_LOW +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 85b825959e..83abbaa1ff 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -165,10 +165,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. */ if (hal_dts_fixup((uint32_t*)dts_offset, WOLFBOOT_DTS_MAX_SIZE) != 0) { diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index e9c99d44be..7de7bdfaed 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -50,6 +50,16 @@ #include "hal/imx8qm.h" #endif +/* VA region the TCR_EL{1,2,3} T0SZ values below cover. A high DDR window + * must fit inside it; keep in step with those TCR literals. */ +#ifndef WOLFBOOT_AARCH64_VA_LIMIT +#ifdef TARGET_versal +#define WOLFBOOT_AARCH64_VA_LIMIT 0x100000000000 /* 2^44, TCR 0x80843514 */ +#else +#define WOLFBOOT_AARCH64_VA_LIMIT 0x10000000000 /* 2^40, TCR 0x80823518 */ +#endif +#endif + /* GICv2 Register Offsets */ #ifndef GICD_BASE #define GICD_BASE 0xF9010000 @@ -610,34 +620,35 @@ MMUTableL0: .set SECT, MMUTableL1+0x1000 /* 0x80_0000_0000 - 0xFF_FFFF_FFFF */ .8byte SECT + 0x3 -#if defined(TARGET_versal) && defined(VERSAL_DDR_HIGH_BASE) +#ifdef WOLFBOOT_DDR_HIGH_BASE /* Optional high DDR window (designs whose DDR aperture sits above 1 TB). * One L0 descriptor per 512 GB, pointing at MMUTableL1_HI pages below. - * Base and size must be 512 GB aligned and end within the 44-bit space. */ -.if (VERSAL_DDR_HIGH_BASE % 0x8000000000) != 0 -.error "VERSAL_DDR_HIGH_BASE must be 512GB aligned" + * Base and size must be 512 GB aligned and the window must end inside the + * VA region this target's TCR covers. */ +.if (WOLFBOOT_DDR_HIGH_BASE % 0x8000000000) != 0 +.error "WOLFBOOT_DDR_HIGH_BASE must be 512GB aligned" .endif -.if VERSAL_DDR_HIGH_SIZE < 0x8000000000 -.error "VERSAL_DDR_HIGH_SIZE must be at least 512GB (one L0 granule)" +.if WOLFBOOT_DDR_HIGH_SIZE < 0x8000000000 +.error "WOLFBOOT_DDR_HIGH_SIZE must be at least 512GB (one L0 granule)" .endif -.if (VERSAL_DDR_HIGH_SIZE % 0x8000000000) != 0 -.error "VERSAL_DDR_HIGH_SIZE must be a multiple of 512GB" +.if (WOLFBOOT_DDR_HIGH_SIZE % 0x8000000000) != 0 +.error "WOLFBOOT_DDR_HIGH_SIZE must be a multiple of 512GB" .endif -.if (VERSAL_DDR_HIGH_BASE + VERSAL_DDR_HIGH_SIZE) > 0x100000000000 -.error "VERSAL_DDR_HIGH window exceeds the 44-bit address space" +.if (WOLFBOOT_DDR_HIGH_BASE + WOLFBOOT_DDR_HIGH_SIZE) > WOLFBOOT_AARCH64_VA_LIMIT +.error "DDR_HIGH window exceeds the VA space this target's TCR covers" .endif -.if VERSAL_DDR_HIGH_BASE < 0x10000000000 -.error "VERSAL_DDR_HIGH_BASE must be at least 1TB (the first two L0 entries are fixed)" +.if WOLFBOOT_DDR_HIGH_BASE < 0x10000000000 +.error "WOLFBOOT_DDR_HIGH_BASE must be at least 1TB (the first two L0 entries are fixed)" .endif -.rept ((VERSAL_DDR_HIGH_BASE / 0x8000000000) - 2) +.rept ((WOLFBOOT_DDR_HIGH_BASE / 0x8000000000) - 2) .8byte 0 /* invalid up to the high window */ .endr .set SECT, MMUTableL1_HI -.rept (VERSAL_DDR_HIGH_SIZE / 0x8000000000) +.rept (WOLFBOOT_DDR_HIGH_SIZE / 0x8000000000) .8byte SECT + 0x3 .set SECT, SECT + 0x1000 .endr -#endif /* TARGET_versal && VERSAL_DDR_HIGH_BASE */ +#endif /* WOLFBOOT_DDR_HIGH_BASE */ .section .mmu_tbl1,"a" @@ -707,16 +718,16 @@ MMUTableL1: .endr -#if defined(TARGET_versal) && defined(VERSAL_DDR_HIGH_BASE) +#ifdef WOLFBOOT_DDR_HIGH_BASE /* 1 GB Normal-memory blocks covering the high DDR window. */ .balign 4096 MMUTableL1_HI: -.set SECT, VERSAL_DDR_HIGH_BASE -.rept (VERSAL_DDR_HIGH_SIZE / 0x40000000) +.set SECT, WOLFBOOT_DDR_HIGH_BASE +.rept (WOLFBOOT_DDR_HIGH_SIZE / 0x40000000) .8byte SECT + Memory .set SECT, SECT + 0x40000000 .endr -#endif /* TARGET_versal && VERSAL_DDR_HIGH_BASE */ +#endif /* WOLFBOOT_DDR_HIGH_BASE */ .section .mmu_tbl2,"a" @@ -724,7 +735,9 @@ MMUTableL2: .set SECT, 0 -#ifdef XPAR_PSU_DDR_0_S_AXI_BASEADDR +/* WOLFBOOT_NO_DDR_LOW drops this window for designs whose DDR lives + * entirely in a high aperture. */ +#if defined(XPAR_PSU_DDR_0_S_AXI_BASEADDR) && !defined(WOLFBOOT_NO_DDR_LOW) .set DDR_0_START, XPAR_PSU_DDR_0_S_AXI_BASEADDR .set DDR_0_END, XPAR_PSU_DDR_0_S_AXI_HIGHADDR .set DDR_0_SIZE, (DDR_0_END - DDR_0_START)+1 diff --git a/src/fdt.c b/src/fdt.c index 257c04e6b2..bdccb3891a 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -47,6 +47,12 @@ #include "gzip.h" #endif +/* Largest address a pointer can hold. Overridable so the two-cell + * rejection below can be exercised at 32-bit width on a 64-bit host. */ +#ifndef FDT_PTR_MAX +#define FDT_PTR_MAX UINTPTR_MAX +#endif + /* Coarse upper bound on a single FIT subimage's decompressed size. * This is a sanity ceiling, not a per-destination memory-safety * bound. Authenticity of the FIT bytes is provided by the outer @@ -812,7 +818,15 @@ 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); + /* Reject rather than truncate: dropping the high cell would turn + * an out-of-range address into an unrelated low one. */ +#if FDT_PTR_MAX < UINT64_MAX + if (addr64 > (uint64_t)FDT_PTR_MAX) { + return NULL; + } +#endif + return (void*)(uintptr_t)addr64; } if (len == 4) { return (void*)(uintptr_t)fdt_rd32(val); @@ -1674,6 +1688,28 @@ const char* fit_get_compatible(fdt_ctx* ctx, const char* image) return NULL; } +/* 0 honors the FIT's own `load` property. */ +#ifndef WOLFBOOT_LOAD_KERNEL_ADDRESS +#define WOLFBOOT_LOAD_KERNEL_ADDRESS 0 +#endif + +/* Upper bound on the decompressed kernel size. */ +#ifndef WOLFBOOT_FIT_MAX_KERNEL +#define WOLFBOOT_FIT_MAX_KERNEL WOLFBOOT_FIT_MAX_DECOMP +#endif + +/* Stage the kernel at WOLFBOOT_LOAD_KERNEL_ADDRESS when nonzero, bypassing + * the FIT's `load`/`entry` so a FIT declaring neither still boots. */ +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 @@ -1941,7 +1977,9 @@ 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, or set this sub-image's " + "WOLFBOOT_LOAD_*_ADDRESS\n", image, compstr, load, data); return NULL; } diff --git a/src/update_disk.c b/src/update_disk.c index 8cdacb7a6a..c4f693865b 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -677,6 +677,11 @@ 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, set once the FIT parses. The initrd fixup is + * deferred until dts_addr is final. */ + const char *fit_ramdisk = NULL; + #endif #endif #endif #if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU) @@ -753,7 +758,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 @@ -1172,7 +1177,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); @@ -1223,20 +1228,16 @@ 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 { + /* Do not fall through to the firmware DTB silently: it is + * a different device tree than the FIT names. */ + wolfBoot_printf("FIT: '%s' is not a usable DTB (%d); " + "trying the boot firmware's instead\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 @@ -1252,7 +1253,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(); @@ -1285,6 +1286,49 @@ void RAMFUNCTION wolfBoot_start(void) if (dts_addr == NULL) { dts_addr = (uint8_t*)hal_get_boot_dts(); } + if (dts_addr == NULL) { + /* Fall back to the configured DTB, as src/update_ram.c does - e.g. + * a bootgen raw partition at WOLFBOOT_LOAD_DTS_ADDRESS. */ + uint8_t *cfg_dts = (uint8_t*)hal_get_dts_address(); + int cfg_len; + + if (cfg_dts != NULL) { + cfg_len = wolfBoot_get_dts_size(cfg_dts, WOLFBOOT_DTS_MAX_SIZE); + if (cfg_len < (int)WOLFBOOT_DTS_MIN_SIZE || + (uint32_t)cfg_len > WOLFBOOT_DTS_MAX_SIZE) { + wolfBoot_printf("DTB parse/size check failed - ignoring\r\n"); + } + else if ((uintptr_t)cfg_dts == + (uintptr_t)WOLFBOOT_LOAD_DTS_ADDRESS) { + /* Usual case: the HAL returns the staging address. */ + dts_addr = cfg_dts; + } + else if (wolfBoot_fit_memcpy((void*)WOLFBOOT_LOAD_DTS_ADDRESS, + cfg_dts, (uint32_t)cfg_len) == 0) { + /* Relocate first: the initrd fixup grows the tree in place + * against WOLFBOOT_DTS_MAX_SIZE of writable space. */ + dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS; + } + else { + wolfBoot_printf("DTB relocation failed - ignoring\r\n"); + } + } + } +#endif +#if defined(WOLFBOOT_FDT) && defined(WOLFBOOT_FIT_RAMDISK) + /* Deferred until dts_addr is final so the fixup lands on the tree the + * kernel is handed. Outside the MMU guard for the MPFS E51 boot. */ + if (fit_ramdisk != NULL) { + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The DTB is in the staging window; that is the growth capacity. */ + 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..db57bd7d2e 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -302,6 +302,11 @@ 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, set once the FIT parses. The initrd fixup is + * deferred until dts_addr is final. */ + 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 +647,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 +692,23 @@ 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 { + /* Do not fall through to the firmware DTB silently: it is + * a different device tree than the FIT names. */ + wolfBoot_printf("FIT: '%s' is not a usable DTB (%d); " + "trying the boot firmware's instead\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 a FIT parsed but carried no usable `fdt`: the + * device tree then comes from the boot firmware. */ + 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 +789,21 @@ void RAMFUNCTION wolfBoot_start(void) } } } +#ifdef WOLFBOOT_FIT_RAMDISK + /* Deferred until dts_addr is final so the fixup lands on the tree the + * kernel is handed. */ + if (fit_ramdisk != NULL) { + fdt_ctx dts_ctx; + fdt_ctx* dts_for_initrd = NULL; + + /* The DTB is in the staging window; that is the growth capacity. */ + 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..ee72d17834 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,8 @@ 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 \ + DDR_HIGH_BASE DDR_HIGH_SIZE NO_DDR_LOW \ 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 5ccc3509b1..9bfb1204a5 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-golden 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-golden 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 \ @@ -147,6 +147,7 @@ TESTS+=unit-rp2350-flash-write TESTS+=unit-fwtpm-rsp-overrun TESTS+=unit-fwtpm-cmd-toctou TESTS+=unit-fdt-memrsv-wrap +TESTS+=unit-fdt-addr32 TESTS+=unit-ns16550 TESTS+=unit-zynq-dma-range TESTS+=unit-pkcs11_store-stalecache @@ -358,6 +359,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 \ @@ -456,9 +464,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). @@ -487,6 +497,13 @@ unit-fdt: ../../include/target.h unit-fdt.c ../../src/fdt.c gcc -o $@ $^ $(CFLAGS) -ffunction-sections -fdata-sections $(LDFLAGS) \ -Wl,--gc-sections +# Same suite with the pointer bound narrowed to 32 bits, so a two-cell +# load/entry above 4 GB has to be rejected rather than truncated. +unit-fdt-addr32:CFLAGS+=-DWOLFBOOT_FDT -DFDT_PTR_MAX=0xFFFFFFFFUL +unit-fdt-addr32: ../../include/target.h unit-fdt.c ../../src/fdt.c + gcc -o $@ $^ $(CFLAGS) -ffunction-sections -fdata-sections $(LDFLAGS) \ + -Wl,--gc-sections + unit-extflash: ../../include/target.h unit-extflash.c gcc -o $@ $^ $(CFLAGS) $(LDFLAGS) @@ -991,6 +1008,7 @@ unit-gzip-root7: ../../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 @@ -1001,7 +1019,7 @@ unit-fit-nogzip: ../../include/target.h unit-fit-gzip.c unit-fit-gzip-small: ../../include/target.h unit-fit-gzip.c gcc -o $@ unit-fit-gzip.c $(CFLAGS) -DWOLFBOOT_FDT -DWOLFBOOT_GZIP \ - -DWOLFBOOT_GZIP_SMALL -DWOLFBOOT_NO_PRINTF \ + -DWOLFBOOT_GZIP_SMALL -DWOLFBOOT_FIT_RAMDISK -DWOLFBOOT_NO_PRINTF \ -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections # FIT fpga-subimage discovery (fit_find_images / fit_get_compatible). @@ -1090,6 +1108,11 @@ 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; 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) @@ -1160,6 +1183,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 MMU and FIT_RAMDISK on: the device-tree fallback and the +# deferred initrd fixup both live under #ifdef MMU in update_disk.c. +unit-update-disk-fit-ramdisk: ../../include/target.h unit-update-disk-fit.c + gcc -o $@ unit-update-disk-fit.c $(CFLAGS) -DWOLFBOOT_FIT_RAMDISK -DMMU $(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 b34a5581fc..be179fc5eb 100644 --- a/tools/unit-tests/unit-fdt.c +++ b/tools/unit-tests/unit-fdt.c @@ -27,6 +27,12 @@ #include "../../include/fdt.h" +/* Must match src/fdt.c; the unit-fdt-addr32 target narrows both to 32 bits + * so the two-cell rejection runs on a 64-bit host. */ +#ifndef FDT_PTR_MAX +#define FDT_PTR_MAX UINTPTR_MAX +#endif + void wolfBoot_printf(const char *fmt, ...) { (void)fmt; @@ -1306,6 +1312,44 @@ 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 can express an address above + * 4 GB, so the 8-byte form has to decode whole. */ + 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 FDT_PTR_MAX > 0xFFFFFFFFUL + ck_assert_ptr_eq(fdt_getprop_address(&ctx, off, "load"), + (void*)(uintptr_t)0x40008000000ULL); +#else + /* A 32-bit target cannot represent it; truncating would yield an + * unrelated address, so it has to come back NULL. */ + ck_assert_ptr_null(fdt_getprop_address(&ctx, off, "load")); +#endif + + /* A single cell still decodes. */ + 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))); @@ -1517,6 +1561,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..6025d5c8ec 100644 --- a/tools/unit-tests/unit-fit-gzip.c +++ b/tools/unit-tests/unit-fit-gzip.c @@ -44,6 +44,13 @@ void wolfBoot_printf(const char *fmt, ...) (void)fmt; } +/* Resolves to a variable here so one binary can exercise both branches of + * fit_load_kernel() with the override pointing at a real buffer. */ +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 +72,17 @@ 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, standing in for one the boot firmware + * left rather than 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 +281,102 @@ START_TEST(test_fit_ex_gzip_no_load_returns_null) } END_TEST +START_TEST(test_fit_kernel_no_override_gzip_no_load_fails) +{ + /* No `load`/`entry` and no override: 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 with the override set: it supplies the destination, so the + * kernel inflates and the override 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 has no `fdt`, so the device tree comes from the + * boot firmware - and the initrd fixup must land in THAT tree. */ + 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)); + + /* Full staging capacity, as the loaders do, so /chosen fits. */ + 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, which this process cannot touch). */ + 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 +482,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 4ddd132c78..576d29d8f4 100644 --- a/tools/unit-tests/unit-update-disk-fit.c +++ b/tools/unit-tests/unit-update-disk-fit.c @@ -78,9 +78,25 @@ static int mock_dts_size; static int mock_boot_part_prop; static int mock_chosen_val_ret; static int mock_do_boot_called; +/* The device tree actually handed to the kernel. */ +static const uint32_t *mock_do_boot_dts; 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: hal_flash_protect() runs between the FIT block and + * the deferred block, so a fixup that drifted back is visible here. */ +static int mock_flash_protect_calls; +static int mock_ramdisk_calls; +static int mock_ramdisk_saw_flash_protect; +static fdt_ctx *mock_ramdisk_dts; +/* Stands in for the blob a bootgen raw partition leaves at + * WOLFBOOT_LOAD_DTS_ADDRESS. NULL means the HAL offers none. */ +static uint8_t configured_dts[TEST_DTS_STAGE_SIZE]; +static void *mock_configured_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,11 +137,20 @@ static void reset_mocks(void) memset(fit_dts_image, 0xDD, sizeof(fit_dts_image)); mock_dts_size = TEST_DTS_SIZE; mock_do_boot_called = 0; + mock_do_boot_dts = (const uint32_t*)0x1; /* poison; must be overwritten */ mock_fit_memcpy_ret = 0; mock_fit_memcpy_called = 0; mock_boot_part_prop = -1; mock_chosen_val_ret = 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(configured_dts, 0xCD, sizeof(configured_dts)); + mock_configured_dts = configured_dts; memset(panic_key_snapshot, 0xFF, sizeof(panic_key_snapshot)); memset(panic_nonce_snapshot, 0xFF, sizeof(panic_nonce_snapshot)); wolfBoot_panicked = 0; @@ -272,9 +297,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"; @@ -299,6 +324,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++; @@ -314,17 +346,42 @@ void hal_prepare_boot(void) void do_boot(const uint32_t *address, const uint32_t *dts_address) { - (void)dts_address; (void)address; + mock_do_boot_dts = dts_address; mock_do_boot_called++; } +void* hal_get_dts_address(void) +{ + return mock_configured_dts; +} + +void* hal_get_dts_update_address(void) +{ + return NULL; +} + 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" @@ -436,6 +493,95 @@ START_TEST(test_update_disk_fit_dts_below_min_rejected) } END_TEST +#ifdef WOLFBOOT_FIT_RAMDISK +/* A kernel-only FIT must still get /chosen/linux,initrd-* written into + * whichever device tree is selected, so the fixup runs after the fallback. */ +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 carries its own fdt the fixup still runs exactly once + * and is handed a tree. */ +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 */ + +#ifdef MMU +/* hal_get_boot_dts() is a weak NULL on every target but CM4, so without + * the configured-DTB fallback a kernel-only FIT gets a NULL tree. */ +START_TEST(test_update_disk_fit_kernel_only_uses_configured_dtb) +{ + reset_mocks(); + mock_flat_dt = NULL; /* kernel-only FIT */ + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_int_eq(mock_do_boot_called, 1); + /* Relocated into the staging window, not used where the HAL put it: + * the initrd fixup grows the tree in place. */ + ck_assert_ptr_eq(mock_do_boot_dts, (void*)dts_buffer); + ck_assert_int_eq(memcmp(dts_buffer, configured_dts, TEST_DTS_SIZE), 0); +} +END_TEST + +/* When the HAL hands back the staging window itself, as hal/versal.c does, + * it is used directly with no self-copy. */ +START_TEST(test_update_disk_fit_configured_dtb_already_staged) +{ + reset_mocks(); + mock_flat_dt = NULL; + memset(dts_buffer, 0xAB, TEST_DTS_SIZE); + mock_configured_dts = dts_buffer; + + wolfBoot_start(); + + ck_assert_int_eq(wolfBoot_panicked, 0); + ck_assert_ptr_eq(mock_do_boot_dts, (void*)dts_buffer); + ck_assert_uint_eq(dts_buffer[0], 0xAB); +} +END_TEST + +/* An unparseable configured DTB is dropped, not forwarded. */ +START_TEST(test_update_disk_fit_bad_configured_dtb_rejected) +{ + reset_mocks(); + mock_flat_dt = NULL; + mock_dts_size = 8; /* below WOLFBOOT_DTS_MIN_SIZE */ + + wolfBoot_start(); + + ck_assert_int_eq(mock_do_boot_called, 1); + ck_assert_ptr_null(mock_do_boot_dts); +} +END_TEST +#endif /* MMU */ + Suite *wolfboot_suite(void) { Suite *s = suite_create("wolfBoot"); @@ -444,6 +590,15 @@ 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 MMU + tcase_add_test(tc, test_update_disk_fit_kernel_only_uses_configured_dtb); + tcase_add_test(tc, test_update_disk_fit_configured_dtb_already_staged); + tcase_add_test(tc, test_update_disk_fit_bad_configured_dtb_rejected); +#endif +#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); tcase_add_test(tc, test_update_disk_fit_boot_part_fixup_failure_panics); 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..a7f4884072 --- /dev/null +++ b/tools/unit-tests/unit-update-ram-fit.c @@ -0,0 +1,394 @@ +/* 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. fdt_open() + * 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 falls back to the device tree the boot firmware left, + * and the initrd fixup has to be applied to THAT tree. */ +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; +}