Skip to content

Commit 30f6cee

Browse files
committed
Add wolfBoot target for NXP i.MX95 Cortex-A55 as BL33 booting a Linux FIT from SD
1 parent 7a2ed47 commit 30f6cee

12 files changed

Lines changed: 2120 additions & 13 deletions

File tree

‎Makefile‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ ifeq ($(TARGET),tegra234)
408408
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
409409
endif
410410

411+
# i.MX95 A55 is a RAM-resident BL33 loaded by SPL inside an AHAB container, so
412+
# there is no contiguous flash image to assemble into a factory.bin.
413+
ifeq ($(TARGET),imx95_a55)
414+
MAIN_TARGET:=wolfboot.bin
415+
endif
416+
411417
ifeq ($(TARGET),cm4)
412418
MAIN_TARGET:=wolfboot.bin
413419
endif

‎arch.mk‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,53 @@ ifeq ($(ARCH),AARCH64)
231231
# BOOT_EL1 itself is emitted by options.mk; nothing to add here.
232232
endif
233233

234+
ifeq ($(TARGET),imx95_a55)
235+
# NXP i.MX95 Cortex-A55 cluster, running as BL33 in AHAB container 2.
236+
# -mstrict-align because this stage runs with the MMU off, so every access
237+
# is Device-nGnRnE and unaligned ones fault.
238+
ARCH_FLAGS=-mcpu=cortex-a55+crypto -march=armv8.2-a+crypto -mstrict-align
239+
CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A55
240+
LDFLAGS+=$(ARCH_FLAGS)
241+
# BL31 loads and enters this image here; must match ORIGIN in
242+
# hal/imx95_a55.ld and IMX95_BL33_BASE in hal/imx95_a55.h.
243+
WOLFBOOT_ORIGIN=0x90200000
244+
# Bring-up aid: print the entry EL, SCTLR and handoff x0 that BL31 passed.
245+
ifeq ($(IMX95_HANDOFF_DUMP),1)
246+
CFLAGS+=-DIMX95_HANDOFF_DUMP
247+
endif
248+
# i.MX95 is GICv3 and BL31 already configured it. EL2_HYPERVISOR is not a
249+
# generic options.mk variable, so it is emitted here as tegra234 does.
250+
ifeq ($(EL2_HYPERVISOR),1)
251+
CFLAGS+=-DEL2_HYPERVISOR=1
252+
endif
253+
# i.MX uSDHC is not SDHCI-compatible: skip src/sdhci.c, use our driver.
254+
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
255+
DISK_DRIVER=usdhc
256+
OBJS+=hal/imx95_usdhc.o
257+
# SCMI cold-init of uSDHC2 (clock+pinmux) when no prior stage did it.
258+
ifeq ($(IMX95_SCMI_COLD_INIT),1)
259+
CFLAGS+=-DIMX95_SCMI_COLD_INIT
260+
endif
261+
endif
262+
# SCMI M7 power-on + TCM ECC scrub (U-Boot's power_on_m7 equivalent) for a
263+
# Cortex-M7 that Linux launches later. Independent of the disk driver.
264+
ifeq ($(IMX95_INIT_M7),1)
265+
CFLAGS+=-DIMX95_INIT_M7
266+
endif
267+
# imx95_scmi.o carries the SCMI-over-MU client used by both of the above.
268+
ifneq ($(filter 1,$(IMX95_SCMI_COLD_INIT) $(IMX95_INIT_M7)),)
269+
OBJS+=hal/imx95_scmi.o
270+
endif
271+
# Mirror the console into a DDR ring that survives the handoff to Linux.
272+
ifeq ($(IMX95_LOG_RING),1)
273+
CFLAGS+=-DIMX95_LOG_RING
274+
endif
275+
# Verbose per-step trace of the SCMI cold-init and SD bring-up.
276+
ifeq ($(IMX95_SCMI_DEBUG),1)
277+
CFLAGS+=-DIMX95_SCMI_DEBUG
278+
endif
279+
endif
280+
234281
ifeq ($(TARGET),cm4)
235282
# Raspberry Pi Compute Module 4 - Broadcom BCM2711, Cortex-A72
236283
ARCH_FLAGS=-mcpu=cortex-a72+crypto -march=armv8-a+crypto -mtune=cortex-a72
@@ -257,7 +304,7 @@ ifeq ($(ARCH),AARCH64)
257304
# asm (sp_arm64.c / WOLFSSL_SP_ARM64_ASM); that is enabled independently on
258305
# __aarch64__ in include/user_settings.h, so a non-FIPS cm4 build still links
259306
# sp_arm64 asm.
260-
ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),)
307+
ifeq ($(filter zynq versal nxp_ls1028a imx95_a55,$(TARGET)),)
261308
NO_ARM_ASM?=1
262309
endif
263310

‎config/examples/imx95-a55.config‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# NXP i.MX95 Cortex-A55 - wolfBoot as BL33 (replaces U-Boot proper):
2+
# ROM -> ELE -> SM -> OEI -> SPL [c1] -> BL31 -> OP-TEE -> wolfBoot -> Linux
3+
# wolfBoot takes U-Boot's slot in AHAB container 2 at 0x90200000; the other
4+
# container images are the vendor's, reused verbatim.
5+
6+
ARCH?=AARCH64
7+
TARGET?=imx95_a55
8+
9+
# ML-DSA-87, matching the imx95_m7 port. Header fits sig (4627) + pubkey.
10+
SIGN?=ML_DSA
11+
ML_DSA_LEVEL?=5
12+
HASH?=SHA256
13+
# Required: unset, ML_DSA_IMAGE_SIGNATURE_SIZE expands empty -> image.c errors.
14+
IMAGE_SIGNATURE_SIZE?=4627
15+
IMAGE_HEADER_SIZE?=12288
16+
17+
DEBUG?=0
18+
DEBUG_SYMBOLS=1
19+
DEBUG_UART?=1
20+
# Print entry EL, SCTLR, MPIDR, CNTFRQ and the handoff x0 pointer.
21+
IMX95_HANDOFF_DUMP?=1
22+
# Report how long each boot step takes, in ms, from the generic timer.
23+
BOOT_BENCHMARK?=1
24+
# Also mirror the console into a DDR ring, so the verified-boot log can be read
25+
# from Linux (and shown on a display) long after the UART output has scrolled.
26+
IMX95_LOG_RING?=1
27+
28+
V?=0
29+
SPMATH?=1
30+
NO_ASM?=0
31+
32+
# RAM-resident: BL31 loads and enters this image, so there is nothing to copy
33+
# from a stored .data and no XIP flash behind it.
34+
NO_XIP?=1
35+
36+
# GICv3, configured by BL31; gicv2_init_secure() would abort on an unmapped
37+
# ZynqMP address.
38+
SKIP_GIC_INIT?=1
39+
40+
# Stay at EL2 (Linux wants it for KVM). Do NOT set BOOT_EL1: el2_to_el1_boot
41+
# skips cache/MMU cleanup and would hand Linux stale DRAM.
42+
EL2_HYPERVISOR=1
43+
44+
# No flash: boot/update addresses come from the HAL.
45+
WOLFBOOT_NO_PARTITIONS=1
46+
47+
# Must match ORIGIN in hal/imx95_a55.ld and IMX95_BL33_BASE in hal/imx95_a55.h.
48+
WOLFBOOT_ORIGIN=0x90200000
49+
50+
# RAM staging at 0xA1000000: clear of the M7/BL31/OP-TEE/ELE/vpu carveouts;
51+
# hal_init() asserts this via imx95_check_load_ranges().
52+
WOLFBOOT_LOAD_ADDRESS?=0xA1000000
53+
WOLFBOOT_LOAD_DTS_ADDRESS?=0xA0800000
54+
55+
# Bounds the image before authentication; required without fixed partitions.
56+
WOLFBOOT_RAMBOOT_MAX_SIZE=0x10000000
57+
58+
# Required for the test-app link even with WOLFBOOT_NO_PARTITIONS=1.
59+
WOLFBOOT_SECTOR_SIZE?=0x4000
60+
WOLFBOOT_PARTITION_SIZE?=0x4000000
61+
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0xA1000000
62+
63+
# Toolchain
64+
USE_GCC=1
65+
CROSS_COMPILE?=aarch64-none-elf-
66+
67+
# Storage: carrier SD on uSDHC2 (hal/imx95_usdhc.c; i.MX uSDHC, not SDHCI).
68+
# Boot containers stay on eMMC.
69+
DISK_SDCARD?=1
70+
# Cold start: with no U-Boot ahead of wolfBoot the uSDHC2 clock and pinmux are
71+
# still owned by the System Manager and must be requested over SCMI.
72+
IMX95_SCMI_COLD_INIT?=1
73+
# Power the Cortex-M7 mix and scrub its TCM, as U-Boot's board init does, for
74+
# boards that launch an M7 image from Linux.
75+
IMX95_INIT_M7?=1
76+
# A/B slots by 0-based index into the parsed partition table.
77+
CFLAGS_EXTRA+=-DBOOT_PART_A=1 -DBOOT_PART_B=2
78+
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x10000
79+
80+
# Linux FIT boot: kernel at 0xB2000000, ramdisk at 0xBA000000 - both above
81+
# the staging window so the copies cannot overlap the staged FIT.
82+
FIT_RAMDISK?=1
83+
WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0xBA000000
84+
# OS cmdline plus serial console; the ostree= path pins the deployment.
85+
# audit=0: the audit subsystem writes multi-line records to this console
86+
# during late boot, interleaving with anything else printing there. Nothing
87+
# in this configuration consumes them.
88+
CFLAGS_EXTRA+=-DLINUX_BOOTARGS='"earlycon console=ttyLP1,115200 audit=0 root=LABEL=otaroot rootfstype=ext4 logo.nologo ostree=/ostree/boot.1/torizon/d976a72bdc954405cfbe989bae393ad9da0b14b5dc0c1328c9b19e88201745a6/0"'
89+
90+
# --- Later phases (not yet implemented) -------------------------------------
91+
#ELF?=1
92+
#GZIP?=1

‎docs/Targets.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This README describes configuration of supported targets.
2222
* [Nordic nRF5340](#nordic-nrf5340)
2323
* [Nordic nRF54L15](#nordic-nrf54l15)
2424
* [NXP i.MX95 Cortex-M7](#nxp-imx95-cortex-m7)
25+
* [NXP i.MX95 Cortex-A55 (BL33)](#nxp-imx95-cortex-a55-bl33)
2526
* [NXP iMX-RT](#nxp-imx-rt)
2627
* [NXP Kinetis](#nxp-kinetis)
2728
* [NXP Kinetis KL26Z](#nxp-kinetis-kl26z)
@@ -9447,6 +9448,19 @@ The difference between the two timestamps is the cost of everything wolfBoot doe
94479448
94489449
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.
94499450
9451+
## NXP i.MX95 Cortex-A55 (BL33)
9452+
9453+
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.
9454+
9455+
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).
9456+
9457+
Notes:
9458+
- 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.
9459+
- 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.
9460+
- Use plain `earlycon` (DTB-derived); an explicit `earlycon=lpuart32,mmio32,<addr>` uses the wrong register layout on i.MX and silences all console output.
9461+
- 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.
9462+
- `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.
9463+
94509464
## TI C2000 C28x (LAUNCHXL-F28P55X)
94519465
94529466
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.

0 commit comments

Comments
 (0)