Skip to content

Add wolfBoot target for NXP i.MX95 Cortex-A55, including a stage 1 that replaces U-Boot SPL - #888

Open
dgarske wants to merge 7 commits into
wolfSSL:masterfrom
dgarske:imx95_a55_pr
Open

dgarske wants to merge 7 commits into
wolfSSL:masterfrom
dgarske:imx95_a55_pr

Conversation

@dgarske

@dgarske dgarske commented Sep 11, 2026

Copy link
Copy Markdown
Member

wolfBoot runs on the i.MX95 Cortex-A55 in two positions. As BL33 it is entered by BL31 in NS-EL2, verifies a Linux FIT (kernel, DTB, initramfs) with ML-DSA-87 and boots it at EL2. As a stage 1 it takes the place of U-Boot SPL in AHAB container 0, where the boot ROM loads it into OCRAM, and it brings up the boot device, walks the container set and loads BL31, OP-TEE and BL33. The EdgeLock Enclave (ELE) firmware, the M33 System Manager and the Optional Executable Image (OEI) that trains DDR are untouched and still the SoC's own.

What it adds

  • hal/imx95_a55.{c,h,ld} - HAL, memory map and linker script
  • hal/imx95_lpuart.c - LPUART1 console, shared by both positions
  • hal/imx95_usdhc.c - uSDHC driver for the carrier SD and the on-module eMMC including its boot partitions; the i.MX uSDHC is not SDHCI-register-compatible, so src/sdhci.c does not apply
  • hal/imx95_scmi.c - SCMI-over-MU client for the clocks, pinmux and power domains the System Manager owns
  • hal/imx95_ahab.{c,h} - AHAB container-set parser, with unit tests in tools/unit-tests/unit-imx95-ahab.c
  • hal/imx95_a55_stage1.{c,ld}, hal/imx95_a55_stage1_start.S - the stage 1 and its OCRAM entry
  • config/examples/imx95-a55.config and CI entries for the target and both stage-1 variants

Touches shared code

  • src/boot_aarch64_start.S - identity translation tables and an EL2 MMU enable for this target, so DRAM is mapped Normal cacheable and the wolfCrypt ARM assembly is usable; the carveout shared with the Cortex-M7 is mapped Normal Non-Cacheable because a core outside this cluster's coherency reads it
  • stage1/Makefile - build freestanding and without the C runtime startup files. Without -ffreestanding the compiler recognizes the byte-scan loop in src/string.c as the strlen idiom and rewrites it into a call to strlen itself, so strlen becomes a branch to itself
  • hal/imx95_m7.{c,h} - do not issue a cache clean by address while the D-cache is disabled. The Cortex-M7 performs maintenance by address regardless, and out of a cold reset the cache RAMs hold random tags and dirty bits, so a hit writes to an arbitrary address

Hardware / test status

Validated on a Toradex SMARC i.MX95 (LPUART1, 115200 8N1). Both positions boot cold to Torizon Linux userspace over repeated power cycles, with the FIT on the carrier SD and the boot containers in an eMMC boot partition. The uSDHC driver is also exercised against the state the boot ROM leaves behind, which differs from the state U-Boot leaves: the ROM reads the boot partition in the controller's fast-boot mode with HS400 tuning applied, and SYS_CTRL_RSTA clears none of it.

Scope

The driver is PIO; ADMA2 is follow-on work. Stage 1 does not itself authenticate the containers it loads - AHAB verification belongs to the ELE, which does it in hardware.

@dgarske dgarske self-assigned this Sep 11, 2026
Copilot AI lite review requested due to automatic review settings September 11, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate findings remain in uSDHC initialization and selection, FIT memory bounds, and DT fixups.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds an i.MX95 Cortex-A55 BL33 target that verifies signed Linux FIT images from SD and boots them at EL2.

Changes:

  • Adds i.MX95 HAL, linker layout, configuration, documentation, and build integration.
  • Adds a polled uSDHC SD-card driver with controller selection.
  • Adds AArch64 handoff, cache/MMU teardown, DTB fixups, and crash diagnostics.
File summaries
File Summary and final findings
src/boot_aarch64.c Invokes the fused EL2 handoff.
src/boot_aarch64_start.S Implements startup, cache teardown, and crash vectors. Nit (1): Correct the reversed x0/x1 ABI comment.
options.mk Adds selectable disk-controller configuration.
Makefile Configures the target artifact build.
hal/imx95_usdhc.c Implements uSDHC SD reads. Critical (3): CMD0 uses an incorrect response type. Critical (1): Required controller clocks are not enabled safely. Moderate (2): eMMC selects the SD-slot controller. Critical (1): Startup clocks are not emitted after reset. Moderate (1): 64-bit offsets are narrowed without overflow validation.
hal/imx95_a55.ld Defines the BL33 memory layout and stack.
hal/imx95_a55.h Defines i.MX95 hardware addresses and constants.
hal/imx95_a55.c Provides platform HAL, FIT loading, cache handling, and DTB fixups. Moderate (3): Kernel and ramdisk cache-clean ranges do not cover permitted destinations and sizes. Moderate (2): /chosen lookup must be restricted to the root child. Moderate (3): DT fixup errors must abort the handoff. Moderate (1): /memory lookup must be restricted to the root child.
docs/Targets.md Documents the new target.
config/examples/imx95-a55.config Configures FIT boot and memory addresses. Moderate (1): The kernel-to-ramdisk gap is smaller than the default kernel allowance and can permit overlap.
arch.mk Integrates target flags and drivers. Moderate (1): The SD-only driver is selected for eMMC configurations.
Review details

Suppressed comments (5)

arch.mk:244

  • DISK_EMMC=1 selects this object, but hal/imx95_usdhc.c is explicitly an SD-only protocol implementation (CMD8/ACMD41/ACMD6); an eMMC uses CMD1 and has no SD-card initialization. This configuration will link the driver and fail at disk_init; reject eMMC or only select this object for DISK_SDCARD.
    ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
      DISK_DRIVER=usdhc
      OBJS+=hal/imx95_usdhc.o

config/examples/imx95-a55.config:72

  • This layout leaves only 0x08000000 (128 MiB) between the kernel destination 0xB2000000 and ramdisk 0xBA000000, but fit_load_image() accepts the default 256 MiB kernel output. A valid signed FIT with a kernel larger than 128 MiB will be overwritten when the ramdisk is loaded. Cap the kernel to the available gap or validate non-overlapping actual ranges before loading.
# 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

hal/imx95_a55.c:334

  • This whole-tree device_type search can select a nested memory node and update its reg, leaving the root /memory node absent or unchanged. Since this fixup is specifically for /memory, use the direct root-child lookup before creating it.
    off = fdt_find_devtype(&ctx, -1, "memory");

hal/imx95_usdhc.c:424

  • disk_read() receives a 64-bit byte offset, but lba is narrowed to uint32_t before being used as the SD command argument. A GPT partition at or above 2 TiB therefore wraps to a different sector instead of failing; src/sdhci.c explicitly rejects this case. Keep the calculation 64-bit until validating the controller's 32-bit LBA limit, then return an error on overflow.
    uint32_t lba, off, chunk, blocks;

src/boot_aarch64_start.S:1759

  • The ABI comment says x0 = dts, but the caller passes entry in x0 and dts in x1; the implementation itself copies those values into x20/x21 in that order. Keeping the reversed documentation makes future callers liable to branch to the DTB or pass the kernel entry as x0.
/* el2_flush_disable_mmu_and_boot(entry, dts): fused flush + MMU-off + jump
 * (x0 = dts, x1-x3 zeroed) touching NO memory after the disable. With the
 * D-cache off, a stack reload of entry/dts between a flush call and a
 * separate C jump can read stale DRAM (set/way misses a DSU system cache).
 * The set/way loop clobbers x0-x11 only; x20/x21 carry the values. */
  • Files reviewed: 11/11 changed files
  • Comments generated: 8
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread hal/imx95_usdhc.c
Comment thread hal/imx95_usdhc.c
Comment thread hal/imx95_usdhc.c Outdated
Comment thread hal/imx95_a55.c Outdated
Comment thread hal/imx95_a55.c Outdated
Comment thread hal/imx95_a55.c Outdated
Comment thread hal/imx95_a55.c
Comment thread hal/imx95_usdhc.c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new uSDHC disk_read() truncates 64-bit byte offsets into 32-bit command arguments without bounds checks, which can wrap and read incorrect sectors on attacker-controlled partition tables.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread hal/imx95_usdhc.c
@dgarske dgarske changed the title Add wolfBoot target for NXP i.MX95 Cortex-A55 as BL33 booting a Linux FIT from SD Add wolfBoot target for NXP i.MX95 Cortex-A55, including a stage 1 that replaces U-Boot SPL Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants