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 3 commits into
wolfSSL:masterfrom
dgarske:imx95_a55_pr
Open

dgarske wants to merge 3 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; it brings up the boot device, walks the container set, has the EdgeLock Enclave authenticate the next container and loads BL31, OP-TEE and BL33 out of it. The 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. Also carries the LPUART1 console, the SCMI-over-MU client for the clocks, pinmux and power domains the System Manager owns, and the 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. All three are A55-only and shared between the two positions, so the BL33-only HAL API is gated on BUILD_LOADER_STAGE1 the way the PPC targets share one HAL
  • hal/imx95_ahab.{c,h} - AHAB container-set parser, and the destination bounds stage 1 applies to every image before it writes any of them, 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. IMX95_AHAB_AUTH, on by default, issues the container-authenticate, per-image verify and release calls to the ELE that SPL used to make; docs/Targets.md covers it and the bring-up switches that are refused alongside it
  • 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
  • src/boot_aarch64_cache.S - the set/way data cache clean moves out of boot_aarch64_start.S into its own translation unit, so stage 1 can call it rather than carry a second copy of it; stage 1 links none of the rest of that file
  • src/boot_aarch64.c - a device tree whose fixup fails no longer boots unpatched, because Linux may then lack its memory node. Enforced only when a tree is actually present, so a payload that passes none is unaffected - update_ram.c hands do_boot() a NULL there, and a target whose hal_dts_fixup() is real reports failure on 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. The ELE authentication path is not hardware-validated yet - the parser and the destination bounds have unit tests and were replayed against the container set this board boots, but the enclave calls themselves have not run on silicon.

Scope

The uSDHC driver is PIO; ADMA2 is follow-on work. Authenticating the container set does not prevent rollback: the offset of the next container is derived by walking container 0, which stage 1 reads off the medium and nothing re-authenticates, so a different but validly signed older set would authenticate happily. That wants sw_version/fuse_version policy or the AHAB monotonic counter. Secondary and fallback container sets are not handled either - if the ROM booted set B, stage 1 still walks set A.

@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 Outdated
Comment thread hal/imx95_usdhc.c Outdated
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 Outdated

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 Outdated
@dgarske
dgarske force-pushed the imx95_a55_pr branch 3 times, most recently from 30f6cee to 5a630fc Compare September 18, 2026 19:10
@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
@dgarske dgarske assigned danielinux and wolfSSL-Bot and unassigned dgarske Sep 24, 2026
@dgarske
dgarske requested a review from danielinux September 24, 2026 04:29
@dgarske
dgarske force-pushed the imx95_a55_pr branch 2 times, most recently from de4d669 to 1d594b4 Compare September 26, 2026 18:56
@dgarske dgarske self-assigned this Sep 29, 2026
@dgarske
dgarske requested a lite review from Copilot September 30, 2026 16:01

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.

Copilot review overview

🟡 Changes recommended

Unresolved critical FIT-destination and stage-1 self-overlap issues, plus four moderate findings, block approval.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (1)

Comment thread hal/imx95_a55.c Outdated
Comment thread hal/imx95_a55_stage1.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.

Comment thread hal/imx95_a55.c
Comment thread hal/imx95_a55.c
Comment thread hal/imx95_a55_stage1.c
Comment thread hal/imx95_a55_stage1.c
Comment thread hal/imx95_ahab.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.

Comment thread arch.mk
Comment thread hal/imx95_a55.c
Comment thread stage1/Makefile
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.

4 participants