Skip to content

Commit a9eea80

Browse files
committed
cm4: hardware-validate eMMC A/B disk boot and FIPS on CM4 (arm64 header, 0x200000 load, mini-UART console)
1 parent 057bf8e commit a9eea80

11 files changed

Lines changed: 346 additions & 115 deletions

File tree

config/examples/cm4_emmc.config

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Raspberry Pi CM4 (BCM2711) - onboard eMMC A/B disk boot.
2+
#
3+
# wolfBoot drives the BCM2711 EMMC2 controller (Arasan SDHCI v3.0 @ 0xFE340000)
4+
# to read GPT A/B image partitions from the onboard eMMC via update_disk.c, and
5+
# boots the highest valid version with rollback.
6+
#
7+
# Transfer mode: SDHCI_SDMA_DISABLED forces PIO. The BCM2711 EMMC2 SDMA
8+
# boundary-restart and cache coherency are not yet validated (same Arasan
9+
# family as the Versal quirk); DISK_EMMC compiles in the PIO BRR-race
10+
# workaround in src/sdhci.c. SDHCI_FORCE_CARD_DETECT: the onboard eMMC has no
11+
# routed card-detect line.
12+
#
13+
# Bring-up: uncomment DEBUG_SDHCI (+ DEBUG_DISK / DEBUG_GPT) for verbose
14+
# controller/partition tracing over the mini-UART console.
15+
#
16+
# For wolfCrypt FIPS 140-3, build with FIPS=1 pointing at a FIPS / FIPS-ready
17+
# wolfSSL tree (NO_ARM_ASM required; entropy from the RNG200 TRNG). See
18+
# docs/FIPS.md. e.g.:
19+
# make FIPS=1 WOLFBOOT_LIB_WOLFSSL=../wolfssl-5.9.2-gplv3-fips-ready NO_ARM_ASM=1
20+
#FIPS?=1
21+
#WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
22+
#NO_ARM_ASM?=1
23+
ARCH?=AARCH64
24+
TARGET?=cm4
25+
SIGN?=ECC384
26+
HASH?=SHA384
27+
IMAGE_HEADER_SIZE?=1024
28+
DEBUG?=0
29+
DEBUG_UART?=1
30+
DISK_SDCARD?=0
31+
DISK_EMMC?=1
32+
CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT
33+
CFLAGS_EXTRA+=-DSDHCI_SDMA_DISABLED
34+
#CFLAGS_EXTRA+=-DDEBUG_SDHCI
35+
#CFLAGS_EXTRA+=-DDEBUG_DISK
36+
#CFLAGS_EXTRA+=-DDEBUG_GPT
37+
EXT_FLASH?=0
38+
NO_XIP=1
39+
NO_QNX?=1
40+
ELF?=1
41+
VTOR?=1
42+
SPMATH?=1
43+
PKA?=0
44+
WOLFTPM?=0
45+
WOLFBOOT_NO_PARTITIONS=1
46+
CFLAGS_EXTRA+=-DBOOT_PART_A=1
47+
CFLAGS_EXTRA+=-DBOOT_PART_B=2
48+
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000
49+
WOLFBOOT_LOAD_ADDRESS?=0x10000000
50+
WOLFBOOT_RAMBOOT_MAX_SIZE=0x2BC00000
51+
WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000
52+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000
53+
WOLFBOOT_PARTITION_SIZE=0x4000000
54+
WOLFBOOT_SECTOR_SIZE=0x1000

config/examples/cm4_sdcard.config

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
# reads GPT A/B image partitions via update_disk.c and boots the highest valid
66
# version with rollback.
77
#
8-
# NOTE: the EMMC2 platform glue in hal/cm4.c is bring-up scaffolding pending
9-
# on-hardware validation (controller clock/pinmux state left by the GPU
10-
# firmware, card-detect wiring on the carrier). Use SDHCI_FORCE_CARD_DETECT
11-
# for the embedded eMMC path.
8+
# The EMMC2 glue in hal/cm4.c is hardware-validated on the onboard eMMC
9+
# (see cm4_emmc.config). The microSD path uses the same driver but needs a CM4
10+
# Lite - an eMMC module disables the carrier's microSD slot. Use
11+
# SDHCI_FORCE_CARD_DETECT for media with no routed card-detect line.
1212
#
1313
# For wolfCrypt FIPS 140-3, build with FIPS=1 pointing at a FIPS / FIPS-ready
1414
# wolfSSL tree. The FIPS module uses portable-C crypto, so NO_ARM_ASM is

docs/FIPS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ The FIPS DRBG needs a seed. wolfBoot's lean configuration compiles out the OS se
5555

5656
The module verifies an in-core integrity hash (HMAC-SHA-256 over the module's code and read-only data) at startup. A fresh build ships with a placeholder, so the first run reports a mismatch; capture the runtime hash and seal it:
5757

58-
1. Build and run with a FIPS callback registered (wolfBoot does this in `src/loader.c`). On a mismatch the module reports the runtime hash; wolfBoot prints it (`FIPS in-core hash = ...`, from `wolfCrypt_GetCoreHash_fips()`), and on the CM4 the test app (`test-app/app_cm4.c`) prints it over UART.
58+
1. Build and run with a FIPS callback registered (wolfBoot does this in `src/loader.c`). On a mismatch the module reports the runtime hash; wolfBoot prints it (`FIPS in-core hash = ...`, from `wolfCrypt_GetCoreHash_fips()`) before halting.
5959
2. Copy the reported 64-hex-character hash into `verifyCore[]` in `wolfcrypt/src/fips_test.c`.
6060
3. Rebuild and re-run. `wolfCrypt_GetStatus_fips()` now returns 0 (operational).
6161

6262
The seal is **specific to the exact binary layout**: any code change that shifts the FIPS module's link addresses changes the in-core hash and requires a re-seal. Re-sealing `verifyCore[]` itself does not shift addresses (same-size rewrite), so once the rest of the build is fixed the seal converges in one pass.
6363

64+
Two practical traps when re-sealing (both cost time on the CM4 bring-up):
65+
66+
- `verifyCore[]` can be sealed via a build define instead of editing the FIPS tree: `CFLAGS_EXTRA="-DWOLFCRYPT_FIPS_CORE_HASH_VALUE=<hash>"` (unquoted; `fips_test.c` stringifies it). But apply it by recompiling **only** `fips_test.o` - `rm "$WOLFBOOT_LIB_WOLFSSL/wolfcrypt/src/fips_test.o"` then rebuild. `verifyCore[]` lives *after* `wolfCrypt_FIPS_last`, so this leaves the hashed region byte-identical and converges in one pass. Passing the define through a **full** rebuild (`make clean` + build) recompiles the whole module and shifts its link addresses, so the hash never stabilizes.
67+
- `make clean` removes `$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/*.o`. If you build with `WOLFBOOT_LIB_WOLFSSL=<fips-tree>` but run `make clean` **without** that variable, it cleans the default `lib/wolfssl` instead, leaving the stale FIPS `fips_test.o` in place - the new seal silently never lands. Pass `WOLFBOOT_LIB_WOLFSSL` to `clean` too, or `rm` the object directly.
68+
6469
## Bare-metal targets
6570

6671
The FIPS module targets a hosted environment; a few things must be provided on bare-metal (the CM4 does all of these):

docs/Targets.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,11 +3714,11 @@ cp config/examples/cm4.config .config
37143714
make CROSS_COMPILE=aarch64-none-elf-
37153715
```
37163716
3717-
The example uses `SIGN=ECC384 HASH=SHA384` (both FIPS-approved). wolfBoot is entered by the firmware at `0x80000` at EL2, matching `hal/cm4.ld`. The image is loaded from RAM: wolfBoot reads the signed application at `kernel_addr` (`0x140000`), verifies it, copies it to `WOLFBOOT_LOAD_ADDRESS`, and boots.
3717+
The example uses `SIGN=ECC384 HASH=SHA384` (both FIPS-approved). wolfBoot is built as an AArch64 Linux kernel image (`kernel8.img`): `src/boot_aarch64_start.S` prepends the 64-byte ARM64 image header (`"ARM\x64"` magic), and `hal/cm4.ld` links at `0x200000`. The VideoCore firmware only transfers control to a 64-bit kernel that carries this header, and it runs the image in place at the 2 MB-aligned load address `0x200000` (it does not relocate a header image down to `0x80000`) at EL2. The image is loaded from RAM: wolfBoot reads the signed application at `kernel_addr` (`0x2C0000`), verifies it, copies it to `WOLFBOOT_LOAD_ADDRESS`, and boots.
37183718
37193719
### Signing and assembling the boot image
37203720
3721-
Sign the application, then concatenate wolfBoot and the signed image so the signed image lands at `kernel_addr` (`0x140000` = `0x80000` load + `0xC0000`):
3721+
Sign the application, then concatenate wolfBoot and the signed image so the signed image lands at `kernel_addr` (`0x2C0000` = `0x200000` load + `0xC0000`):
37223722
37233723
```
37243724
make keytools tools/bin-assemble/bin-assemble
@@ -3731,7 +3731,7 @@ tools/bin-assemble/bin-assemble kernel8.img \
37313731
37323732
### config.txt
37333733
3734-
The CM4 UART on GPIO14/15 defaults to the mini-UART because the PL011 is used by Bluetooth. wolfBoot drives the PL011, so route it to the header and fix a known UART clock:
3734+
The debug console on GPIO14/15 is the BCM2711 mini-UART (AUX, Linux `ttyS0`); the PL011 is used by Bluetooth. wolfBoot drives the **mini-UART** by default (it inherits the firmware's stable baud, which `enable_uart=1` fixes by pinning `core_freq`), so no baud reprogramming is needed. Boards where `dtoverlay=disable-bt` actually routes the PL011 onto GPIO14/15 can build with `CFLAGS_EXTRA=-DCM4_UART_PL011` to use the PL011 instead.
37353735
37363736
```
37373737
arm_64bit=1
@@ -3761,13 +3761,15 @@ Firmware Valid
37613761
Booting at 0x3080000
37623762
```
37633763
3764-
### Optional: eMMC/SD A/B updates
3764+
### Optional: eMMC/SD A/B disk boot
37653765
3766-
`config/examples/cm4_sdcard.config` enables the disk updater (`DISK_SDCARD`/`DISK_EMMC`), driving the BCM2711 EMMC2 controller through the generic SDHCI driver (`src/sdhci.c`) for A/B image partitions on the boot medium. This path is provided as bring-up scaffolding and is not yet hardware-validated.
3766+
`config/examples/cm4_emmc.config` (onboard eMMC) and `config/examples/cm4_sdcard.config` (microSD) enable the disk updater (`DISK_EMMC`/`DISK_SDCARD`), driving the BCM2711 EMMC2 controller through the generic SDHCI driver (`src/sdhci.c` + the `hal/cm4.c` register glue) to read A/B signed images from GPT partitions. wolfBoot reads the GPT, selects the higher-version image, verifies it, ELF-loads it (`ELF=1`) to `WOLFBOOT_LOAD_ADDRESS`, and boots.
3767+
3768+
The **eMMC** path (`cm4_emmc.config`) has been validated end to end on CM4 hardware: SDHCI/eMMC card init -> GPT parse -> A/B version select -> SHA-384 integrity -> ECDSA-P384 signature verify -> ELF64 load -> boot of a signed payload. `tools/scripts/cm4/prepare_emmc.sh` builds the GPT layout (FAT boot partition with `kernel8.img` + firmware, plus raw A/B image partitions), signs a minimal test payload (`tools/scripts/cm4/disk_app.S`), and writes it to the eMMC over `rpiboot`. Uncomment `DEBUG_SDHCI` / `DEBUG_DISK` / `DEBUG_GPT` in the config for verbose bring-up tracing. The **microSD** path shares the same driver but is validated only on modules whose SD lines reach the microSD slot (a CM4 with onboard eMMC disables that slot).
37673769
37683770
### FIPS 140-3
37693771
3770-
The CM4 target uses `SIGN=ECC384 HASH=SHA384` (FIPS-approved) and can perform its signature verification with the wolfCrypt FIPS 140-3 module (build `config/examples/cm4.config` with `FIPS=1`, pointing `WOLFBOOT_LIB_WOLFSSL` at a FIPS wolfSSL tree). At boot the module runs its power-on self-test and in-core integrity check, and wolfBoot refuses to boot unless the module is operational. Entropy for the FIPS DRBG comes from the BCM2711 RNG200 hardware TRNG. The full flow has been brought up and exercised end to end on CM4 hardware using the FIPS-ready bundle: FIPS module operational -> SHA-384 integrity -> ECDSA-P384 signature verification -> boot handoff. A production, CMVP-validated deployment additionally requires the licensed validated wolfCrypt FIPS bundle at the validated revision (see [FIPS.md](FIPS.md)). The on-target harness in `test-app/app_cm4.c` prints the runtime in-core hash over the UART for the `verifyCore[]` seal. See [FIPS.md](FIPS.md) for the full build, entropy, and hash-sealing procedure.
3772+
The CM4 target uses `SIGN=ECC384 HASH=SHA384` (FIPS-approved) and can perform its signature verification with the wolfCrypt FIPS 140-3 module (build `config/examples/cm4.config` with `FIPS=1`, pointing `WOLFBOOT_LIB_WOLFSSL` at a FIPS wolfSSL tree). At boot the module runs its power-on self-test and in-core integrity check, and wolfBoot refuses to boot unless the module is operational. Entropy for the FIPS DRBG comes from the BCM2711 RNG200 hardware TRNG. The FIPS configuration builds with the CM4 hardware-boot support (ARM64 image header, `0x200000` load address, mini-UART console) and has been validated end to end on CM4 hardware with the FIPS-ready bundle: after sealing the in-core integrity hash, wolfBoot reports `FIPS 140-3 module operational` and the module gates the boot with SHA-384 integrity and ECDSA-P384 signature verification of the eMMC A/B image (`cm4_emmc.config` with `FIPS=1`; wolfBoot's `src/loader.c` runs the power-on self-test and in-core check before booting). A production, CMVP-validated deployment additionally requires the licensed validated wolfCrypt FIPS bundle at the validated revision (see [FIPS.md](FIPS.md)). On an in-core hash mismatch, wolfBoot prints the runtime hash (`FIPS in-core hash = ...`, from `src/loader.c`) to seal into `verifyCore[]`. Re-seal by recompiling only `fips_test.o` with `-DWOLFCRYPT_FIPS_CORE_HASH_VALUE=<hash>` (a full rebuild shifts the module boundary and the hash); see [FIPS.md](FIPS.md) for the full build, entropy, and hash-sealing procedure.
37713773
37723774
## Xilinx Zynq UltraScale
37733775

hal/cm4.c

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
* HAL for the Raspberry Pi Compute Module 4 (CM4): Broadcom BCM2711,
44
* quad-core Cortex-A72 (ARMv8-A).
55
*
6-
* The VideoCore GPU firmware loads wolfBoot (as kernel8.img) to 0x80000 and
7-
* releases the A72 cores; wolfBoot verifies the appended signed payload and
8-
* boots it from RAM. hal_flash_* are no-ops (no in-place flash in this mode).
9-
* Optional eMMC/SD A/B via the generic SDHCI driver is at the end of the file.
6+
* The VideoCore GPU firmware loads wolfBoot (an ARM64 kernel8.img carrying the
7+
* Linux image header) to 0x200000 and enters it at EL2; wolfBoot verifies the
8+
* signed payload and boots it from RAM, or from eMMC/SD A/B via the generic
9+
* SDHCI driver (at the end of this file). hal_flash_* are no-ops (no in-place
10+
* flash in this mode).
1011
*
1112
* Copyright (C) 2026 wolfSSL Inc.
1213
*
@@ -42,39 +43,67 @@
4243
/* Fixed addresses (provided by the linker script) */
4344
extern void *kernel_addr, *update_addr, *dts_addr;
4445

45-
#if defined(HAVE_FIPS)
46+
/* Enable the identity MMU + caches when the build does more than the trivial
47+
* RAM-boot: FIPS (unaligned/SIMD in the module), or the disk path (optimized
48+
* code + SDHCI block-buffer memcpy fault on MMU-off Device memory). Normal
49+
* cacheable memory permits those accesses and speeds up crypto/disk reads. */
50+
#if (defined(HAVE_FIPS) || defined(DISK_SDCARD) || defined(DISK_EMMC)) \
51+
&& defined(__aarch64__)
52+
#define CM4_USE_MMU
53+
#endif
54+
55+
#if defined(CM4_USE_MMU)
4656
void cm4_mmu_enable(void); /* defined below; called from hal_init */
4757
void cm4_mmu_disable(void); /* defined below; called from hal_prepare_boot */
4858
#endif
4959

5060
#if defined(DEBUG_UART)
61+
/* Console UART select. On this bench CM4 the debug cable on GPIO14/15 is the
62+
* BCM2711 mini-UART (AUX, Linux ttyS0), so that is the default. Boards where
63+
* dtoverlay=disable-bt actually routes the PL011 onto GPIO14/15 can build with
64+
* CM4_UART_PL011 to use the PL011 (0xFE201000) instead. */
65+
#if defined(CM4_UART_PL011)
5166
static void uart_tx(char c)
5267
{
5368
while (*UART0_FR & 0x20) /* TXFF: wait while FIFO full */
5469
;
55-
*UART0_DR = c;
56-
}
57-
58-
void uart_write(const char* buf, uint32_t sz)
59-
{
60-
while (sz-- > 0 && *buf)
61-
uart_tx(*buf++);
70+
*UART0_DR = (unsigned int)(unsigned char)c;
6271
}
6372

6473
void uart_init(void)
6574
{
66-
/* The VideoCore firmware has already routed the PL011 to GPIO14/15
67-
* (dtoverlay=disable-bt) and set init_uart_clock=48MHz. Program the PL011
68-
* for 115200 8N1 directly, without the VideoCore mailbox (a mailbox poll
69-
* that never returns post-handoff would hang before any output).
70-
* 48MHz UARTCLK: BAUDDIV = 48e6/(16*115200) = 26.04 -> IBRD 26, FBRD 3. */
75+
/* Program the PL011 for 115200 8N1 assuming a 48MHz UARTCLK
76+
* (init_uart_clock=48000000): BAUDDIV = 48e6/(16*115200) -> IBRD 26 FBRD 3. */
7177
*UART0_CR = 0;
7278
*UART0_ICR = 0x7FF;
7379
*UART0_IBRD = 26;
7480
*UART0_FBRD = 3;
7581
*UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); /* FIFO, 8-bit */
7682
*UART0_CR = (1 << 0) | (1 << 8) | (1 << 9); /* enable UART, TX, RX */
7783
}
84+
#else /* mini-UART (default) */
85+
static void uart_tx(char c)
86+
{
87+
while ((*MU_LSR & MU_LSR_TXFF_EMPTY) == 0) /* wait until TX can accept */
88+
;
89+
*MU_IO = (unsigned int)(unsigned char)c;
90+
}
91+
92+
void uart_init(void)
93+
{
94+
/* The firmware has already enabled the mini-UART at a stable baud
95+
* (enable_uart=1 fixes core_freq), so - like the Linux 8250 console with
96+
* "skip-init" - wolfBoot inherits that setup and just writes AUX_MU_IO.
97+
* Reprogramming the baud here is unnecessary (and error-prone: the mini-UART
98+
* clock is core_freq-derived, not a fixed rate). */
99+
}
100+
#endif /* CM4_UART_PL011 */
101+
102+
void uart_write(const char* buf, uint32_t sz)
103+
{
104+
while (sz-- > 0 && *buf)
105+
uart_tx(*buf++);
106+
}
78107
#endif /* DEBUG_UART */
79108

80109
void* hal_get_primary_address(void)
@@ -134,18 +163,19 @@ void hal_init(void)
134163
wolfBoot_printf("wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL%d\n",
135164
(int)((el >> 2) & 0x3));
136165
#endif
137-
#if defined(HAVE_FIPS)
138-
/* Bring up Normal cacheable memory before the FIPS POST, which uses
139-
* unaligned / SIMD accesses that the MMU-off Device memory rejects. */
166+
#if defined(CM4_USE_MMU)
167+
/* Bring up Normal cacheable memory before any code that uses unaligned /
168+
* SIMD accesses (FIPS module, optimized disk path) which the MMU-off
169+
* Device memory rejects. */
140170
cm4_mmu_enable();
141171
#endif
142172
}
143173

144174
void hal_prepare_boot(void)
145175
{
146-
#if defined(HAVE_FIPS)
147-
/* Undo cm4_mmu_enable() before handoff: flush the app out of the D-cache
148-
* and return to the MMU-off state the application expects. */
176+
#if defined(CM4_USE_MMU)
177+
/* Undo cm4_mmu_enable() before handoff: flush the loaded image out of the
178+
* D-cache and return to the MMU-off state the application expects. */
149179
cm4_mmu_disable();
150180
#endif
151181
}
@@ -173,13 +203,16 @@ void* _sbrk(int incr)
173203
brk += incr;
174204
return (void*)prev;
175205
}
206+
#endif /* HAVE_FIPS */
176207

208+
#if defined(CM4_USE_MMU)
177209
/* Minimal identity-mapped MMU + caches for the CM4. wolfBoot's simple startup
178210
* runs with the MMU off, so all memory is Device-nGnRnE, which faults on the
179-
* unaligned / 128-bit SIMD accesses the FIPS module and newlib printf perform.
180-
* Mapping DDR as Normal (cacheable) permits those accesses and speeds up the
181-
* crypto; the peripheral region (incl. 0xFE000000) stays Device.
182-
* Four 1GB block descriptors cover the 32-bit VA space at translation level 1. */
211+
* unaligned / 128-bit SIMD accesses that the FIPS module, newlib printf, and
212+
* the optimized disk/SDHCI code paths perform. Mapping DDR as Normal
213+
* (cacheable) permits those accesses and speeds up crypto/disk reads; the
214+
* peripheral region (incl. 0xFE000000) stays Device. Four 1GB block
215+
* descriptors cover the 32-bit VA space at translation level 1. */
183216
#define MMU_BLOCK_NORMAL 0x0000000000000701ULL /* block, AttrIdx0, AF, SH inner */
184217
#define MMU_BLOCK_DEVICE 0x0000000000000405ULL /* block, AttrIdx1, AF, SH none */
185218

@@ -275,7 +308,7 @@ void cm4_mmu_disable(void)
275308
__asm__ volatile("dsb sy");
276309
__asm__ volatile("isb");
277310
}
278-
#endif /* HAVE_FIPS */
311+
#endif /* CM4_USE_MMU */
279312

280313
#if defined(DEBUG) && defined(DEBUG_UART)
281314
/* CM4 bring-up diagnostic: exception handler invoked from cm4_vectors in
@@ -371,9 +404,9 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len)
371404
/* BCM2711 EMMC2 platform glue for the generic SDHCI driver (src/sdhci.c).
372405
* EMMC2 is a standard SDHCI v3.0 Arasan block at 0xFE340000. The driver uses
373406
* Cadence-style SRS offsets (0x200 + std); translate them to the standard
374-
* Arasan layout, mirroring the ZynqMP path in hal/zynq.c. NOTE: not yet
375-
* hardware-validated; clock/caps/card-detect quirks may be required once
376-
* validated on hardware. */
407+
* Arasan layout, mirroring the ZynqMP path in hal/zynq.c. The GPU firmware has
408+
* already configured the EMMC2 clock/pinmux, so only a controller soft reset is
409+
* needed here (hardware-validated on CM4 eMMC). */
377410
#include "sdhci.h"
378411

379412
uint32_t sdhci_reg_read(uint32_t offset)

0 commit comments

Comments
 (0)