fdt: boot a FIT with no kernel load/entry and no device tree

pull/920/head
David Garske 2026-10-02 20:07:00 -07:00
parent aef57a113d
commit d490e36d2e
22 changed files with 1634 additions and 148 deletions

View File

@ -1332,12 +1332,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:

View File

@ -887,8 +887,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" \
> $@

View File

@ -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
@ -78,6 +77,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-
@ -118,17 +124,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

View File

@ -0,0 +1,159 @@
# 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
# Cap the decompressed kernel at the 224 MB gap to the staging buffer above,
# which is tighter than the 256 MB WOLFBOOT_FIT_MAX_DECOMP default.
CFLAGS_EXTRA+=-DWOLFBOOT_FIT_MAX_KERNEL=0xE000000
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

View File

@ -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 <addr> image.ub ; bootm <addr> <addr>
# ${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).

View File

@ -4601,7 +4601,7 @@ qemu-system-aarch64 -machine xlnx-zcu102 -cpu cortex-a53 -serial stdio -display
Use `config/examples/zynqmp_sdcard.config`. This uses the Arasan SDHCI controller (SD1 - external SD card slot on ZCU102) and an **MBR** partitioned SD card.
On the direct-jump handoff path, wolfBoot flushes the EL2 D-cache/I-cache and disables the EL2 MMU via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S` when `BOOT_EL1` is not enabled and the current exception level is EL2. The ERET-to-EL1 handoff path is different, so this cleanup is not unconditional.
Both handoff paths flush the EL2 D-cache/I-cache and disable the EL2 MMU via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S`, under different conditions: the ERET-to-EL1 path whenever `BOOT_EL1` and `EL2_HYPERVISOR=1` are both set, and the direct-jump path when `EL2_HYPERVISOR=1` and the current exception level is EL2.
**Partition layout**
| Partition | Name | Size | Type | Contents |
@ -5253,11 +5253,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`
@ -5274,7 +5276,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()`
@ -5500,6 +5502,43 @@ 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.
`WOLFBOOT_FIT_MAX_KERNEL` bounds how much may be written at that address. It defaults to `WOLFBOOT_FIT_MAX_DECOMP` (256 MB) and is set with `CFLAGS_EXTRA+=-DWOLFBOOT_FIT_MAX_KERNEL=...`; `WOLFBOOT_FIT_MAX_RAMDISK` and `WOLFBOOT_FIT_MAX_FPGA` are the equivalents for those nodes. Like the other `WOLFBOOT_FIT_MAX_*` caps it is a compile-time sanity ceiling and not a description of your DDR map, so lower it to the staging window if you want a tight bound. wolfBoot narrows it further at run time to the space left below the staged FIT and below wolfBoot's own image, and a kernel that does not fit there is rejected with a message rather than copied or decompressed over either - both the uncompressed and the `gzip` path make that check.
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.
@ -5608,7 +5647,7 @@ Typical boot timing with ECC384/SHA384 signing:
Use `config/examples/versal_vmk180_sdcard.config`. This uses the Arasan SDHCI controller and an **MBR** partitioned SD card.
Versal defaults to `BOOT_EL1` — the handoff goes through `el2_to_el1_boot` (ERET to EL1). Custom `BOOT_EL2` Versal configs get the same EL2 cache/MMU teardown as ZynqMP via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S`, so no extra config flag is needed to boot Linux directly at EL2.
Versal enters the application at `BOOT_EL2` by default, with the same EL2 cache/MMU teardown as ZynqMP via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S`, so no extra config flag is needed to boot Linux directly at EL2. Set `BOOT_EL1=1` to hand off through `el2_to_el1_boot` (ERET to EL1) instead.
**Partition layout**
| Partition | Name | Size | Type | Contents |

View File

@ -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;

View File

@ -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(VERSAL_DDR_HIGH_BASE)
#error "VERSAL_DDR_HIGH_SIZE requires VERSAL_DDR_HIGH_BASE"
#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(VERSAL_DDR_HIGH_BASE)
#error "VERSAL_NO_DDR_LOW without VERSAL_DDR_HIGH_BASE maps no DDR at all"
#if defined(VERSAL_NO_DDR_LOW) && !defined(WOLFBOOT_NO_DDR_LOW)
#define WOLFBOOT_NO_DDR_LOW
#endif
#ifndef VERSAL_NO_DDR_LOW
#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(WOLFBOOT_NO_DDR_LOW) && !defined(WOLFBOOT_DDR_HIGH_BASE)
#error "NO_DDR_LOW without DDR_HIGH_BASE maps no DDR at all"
#endif
/* 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
/* ============================================================================

View File

@ -415,11 +415,15 @@ 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
* `dts`. Pass dts as NULL to skip the fixup. Returns 0 on success, -1
* on load failure. Callers typically log and continue. */
* `dts`. Returns 0 on success, -1 on load failure or when `dts` is NULL,
* since a staged ramdisk is useless unless the tree names it. */
int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts);
#endif

View File

@ -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_ */

View File

@ -1149,6 +1149,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
@ -1162,8 +1181,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

View File

@ -161,10 +161,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) {

View File

@ -58,6 +58,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
@ -573,34 +583,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"
@ -670,16 +681,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"
@ -695,7 +706,9 @@ MMUTableL2:
.set SECT, SECT+0x200000
.endr
#else
#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

141
src/fdt.c
View File

@ -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
@ -1697,12 +1733,12 @@ const char* fit_get_compatible(fdt_ctx* ctx, const char* image)
* straight into the override (with the override capacity acting as
* the safety bound). Otherwise the address fit_load_image returned
* (FIT-specified or in-FIT pointer) is used as-is. Caller passes the
* DTB context for the initrd fixup, or NULL to skip the fixup.
* DTB context the initrd fixup writes into.
*
* Returns 0 on success, -1 if the ramdisk node was found but the
* load failed. The current callers ignore the return value
* (log-and-continue), so a missing/failed ramdisk does not abort
* the boot. */
* Returns 0 on success, -1 if the ramdisk node was found but the load
* failed, or if `dts` is NULL - a ramdisk the kernel is never told
* about is not a success. Callers log and continue, so this does not
* abort the boot. */
int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts)
{
int rd_size = 0;
@ -1733,7 +1769,14 @@ int fit_load_ramdisk(fdt_ctx* ctx, const char* ramdisk_node, fdt_ctx* dts)
wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", rd_dst, rd_size);
}
if (dts != NULL) {
if (dts == NULL) {
/* Staged but unreachable: without a tree to write
* /chosen/linux,initrd-* into, the kernel never learns of it. */
wolfBoot_printf("FIT: no device tree for the initrd fixup; "
"kernel will not see initrd\n");
return -1;
}
{
int frc = fdt_fixup_initrd(dts, (uint64_t)(uintptr_t)rd_dst,
(uint64_t)rd_size);
if (frc != 0) {
@ -1773,6 +1816,46 @@ int __attribute__((weak)) wolfBoot_fit_check_dest(void *dst, uint32_t len)
return 0;
}
/* Narrow `out_max` to what may actually be written at `load`: anything above
* it that has to survive the load caps the window - the staged FIT, whose
* header and structure block the later `fdt`/`ramdisk` lookups still read,
* and wolfBoot's own image. A gzip stream can emit garbage at full match
* speed and a raw subimage is copied just as blindly, so both paths need the
* same bound. Conservative lower bounds. */
static uint32_t fit_dest_bound(const fdt_ctx* ctx, const void* load,
const void* data, uint32_t out_max)
{
uintptr_t gap;
if ((uintptr_t)ctx->blob > (uintptr_t)load) {
gap = (uintptr_t)ctx->blob - (uintptr_t)load;
if (gap < (uintptr_t)out_max) {
out_max = (uint32_t)gap;
}
}
/* A destination inside the blob but below `data` is past the bound
* above, so the data pointer still has to cap it. */
if ((uintptr_t)data > (uintptr_t)load) {
gap = (uintptr_t)data - (uintptr_t)load;
if (gap < (uintptr_t)out_max) {
out_max = (uint32_t)gap;
}
}
if ((uintptr_t)wolfboot_start_text > (uintptr_t)load) {
gap = (uintptr_t)wolfboot_start_text - (uintptr_t)load;
if (gap < (uintptr_t)out_max) {
out_max = (uint32_t)gap;
}
}
else if (wolfboot_start_text == NULL) {
/* Linker script does not export _start_text: the bootloader-image
* bound cannot be applied. Say so rather than skipping it silently. */
wolfBoot_printf("FIT: no _start_text, load not bounded by the "
"wolfBoot image\n");
}
return out_max;
}
/* Inner implementation shared by fit_load_image_ex and fit_load_image_to.
* When dst_override is non-NULL it replaces the FIT image's `load`
* property as the destination, so a compressed (gzip) payload is
@ -1855,35 +1938,10 @@ static void* fit_load_image_inner(fdt_ctx* ctx, const char* image, int* lenp,
if (is_gzip) {
#ifdef WOLFBOOT_GZIP
uint32_t out_len = 0;
uint32_t gz_max = out_max;
uintptr_t gap;
/* out_max is only a sanity ceiling; the inflate window
* must also stop below anything that has to survive it. */
uint32_t gz_max = fit_dest_bound(ctx, load, data, out_max);
int rc;
/* out_max is only a sanity ceiling. Cap the output below
* anything above the destination that must survive the
* inflate: the staged compressed input, and wolfBoot's own
* image (a corrupted stream can emit garbage at full match
* speed, and without this cap the window reaches straight
* through the bootloader before the decoder trips on an
* invalid code). Conservative lower bounds. */
if ((uintptr_t)data > (uintptr_t)load) {
gap = (uintptr_t)data - (uintptr_t)load;
if (gap < (uintptr_t)gz_max) {
gz_max = (uint32_t)gap;
}
}
if ((uintptr_t)wolfboot_start_text > (uintptr_t)load) {
gap = (uintptr_t)wolfboot_start_text - (uintptr_t)load;
if (gap < (uintptr_t)gz_max) {
gz_max = (uint32_t)gap;
}
}
else if (wolfboot_start_text == NULL) {
/* Linker script does not export _start_text: the
* bootloader-image bound cannot be applied. Say so
* rather than skipping it silently. */
wolfBoot_printf("gzip: no _start_text, output not "
"bounded by the wolfBoot image\n");
}
if (wolfBoot_fit_check_dest(load, gz_max) != 0) {
wolfBoot_printf("FIT: %s decompression destination %p "
"rejected\n", image, load);
@ -1921,9 +1979,14 @@ static void* fit_load_image_inner(fdt_ctx* ctx, const char* image, int* lenp,
return NULL;
}
else {
if (len < 0 || (uint32_t)len > out_max) {
/* Same bound as the inflate above: a raw subimage too
* large for the space below the staged FIT or below
* wolfBoot itself would be copied over them. */
uint32_t cp_max = fit_dest_bound(ctx, load, data, out_max);
if (len < 0 || (uint32_t)len > cp_max) {
wolfBoot_printf("FIT: subimage '%s' size %d exceeds "
"staging bound %u\n", image, len, out_max);
"staging bound %u\n", image, len, cp_max);
return NULL;
}
wolfBoot_printf("Loading Image %s: %p -> %p "
@ -1959,7 +2022,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;
}

View File

@ -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 %p\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
@ -1179,7 +1184,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);
@ -1230,20 +1235,16 @@ void RAMFUNCTION wolfBoot_start(void)
wolfBoot_panic();
}
}
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);
}
}
#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;
}
(void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd);
}
fit_ramdisk = ramdisk;
#else
(void)ramdisk;
#endif
@ -1259,7 +1260,7 @@ void RAMFUNCTION wolfBoot_start(void)
}
#endif
wolfBoot_printf("Booting at %p\r\n", load_address);
wolfBoot_printf("Booting at %p\r\n", (void*)load_address);
#ifdef WOLFBOOT_ENABLE_WOLFHSM_CLIENT
(void)hal_hsm_disconnect();
@ -1292,6 +1293,55 @@ 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 firmware DTB, forwarded unauthenticated: no
* digest check here, and only a mapped HAL pointer is usable. */
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;
}
else if (dts_addr != NULL) {
wolfBoot_printf("FIT: DTB at %p is not usable for the initrd "
"fixup\n", dts_addr);
}
if (fit_load_ramdisk(&fit_ctx, fit_ramdisk, dts_for_initrd) != 0) {
wolfBoot_printf("FIT: booting without an initrd\n");
}
}
#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. */

View File

@ -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 @@ backup_on_failure:
(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 @@ backup_on_failure:
fdt_set_dtb_authenticated(1);
memcpy(dts_addr, dts_ptr, dts_size);
}
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);
}
}
#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;
}
(void)fit_load_ramdisk(fit, ramdisk, dts_for_initrd);
}
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). */
@ -717,6 +720,11 @@ backup_on_failure:
wolfBoot_printf("DTB parse/size check failed - ignoring\n");
dts_addr = NULL; /* never forward an unvalidated address */
}
else if ((uintptr_t)dts_addr ==
(uintptr_t)WOLFBOOT_LOAD_DTS_ADDRESS) {
/* Usual case: the HAL returns the staging address. */
dts_size = (uint32_t)ret;
}
else {
dts_size = (uint32_t)ret;
memcpy((void*)WOLFBOOT_LOAD_DTS_ADDRESS, dts_addr, dts_size);
@ -786,6 +794,27 @@ backup_on_failure:
}
}
}
#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;
}
else if (dts_addr != NULL) {
wolfBoot_printf("FIT: DTB at %p is not usable for the initrd "
"fixup\n", dts_addr);
}
if (fit_load_ramdisk(&fit_ctx, fit_ramdisk, dts_for_initrd) != 0) {
wolfBoot_printf("FIT: booting without an initrd\n");
}
}
#endif
#endif /* MMU */
#ifdef WOLFBOOT_UBOOT_LEGACY

View File

@ -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
# Hardware watchdog, off by default. Defaulted globally (outside the CI ifeq
# block above) so any target can enable it without editing its config.
@ -130,6 +131,9 @@ 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 \
BOOT_EL1 BOOT_EL2 \
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\

View File

@ -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-flash-enc-intswap 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-flash-enc-intswap 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 \
@ -154,6 +154,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
@ -365,6 +366,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 \
@ -463,9 +471,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).
@ -494,6 +504,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)
@ -1002,6 +1019,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
@ -1012,7 +1030,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).
@ -1114,6 +1132,11 @@ unit-update-flash-enc-intswap: ../../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)
@ -1184,6 +1207,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)

View File

@ -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,53 @@ 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 two-cell value that does fit must still decode: #address-cells = <2>
* is what mkimage emits on a 64-bit host even for a low address, so
* rejecting these would break every such FIT on a 32-bit target. */
val[0] = 0x00; val[1] = 0x00; val[2] = 0x00; 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);
ck_assert_ptr_eq(fdt_getprop_address(&ctx, off, "load"),
(void*)(uintptr_t)0x08000000UL);
/* 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 +1570,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);

View File

@ -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,122 @@ 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_initrd_without_a_dtb_reports_failure)
{
/* Staging the ramdisk but silently skipping the fixup would leave the
* kernel with no initrd and nothing on the console to say so. */
static uint8_t rd_dst[64 * 1024] __attribute__((aligned(16)));
static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4)));
memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch));
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", NULL), -1);
ramdisk_override_addr = 0;
/* The ramdisk itself still landed; only the fixup was impossible. */
ck_assert_int_eq(memcmp(rd_dst, fit_plain_payload, FIT_PLAIN_LEN), 0);
}
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)
@ -356,6 +490,89 @@ START_TEST(test_fit_to_none_oversized_rejected)
}
END_TEST
/* fit_load_kernel()'s override path with an uncompressed kernel: the only
* shape a build without WOLFBOOT_GZIP can stage, and the one a FIT that
* declares neither `load` nor `entry` most often carries. */
START_TEST(test_fit_kernel_override_loads_uncompressed)
{
static uint8_t dst[64 * 1024] __attribute__((aligned(16)));
static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4)));
int len = -1;
void *ret;
memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch));
memset(dst, 0, FIT_PLAIN_LEN + 1);
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
/* The raw copy stops below the staged FIT just as the inflate does: an
* override under the FIT's own `data` would otherwise overwrite the blob
* that the later `fdt` and `ramdisk` lookups still read from. */
START_TEST(test_fit_kernel_override_into_the_fit_rejected)
{
static uint8_t fit_scratch[sizeof(fit_with_none_comp)] __attribute__((aligned(4)));
uint8_t *payload = NULL;
int len = -1;
void *ret;
unsigned i;
memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch));
for (i = 0; i + FIT_PLAIN_LEN <= sizeof(fit_scratch); i++) {
if (memcmp(fit_scratch + i, fit_plain_payload, FIT_PLAIN_LEN) == 0) {
payload = fit_scratch + i;
break;
}
}
ck_assert_ptr_nonnull(payload);
/* 8 bytes of headroom for a 23-byte payload. */
kernel_override_addr = (uintptr_t)(payload - 8);
ret = fit_load_kernel(fit_open(fit_scratch, (uint32_t)sizeof(fit_scratch)),
"kernel-1", &len);
kernel_override_addr = 0;
ck_assert_ptr_null(ret);
ck_assert_int_eq(memcmp(fit_scratch, fit_with_none_comp,
sizeof(fit_scratch)), 0);
}
END_TEST
/* The bound is the start of the blob, not its `data` pointer: a destination
* below the FIT would otherwise be allowed to run over the header and
* structure block while the `fdt` and `ramdisk` lookups still need them. */
START_TEST(test_fit_kernel_override_under_the_fit_rejected)
{
/* The FIT is placed inside a larger arena so there is addressable
* space below the blob to aim the override at. */
static uint8_t arena[256 + sizeof(fit_with_none_comp)] __attribute__((aligned(4)));
uint8_t *blob = arena + 200;
int len = -1;
void *ret;
memset(arena, 0, sizeof(arena));
memcpy(blob, fit_with_none_comp, sizeof(fit_with_none_comp));
/* 8 bytes of headroom for a 23-byte payload. */
kernel_override_addr = (uintptr_t)(blob - 8);
ret = fit_load_kernel(fit_open(blob, (uint32_t)sizeof(fit_with_none_comp)),
"kernel-1", &len);
kernel_override_addr = 0;
ck_assert_ptr_null(ret);
ck_assert_int_eq(memcmp(blob, fit_with_none_comp,
sizeof(fit_with_none_comp)), 0);
}
END_TEST
/* ------------------------------------------------------------------------- */
static Suite *fit_gzip_suite(void)
@ -368,12 +585,20 @@ 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);
tcase_add_test(tc, test_fit_initrd_without_a_dtb_reports_failure);
#else
tcase_add_test(tc, test_fit_to_gzip_disabled_returns_null);
#endif
tcase_add_test(tc, test_fit_to_lzma_unknown_returns_null);
tcase_add_test(tc, test_fit_to_none_unterminated_fails_closed);
tcase_add_test(tc, test_fit_to_none_oversized_rejected);
tcase_add_test(tc, test_fit_kernel_override_loads_uncompressed);
tcase_add_test(tc, test_fit_kernel_override_into_the_fit_rejected);
tcase_add_test(tc, test_fit_kernel_override_under_the_fit_rejected);
suite_add_tcase(s, tc);
return s;

View File

@ -78,9 +78,29 @@ 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_ret;
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;
/* A blob fdt_open() refuses, so a tree that passes the size check but will
* not open for the initrd fixup can be reached. */
static const void *mock_fdt_open_fail_blob;
/* 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 +141,22 @@ 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_ret = 0;
mock_ramdisk_saw_flash_protect = 0;
mock_ramdisk_dts = NULL;
memset(configured_dts, 0xCD, sizeof(configured_dts));
mock_configured_dts = configured_dts;
mock_fdt_open_fail_blob = NULL;
memset(panic_key_snapshot, 0xFF, sizeof(panic_key_snapshot));
memset(panic_nonce_snapshot, 0xFF, sizeof(panic_nonce_snapshot));
wolfBoot_panicked = 0;
@ -253,6 +284,9 @@ void fdt_set_dtb_authenticated(int authenticated)
* we only need update_disk.c to take the FIT branch. */
int fdt_open(fdt_ctx* ctx, void* blob, uint32_t capacity)
{
if (mock_fdt_open_fail_blob != NULL && blob == mock_fdt_open_fail_blob) {
return -1;
}
memset(ctx, 0, sizeof(*ctx));
ctx->blob = (uint8_t*)blob;
ctx->capacity = capacity;
@ -272,9 +306,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 +333,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,18 +355,43 @@ 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 mock_ramdisk_ret;
}
#endif
#include "update_disk.c"
/* Runs from inside wolfBoot_panic(), before it spins forever on target. */
@ -436,6 +502,173 @@ 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
/* A ramdisk that cannot be staged, or that has no tree to be announced in,
* is logged and the kernel boots anyway rather than the boot aborting. */
START_TEST(test_update_disk_fit_ramdisk_failure_still_boots)
{
reset_mocks();
mock_flat_dt = NULL;
mock_ramdisk = "ramdisk-1";
mock_ramdisk_ret = -1;
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_int_eq(mock_ramdisk_calls, 1);
}
END_TEST
#endif /* WOLFBOOT_FIT_RAMDISK */
#ifdef MMU
/* A configured DTB that cannot be relocated is dropped and the kernel is
* entered with no tree, rather than the boot aborting. */
START_TEST(test_update_disk_fit_configured_dtb_relocation_failure)
{
reset_mocks();
mock_flat_dt = NULL; /* kernel-only FIT: reach the fallback */
mock_fit_memcpy_ret = -1;
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_ptr_null(mock_do_boot_dts);
}
END_TEST
/* 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
/* No DTB from anywhere: the HAL offers none and the FIT carried none. The
* kernel is entered with a NULL tree instead of the boot aborting. */
START_TEST(test_update_disk_fit_no_dtb_at_all)
{
reset_mocks();
mock_flat_dt = NULL; /* kernel-only FIT */
mock_configured_dts = NULL; /* and no firmware DTB either */
#ifdef WOLFBOOT_FIT_RAMDISK
mock_ramdisk = "ramdisk-1";
#endif
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_ptr_null(mock_do_boot_dts);
#ifdef WOLFBOOT_FIT_RAMDISK
/* The fixup is still attempted, with nothing to write into. */
ck_assert_int_eq(mock_ramdisk_calls, 1);
ck_assert_ptr_null(mock_ramdisk_dts);
#endif
}
END_TEST
#ifdef WOLFBOOT_FIT_RAMDISK
/* A firmware DTB that passes the size check but will not open must not be
* handed to fit_load_ramdisk() as if it were a tree. The kernel still gets
* the tree itself, only without the initrd properties. */
START_TEST(test_update_disk_fit_dtb_that_does_not_open)
{
reset_mocks();
mock_flat_dt = NULL; /* kernel-only FIT: reach the fallback */
mock_ramdisk = "ramdisk-1";
mock_fdt_open_fail_blob = dts_buffer;
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_do_boot_called, 1);
ck_assert_ptr_eq(mock_do_boot_dts, (void*)dts_buffer);
ck_assert_int_eq(mock_ramdisk_calls, 1);
ck_assert_ptr_null(mock_ramdisk_dts);
}
END_TEST
#endif /* WOLFBOOT_FIT_RAMDISK */
/* 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 +677,21 @@ 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);
tcase_add_test(tc, test_update_disk_fit_configured_dtb_relocation_failure);
tcase_add_test(tc, test_update_disk_fit_no_dtb_at_all);
#ifdef WOLFBOOT_FIT_RAMDISK
tcase_add_test(tc, test_update_disk_fit_dtb_that_does_not_open);
#endif
#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);
tcase_add_test(tc, test_update_disk_fit_ramdisk_failure_still_boots);
#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);

View File

@ -0,0 +1,467 @@
/* 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 <stdio.h>
#include <stdlib.h>
#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_ret;
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)
/* What the HAL hands back, and a blob fdt_open() refuses, so the "present
* but unusable" branch can be reached without failing the size check too. */
static void *mock_hal_dts;
static const void *mock_fdt_open_fail_blob;
void* hal_get_dts_address(void)
{
mock_get_dts_calls++;
return mock_hal_dts;
}
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)
{
if (mock_fdt_open_fail_blob != NULL && blob == mock_fdt_open_fail_blob) {
return -1;
}
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 mock_ramdisk_ret;
}
#include "update_ram.c"
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <check.h>
#include "unit-mock-flash.c"
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/sha256.h>
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_ret = 0;
mock_ramdisk_saw_get_dts = 0;
mock_ramdisk_dts = NULL;
mock_hal_dts = firmware_dtb;
mock_fdt_open_fail_blob = 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
/* A ramdisk that cannot be staged, or that has no tree to be announced in,
* is logged and the kernel boots anyway rather than the boot aborting. */
START_TEST (test_ram_fit_ramdisk_failure_still_boots)
{
reset_fit_mocks();
mock_ramdisk_ret = -1;
prepare_flash();
add_payload(PART_BOOT, 1, TEST_SIZE_SMALL);
wolfBoot_start();
ck_assert_int_eq(wolfBoot_panicked, 0);
ck_assert_int_eq(mock_ramdisk_calls, 1);
cleanup_flash();
}
END_TEST
/* A firmware DTB that passes the size check but will not open for the fixup
* must not be handed to fit_load_ramdisk() as if it were a tree. */
START_TEST (test_ram_fit_firmware_dtb_that_does_not_open)
{
reset_fit_mocks();
/* The size check runs on the HAL address; only the staged copy the
* fixup opens is refused. */
mock_fdt_open_fail_blob = dts_stage;
prepare_flash();
add_payload(PART_BOOT, 1, TEST_SIZE_SMALL);
wolfBoot_start();
ck_assert_int_eq(mock_ramdisk_calls, 1);
ck_assert_ptr_null(mock_ramdisk_dts);
ck_assert_int_eq(wolfBoot_panicked, 0);
cleanup_flash();
}
END_TEST
/* A HAL that already returns the staging address is used in place: nothing
* is copied over it, and the fixup still gets that same tree. */
START_TEST (test_ram_fit_hal_returns_the_staging_address)
{
reset_fit_mocks();
mock_hal_dts = dts_stage;
memset(firmware_dtb, 0x5A, sizeof(firmware_dtb));
memset(dts_stage, 0xA5, sizeof(dts_stage));
prepare_flash();
add_payload(PART_BOOT, 1, TEST_SIZE_SMALL);
wolfBoot_start();
/* Not overwritten from the HAL buffer. */
ck_assert_uint_eq(dts_stage[0], 0xA5);
ck_assert_int_eq(mock_ramdisk_calls, 1);
ck_assert_ptr_nonnull(mock_ramdisk_dts);
ck_assert_int_eq(wolfBoot_panicked, 0);
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);
tcase_add_test(tc, test_ram_fit_ramdisk_failure_still_boots);
tcase_add_test(tc, test_ram_fit_firmware_dtb_that_does_not_open);
tcase_add_test(tc, test_ram_fit_hal_returns_the_staging_address);
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;
}