Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/test-configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,33 @@ jobs:
config-file: ./config/examples/imx95-m7.config
make-args: wolfboot.bin test-app/image_v1_signed.bin

imx95_a55_test:
uses: ./.github/workflows/test-build.yml
with:
arch: aarch64
# i.MX95 Cortex-A55 as BL33. The payload is a Linux FIT on the boot
# medium, so there is no test app and no contiguous factory.bin.
config-file: ./config/examples/imx95-a55.config
make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu-

imx95_a55_stage1_emmc_test:
uses: ./.github/workflows/test-build.yml
with:
arch: aarch64
# The stage 1 that replaces U-Boot SPL, reading the containers from the
# eMMC boot partition. Nothing else compiles the AHAB parser or the
# OCRAM link, so this keeps both from rotting.
config-file: ./config/examples/imx95-a55.config
make-args: -C stage1 DISK_EMMC=1 DISK_SDCARD=0 loader_stage1.bin CROSS_COMPILE=aarch64-linux-gnu-

imx95_a55_stage1_sd_test:
uses: ./.github/workflows/test-build.yml
with:
arch: aarch64
# Same stage 1 against the SD controller rather than the eMMC one.
config-file: ./config/examples/imx95-a55.config
make-args: -C stage1 DISK_SDCARD=1 DISK_EMMC=0 loader_stage1.bin CROSS_COMPILE=aarch64-linux-gnu-

tegra234_test:
uses: ./.github/workflows/test-build.yml
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ tools/unit-tests/unit-va416x0-fram
tools/unit-tests/unit-wolfhsm_flash_hal
tools/unit-tests/unit-ubootenv
tools/unit-tests/unit-cm4-sdhci
tools/unit-tests/unit-imx95-ahab
tools/unit-tests/unit-cm4-rauc-slot
tools/unit-tests/__pycache__/*

Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ ifeq ($(TARGET),tegra234)
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
endif

# i.MX95 A55 is a RAM-resident BL33 loaded by SPL inside an AHAB container, so
# there is no contiguous flash image to assemble into a factory.bin.
ifeq ($(TARGET),imx95_a55)
MAIN_TARGET:=wolfboot.bin
endif

ifeq ($(TARGET),cm4)
MAIN_TARGET:=wolfboot.bin
endif
Expand Down
57 changes: 56 additions & 1 deletion arch.mk
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,61 @@ ifeq ($(ARCH),AARCH64)
# BOOT_EL1 itself is emitted by options.mk; nothing to add here.
endif

ifeq ($(TARGET),imx95_a55)
# NXP i.MX95 Cortex-A55 cluster, running as BL33 in AHAB container 2.
# -mstrict-align because this stage runs with the MMU off, so every access
# is Device-nGnRnE and unaligned ones fault.
ARCH_FLAGS=-mcpu=cortex-a55+crypto -march=armv8.2-a+crypto -mstrict-align
CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A55
LDFLAGS+=$(ARCH_FLAGS)
# BL31 loads and enters this image here; must match ORIGIN in
# hal/imx95_a55.ld and IMX95_BL33_BASE in hal/imx95_a55.h.
WOLFBOOT_ORIGIN=0x90200000
# LPUART1 console, shared with the stage 1 build.
OBJS+=hal/imx95_lpuart.o
# Bring-up aid: print the entry EL, SCTLR and handoff x0 that BL31 passed.
ifeq ($(IMX95_HANDOFF_DUMP),1)
CFLAGS+=-DIMX95_HANDOFF_DUMP
endif
# i.MX95 is GICv3 and BL31 already configured it. EL2_HYPERVISOR is not a
# generic options.mk variable, so it is emitted here as tegra234 does.
ifeq ($(EL2_HYPERVISOR),1)
CFLAGS+=-DEL2_HYPERVISOR=1
endif
# i.MX uSDHC is not SDHCI-compatible: skip src/sdhci.c, use our driver.
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
DISK_DRIVER=usdhc
OBJS+=hal/imx95_usdhc.o
# SCMI cold-init of uSDHC2 (clock+pinmux) when no prior stage did it.
ifeq ($(IMX95_SCMI_COLD_INIT),1)
CFLAGS+=-DIMX95_SCMI_COLD_INIT
endif
endif
# SCMI M7 power-on + TCM ECC scrub (U-Boot's power_on_m7 equivalent) for a
# Cortex-M7 that Linux launches later. Independent of the disk driver.
ifeq ($(IMX95_INIT_M7),1)
CFLAGS+=-DIMX95_INIT_M7
endif
# imx95_scmi.o carries the SCMI-over-MU client used by both of the above.
ifneq ($(filter 1,$(IMX95_SCMI_COLD_INIT) $(IMX95_INIT_M7)),)
OBJS+=hal/imx95_scmi.o
endif
# One-shot diagnostic: read the AHAB container out of the eMMC boot
# partitions and report it, from inside an image that boots from SD.
ifeq ($(IMX95_EMMC_PROBE),1)
CFLAGS+=-DIMX95_EMMC_PROBE
OBJS+=hal/imx95_ahab.o
endif
# Mirror the console into a DDR ring that survives the handoff to Linux.
ifeq ($(IMX95_LOG_RING),1)
CFLAGS+=-DIMX95_LOG_RING
endif
# Verbose per-step trace of the SCMI cold-init and SD bring-up.
ifeq ($(IMX95_SCMI_DEBUG),1)
CFLAGS+=-DIMX95_SCMI_DEBUG
endif
endif

ifeq ($(TARGET),cm4)
# Raspberry Pi Compute Module 4 - Broadcom BCM2711, Cortex-A72
ARCH_FLAGS=-mcpu=cortex-a72+crypto -march=armv8-a+crypto -mtune=cortex-a72
Expand All @@ -257,7 +312,7 @@ ifeq ($(ARCH),AARCH64)
# asm (sp_arm64.c / WOLFSSL_SP_ARM64_ASM); that is enabled independently on
# __aarch64__ in include/user_settings.h, so a non-FIPS cm4 build still links
# sp_arm64 asm.
ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),)
ifeq ($(filter zynq versal nxp_ls1028a imx95_a55,$(TARGET)),)
NO_ARM_ASM?=1
endif

Expand Down
92 changes: 92 additions & 0 deletions config/examples/imx95-a55.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# NXP i.MX95 Cortex-A55 - wolfBoot as BL33 (replaces U-Boot proper):
# ROM -> ELE -> SM -> OEI -> SPL [c1] -> BL31 -> OP-TEE -> wolfBoot -> Linux
# wolfBoot takes U-Boot's slot in AHAB container 2 at 0x90200000; the other
# container images are the vendor's, reused verbatim.

ARCH?=AARCH64
TARGET?=imx95_a55

# ML-DSA-87, matching the imx95_m7 port. Header fits sig (4627) + pubkey.
SIGN?=ML_DSA
ML_DSA_LEVEL?=5
HASH?=SHA256
# Required: unset, ML_DSA_IMAGE_SIGNATURE_SIZE expands empty -> image.c errors.
IMAGE_SIGNATURE_SIZE?=4627
IMAGE_HEADER_SIZE?=12288

DEBUG?=0
DEBUG_SYMBOLS=1
DEBUG_UART?=1
# Print entry EL, SCTLR, MPIDR, CNTFRQ and the handoff x0 pointer.
IMX95_HANDOFF_DUMP?=1
# Report how long each boot step takes, in ms, from the generic timer.
BOOT_BENCHMARK?=1
# Also mirror the console into a DDR ring, so the verified-boot log can be read
# from Linux (and shown on a display) long after the UART output has scrolled.
IMX95_LOG_RING?=1

V?=0
SPMATH?=1
NO_ASM?=0

# RAM-resident: BL31 loads and enters this image, so there is nothing to copy
# from a stored .data and no XIP flash behind it.
NO_XIP?=1

# GICv3, configured by BL31; gicv2_init_secure() would abort on an unmapped
# ZynqMP address.
SKIP_GIC_INIT?=1

# Stay at EL2 (Linux wants it for KVM). Do NOT set BOOT_EL1: el2_to_el1_boot
# skips cache/MMU cleanup and would hand Linux stale DRAM.
EL2_HYPERVISOR=1

# No flash: boot/update addresses come from the HAL.
WOLFBOOT_NO_PARTITIONS=1

# Must match ORIGIN in hal/imx95_a55.ld and IMX95_BL33_BASE in hal/imx95_a55.h.
WOLFBOOT_ORIGIN=0x90200000

# RAM staging at 0xA1000000: clear of the M7/BL31/OP-TEE/ELE/vpu carveouts;
# hal_init() asserts this via imx95_check_load_ranges().
WOLFBOOT_LOAD_ADDRESS?=0xA1000000
WOLFBOOT_LOAD_DTS_ADDRESS?=0xA0800000

# Bounds the image before authentication; required without fixed partitions.
WOLFBOOT_RAMBOOT_MAX_SIZE=0x10000000

# Required for the test-app link even with WOLFBOOT_NO_PARTITIONS=1.
WOLFBOOT_SECTOR_SIZE?=0x4000
WOLFBOOT_PARTITION_SIZE?=0x4000000
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0xA1000000

# Toolchain
USE_GCC=1
CROSS_COMPILE?=aarch64-none-elf-

# Storage: carrier SD on uSDHC2 (hal/imx95_usdhc.c; i.MX uSDHC, not SDHCI).
# Boot containers stay on eMMC.
DISK_SDCARD?=1
# Cold start: with no U-Boot ahead of wolfBoot the uSDHC2 clock and pinmux are
# still owned by the System Manager and must be requested over SCMI.
IMX95_SCMI_COLD_INIT?=1
# Power the Cortex-M7 mix and scrub its TCM, as U-Boot's board init does, for
# boards that launch an M7 image from Linux.
IMX95_INIT_M7?=1
# A/B slots by 0-based index into the parsed partition table.
CFLAGS_EXTRA+=-DBOOT_PART_A=1 -DBOOT_PART_B=2
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x10000

# Linux FIT boot: kernel at 0xB2000000, ramdisk at 0xBA000000 - both above
# the staging window so the copies cannot overlap the staged FIT.
FIT_RAMDISK?=1
WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0xBA000000
# OS cmdline plus serial console; the ostree= path pins the deployment.
# audit=0: the audit subsystem writes multi-line records to this console
# during late boot, interleaving with anything else printing there. Nothing
# in this configuration consumes them.
CFLAGS_EXTRA+=-DLINUX_BOOTARGS='"earlycon console=ttyLP1,115200 audit=0 root=LABEL=otaroot rootfstype=ext4 logo.nologo ostree=/ostree/boot.1/torizon/d976a72bdc954405cfbe989bae393ad9da0b14b5dc0c1328c9b19e88201745a6/0"'

# --- Later phases (not yet implemented) -------------------------------------
#ELF?=1
#GZIP?=1
28 changes: 28 additions & 0 deletions docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This README describes configuration of supported targets.
* [Nordic nRF5340](#nordic-nrf5340)
* [Nordic nRF54L15](#nordic-nrf54l15)
* [NXP i.MX95 Cortex-M7](#nxp-imx95-cortex-m7)
* [NXP i.MX95 Cortex-A55 (BL33)](#nxp-imx95-cortex-a55-bl33)
* [NXP iMX-RT](#nxp-imx-rt)
* [NXP Kinetis](#nxp-kinetis)
* [NXP Kinetis KL26Z](#nxp-kinetis-kl26z)
Expand Down Expand Up @@ -9447,6 +9448,33 @@ The difference between the two timestamps is the cost of everything wolfBoot doe

Both caches are enabled by `hal_init()`, which matters because verifying an image means hashing megabytes resident in DDR. The ARMv7-M default memory map marks `0x80000000-0x9FFFFFFF` as Normal write-through, so no MPU region is needed and M7 stores to the shared window still reach DDR; the HAL nevertheless cleans the affected lines explicitly so that behaviour is not left depending on an inherited attribute.

## NXP i.MX95 Cortex-A55 (BL33)

wolfBoot replaces U-Boot proper on the i.MX95's Cortex-A55 cluster: it is the third image in AHAB container 2 (after BL31 and OP-TEE), entered by BL31 at `0x90200000` in NS-EL2, where it verifies a Linux FIT (kernel + DTB + initramfs) with ML-DSA-87 and boots it at EL2. SPL/ELE authenticate the container itself (AHAB), so the chain is ROM -> ELE -> SPL -> BL31 -> wolfBoot -> Linux once SRK fuses are programmed. NXP also documents an OEM PQC SRK hybrid (ML-DSA) AHAB flow, so the container half of the chain can be post-quantum as well.

Validated on a Toradex SMARC iMX95 with `TARGET=imx95_a55` (`config/examples/imx95-a55.config`): full boot to Linux userspace with the FIT on the carrier SD (uSDHC2, `hal/imx95_usdhc.c` - i.MX uSDHC, not SDHCI-compatible).

Notes:
- The FIT DTB gets `/chosen` bootargs, initrd properties and a `/memory` node from `hal_dts_fixup()`; deployment DTBs commonly ship without `/memory` (the bootloader is expected to add it) and the kernel hangs silently without one.
- The EL2 exit is a fused asm routine (flush, DAIF mask, TLB invalidate, jump) that touches no memory after the D-cache goes off; set/way cleaning does not reach the A55 cluster's DSU system cache, so payload ranges are also cleaned by VA.
- Use plain `earlycon` (DTB-derived); an explicit `earlycon=lpuart32,mmio32,<addr>` uses the wrong register layout on i.MX and silences all console output.
- uSDHC2's clock, pinmux and card power are owned by the M33 System Manager, and its pad registers data-abort on direct access from BL33. `IMX95_SCMI_COLD_INIT=1` brings them up over SCMI (`hal/imx95_scmi.c`), which is what lets wolfBoot boot from SD on a cold power-on rather than only after a stage that already initialized the controller.
- `IMX95_INIT_M7=1` powers the Cortex-M7 mix and scrubs its TCM for ECC, matching what U-Boot's board init does, for boards that launch an M7 image from Linux later.

### Stage 1: replacing U-Boot SPL

The boot device holds a set of AHAB containers. The first one is what the boot ROM reads: it carries the ELE firmware, the M33 System Manager, the OEI that trains DDR, and one A55 image loaded into OCRAM at `0x20480000`. That last image is U-Boot SPL, and its whole job is to find the next container and load BL31, OP-TEE and BL33 out of it. wolfBoot can take that slot instead:

```
cp config/examples/imx95-a55.config .config
make # BL33, loaded into DRAM by BL31
make stage1 DISK_EMMC=1 # stage1/loader_stage1.bin, the SPL slot
```

DDR is up before stage 1 runs, because the OEI did it, so stage 1 only needs the boot device and the container walk. Nothing records where the next container starts: it is derived by taking the end of the first container - the furthest of its header, its images and its signature block - and rounding up to 1 KiB. `hal/imx95_ahab.c` is that walk, and `hal/imx95_a55_stage1.c` is the rest: the watchdog, GPIO and SMMU state a warm reset out of Linux leaves behind, the SCMI calls for the console clock and the A55 performance level, and the ELE call that starts its random generator.

Two things differ from the BL33 build. The console is programmed rather than inherited, since nothing has configured LPUART1 yet. And on eMMC the containers live in a boot partition, not the user area: `imx95_emmc_boot_partition()` reads back the same `PARTITION_CONFIG` field that told the ROM which one to load from, and reads follow it.

## TI C2000 C28x (LAUNCHXL-F28P55X)

wolfBoot runs on the Texas Instruments C2000 C28x DSP (TMS320F28P550SJ, 150 MHz) as a secure execute-in-place (XIP) bootloader. The C28x is word-addressed with `CHAR_BIT == 16` (no 8-bit type -- each octet occupies one 16-bit cell), built with the TI `cl2000` toolchain against wolfSSL's wide-byte (`CHAR_BIT != 8`) support.
Expand Down
Loading
Loading