cm4: add wolfCrypt FIPS 140-3 support + CI, SDHCI unit test, review fixes

pull/863/head
David Garske 2026-07-10 17:30:27 -07:00 committed by Daniele Lacamera
parent eb7ad83ca0
commit 71b855f7b5
19 changed files with 875 additions and 26 deletions

View File

@ -18,6 +18,12 @@ on:
required: false
type: boolean
default: false
fips:
# Download the FIPS-ready wolfSSL tree into ../ before building, for a
# build-only FIPS=1 verification (see WOLFBOOT_LIB_WOLFSSL in make-args).
required: false
type: boolean
default: false
jobs:
@ -126,6 +132,16 @@ jobs:
unsigned long psu_ddr_qos_init_data(void) { return 1UL; }
STUB
- name: Download FIPS-ready wolfSSL
if: ${{ inputs.fips }}
run: |
set -euxo pipefail
sudo apt-get install -y unzip
curl -fsSL --retry 3 --retry-delay 5 -o /tmp/wolfssl-fips-ready.zip \
https://www.wolfssl.com/wolfssl-5.9.2-gplv3-fips-ready.zip
unzip -q /tmp/wolfssl-fips-ready.zip -d ..
test -f ../wolfssl-5.9.2-gplv3-fips-ready/wolfcrypt/src/fips.c
- name: Build wolfboot
run: |
make ${{inputs.make-args}}

View File

@ -16,6 +16,11 @@ on:
prep-run:
required: false
type: string
fips:
# Download the FIPS-ready wolfSSL tree into ../ before building.
required: false
type: boolean
default: false
jobs:
@ -48,6 +53,16 @@ jobs:
- name: Pre-build setup
if: inputs.prep-run != ''
run: ${{inputs.prep-run}}
- name: Download FIPS-ready wolfSSL
if: ${{ inputs.fips }}
run: |
set -euxo pipefail
# Runs inside the CI container (no sudo/apt); unpack with python3,
# which is already a build dependency.
curl -fsSL --retry 3 --retry-delay 5 -o /tmp/wolfssl-fips-ready.zip \
https://www.wolfssl.com/wolfssl-5.9.2-gplv3-fips-ready.zip
python3 -m zipfile -e /tmp/wolfssl-fips-ready.zip ..
test -f ../wolfssl-5.9.2-gplv3-fips-ready/wolfcrypt/src/fips.c
- name: Build wolfboot
run: |

View File

@ -413,6 +413,56 @@ jobs:
config-file: ./config/examples/tegra234-sdcard.config
make-args: wolfboot.bin test-app/image_v1_signed.bin CROSS_COMPILE=aarch64-linux-gnu-
cm4_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4.config
make-args: wolfboot.bin
cm4_sdcard_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4_sdcard.config
make-args: wolfboot.bin
cm4_emmc_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4_emmc.config
make-args: wolfboot.bin
cm4_emmc_linux_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4_emmc_linux.config
make-args: wolfboot.bin
cm4_emmc_rauc_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4_emmc_rauc.config
make-args: wolfboot.bin
cm4_fips_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/cm4.config
make-args: wolfboot.bin FIPS=1 WOLFBOOT_LIB_WOLFSSL=../wolfssl-5.9.2-gplv3-fips-ready NO_ARM_ASM=1
fips: true
sim_fips_test:
uses: ./.github/workflows/test-build.yml
with:
arch: host
config-file: ./config/examples/sim-fips.config
fips: true
sim_tfm_smallstack_test:
uses: ./.github/workflows/test-build.yml
with:

View File

@ -1,8 +1,18 @@
# Raspberry Pi CM4 (BCM2711) - RAM-boot authenticated boot.
#
# For wolfCrypt FIPS 140-3, build with FIPS=1 pointing at a FIPS / FIPS-ready
# wolfSSL tree. The FIPS module uses portable-C crypto, so NO_ARM_ASM is
# required. Entropy comes from the BCM2711 RNG200 hardware TRNG (hal/cm4.c).
# See docs/FIPS.md for the in-core hash-seal procedure (done on-target). e.g.:
# make FIPS=1 WOLFBOOT_LIB_WOLFSSL=../wolfssl-5.9.2-gplv3-fips-ready NO_ARM_ASM=1
#FIPS?=1
#WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
#NO_ARM_ASM?=1
ARCH?=AARCH64
TARGET?=cm4
SIGN?=ECC384
HASH?=SHA384
DEBUG?=1
DEBUG?=0
VTOR?=1
SPMATH?=1
IMAGE_HEADER_SIZE?=1024
@ -15,4 +25,4 @@ WOLFBOOT_SECTOR_SIZE=0x400
WOLFBOOT_NO_PARTITIONS=1
WOLFBOOT_RAMBOOT_MAX_SIZE=0x20000000
WOLFBOOT_LOAD_ADDRESS?=0x3080000
WOLFBOOT_LOAD_DTS_ADDRESS?=0x400000
WOLFBOOT_LOAD_DTS_ADDRESS?=0x08000000

View File

@ -1,4 +1,4 @@
# Raspberry Pi CM4 (BCM2711) - eMMC/SD A/B disk boot (Phase 2)
# Raspberry Pi CM4 (BCM2711) - eMMC/SD A/B disk boot.
#
# Boots from the BCM2711 EMMC2 controller (Arasan SDHCI @ 0xFE340000), which
# drives the onboard eMMC (eMMC variants) or the microSD (CM4 Lite). wolfBoot
@ -9,6 +9,15 @@
# on-hardware validation (controller clock/pinmux state left by the GPU
# firmware, card-detect wiring on the carrier). Use SDHCI_FORCE_CARD_DETECT
# for the embedded eMMC path.
#
# For wolfCrypt FIPS 140-3, build with FIPS=1 pointing at a FIPS / FIPS-ready
# wolfSSL tree. The FIPS module uses portable-C crypto, so NO_ARM_ASM is
# required. Entropy comes from the BCM2711 RNG200 hardware TRNG (hal/cm4.c).
# See docs/FIPS.md for the in-core hash-seal procedure (done on-target). e.g.:
# make FIPS=1 WOLFBOOT_LIB_WOLFSSL=../wolfssl-5.9.2-gplv3-fips-ready NO_ARM_ASM=1
#FIPS?=1
#WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
#NO_ARM_ASM?=1
ARCH?=AARCH64
TARGET?=cm4
SIGN?=ECC384

View File

@ -0,0 +1,22 @@
# wolfBoot simulator build using the wolfCrypt FIPS 140-3 module.
# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS (or FIPS-ready) wolfSSL tree.
# Prototype target for the CM4 FIPS integration (see docs/FIPS.md).
ARCH=sim
TARGET=sim
SIGN?=ECC384
HASH?=SHA384
FIPS?=1
WOLFBOOT_LIB_WOLFSSL?=../wolfssl-5.9.2-gplv3-fips-ready
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
DEBUG=1
# sizes should be multiple of system page size
WOLFBOOT_PARTITION_SIZE=0x40000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
# required for keytools
WOLFBOOT_FIXED_PARTITIONS=1

82
docs/FIPS.md 100644
View File

@ -0,0 +1,82 @@
# wolfBoot with wolfCrypt FIPS 140-3
This page explains how wolfBoot performs its firmware signature verification with the wolfCrypt FIPS 140-3 module, and what a fully CMVP-validated deployment additionally requires. Read it before making any FIPS claim about a wolfBoot deployment.
## Two distinct things: approved algorithms vs. a validated module
FIPS 140-3 has two separate requirements that are easy to conflate:
1. Using FIPS **approved algorithms** for the security-relevant operations (here: image signature verification and hashing).
2. Performing those operations inside the **CMVP-validated wolfCrypt module** boundary, with the power-on self-test (POST), the in-core integrity check, and status gating (`wolfCrypt_GetStatus_fips`).
A stock wolfBoot build only addresses (1): it compiles individual `wolfcrypt/src/*.c` files selected by `SIGN`/`HASH`, with no POST or in-core integrity check. Building with `FIPS=1` (this page) addresses (2): wolfBoot links the wolfCrypt FIPS module boundary, runs the POST + in-core check at boot, and refuses to boot unless the module is operational.
A production-validated deployment still requires the **licensed, validated** wolfCrypt FIPS bundle at the exact validated revision (not the evaluation "FIPS-ready" drop), the validated module version, and adherence to the module's Security Policy. Contact wolfSSL (facts@wolfssl.com) for the current certificate, validated module version, and integration guidance for a specific target.
## Approved algorithms for image authentication
wolfBoot authenticates images with a public-key signature over a hash of the image. The default wolfBoot signature algorithm, **ED25519, is NOT FIPS approved** and must not be used for a FIPS configuration.
Approved pairs (select in the target `.config`):
- Signature (`SIGN=`): `ECC256` / `ECC384` / `ECC521` (ECDSA P-256/P-384/P-521). RSA-PSS is FIPS-approved but is not yet wired into wolfBoot's FIPS module object list (no `rsa.o` in the boundary); the `FIPS=1` build rejects it. Use ECDSA.
- Hash (`HASH=`): `SHA256`, `SHA384`. Match or exceed the signature strength (e.g. P-384 with SHA-384).
- Set `SPMATH=1` (the single-precision math backend the wolfCrypt FIPS module is validated with).
Not approved for signing: `ED25519`, `ED448`. The post-quantum options (`LMS`/`XMSS`/`ML-DSA`) are governed by separate NIST standards and are out of scope here.
## Getting the FIPS source
Obtain a FIPS wolfCrypt source tree. For evaluation, the FIPS-ready bundle can be downloaded from wolfSSL:
```
https://www.wolfssl.com/wolfssl-5.9.2-gplv3-fips-ready.zip
```
Production use requires the licensed, validated FIPS bundle. Unpack it and point wolfBoot at it with `WOLFBOOT_LIB_WOLFSSL`.
## Building wolfBoot with FIPS
The `FIPS=1` build option (`options.mk`) rebuilds the wolfcrypt object list as the validated module boundary in link order (`wolfcrypt_first.o` first, `wolfcrypt_last.o` last, with `fips.o`/`fips_test.o` and the boundary crypto between them - the in-core integrity hash on GCC/ELF is enforced by this link order). Point the build at the FIPS tree and select an approved algorithm pair:
```
cp config/examples/sim-fips.config .config # or config/examples/cm4.config
make FIPS=1 WOLFBOOT_LIB_WOLFSSL=/path/to/wolfssl-5.9.2-gplv3-fips-ready \
SIGN=ECC384 HASH=SHA384 SPMATH=1
```
`-DHAVE_FIPS` is added by the `FIPS=1` block. `FIPS=1` also defaults `FIPS_READY=1`, which defines `WOLFSSL_FIPS_READY` (`include/user_settings.h`); that macro forces the evaluation bundle's `HAVE_FIPS_VERSION` to 7 and selects the FIPS-186-4 gating in `settings.h`. A production build with the licensed **validated** bundle (not a FIPS-ready drop) must pass `FIPS_READY=0` so the bundle keeps its own declared module version. The `HAVE_FIPS` block in `include/user_settings.h` also enables the module's algorithm set, keeps the RNG/DRBG enabled, and wires the entropy seed (below).
## Entropy source (required)
The FIPS DRBG needs a seed. wolfBoot's lean configuration compiles out the OS seed paths, so a seed is provided via `CUSTOM_RAND_GENERATE_SEED` (the `HAVE_FIPS` block in `include/user_settings.h` keeps the RNG enabled by undoing wolfBoot's `WC_NO_RNG`/`WC_NO_HASHDRBG`). The example wiring points it at `wolfBoot_fips_seed()`, implemented per target: `/dev/urandom` on the simulator (`hal/sim.c`) and the BCM2711 RNG200 hardware TRNG on the CM4 (`hal/cm4.c`). Without a working seed, the ECDSA power-on self-test (which performs a sign) fails with `ECDSA_KAT_FIPS_E` because `wc_GenerateSeed()` returns `NOT_COMPILED_IN`.
## Sealing the in-core integrity hash
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:
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.
2. Copy the reported 64-hex-character hash into `verifyCore[]` in `wolfcrypt/src/fips_test.c`.
3. Rebuild and re-run. `wolfCrypt_GetStatus_fips()` now returns 0 (operational).
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.
## Bare-metal targets
The FIPS module targets a hosted environment; a few things must be provided on bare-metal (the CM4 does all of these):
- **POST entry.** The module registers its POST via a C constructor (`.init_array`), which a hosted runtime runs before `main()`. wolfBoot's bare-metal startup does not run `.init_array`, so build with `NO_ATTRIBUTE_CONSTRUCTOR` and call `fipsEntry()` explicitly (`src/loader.c`).
- **Normal (cacheable) memory.** wolfBoot's simple startup runs with the MMU off, where all memory is Device-nGnRnE and unaligned / 128-bit SIMD accesses fault (the FIPS module and newlib `printf`/`snprintf` do both). The CM4 HAL enables a minimal identity MMU with DDR mapped Normal cacheable before the POST (`cm4_mmu_enable`), and tears it down (clean D-cache, disable MMU/caches) before the boot handoff (`cm4_mmu_disable`) so the loaded image is coherent and the application starts MMU-off.
- **libc.** The module uses malloc/printf; stub the newlib syscalls (`--specs=nosys.specs`) and provide a bounded `_sbrk` so the heap cannot grow into the unverified image (the CM4 HAL allocates from a fixed static buffer in `hal/cm4.c`).
Bring the module up on the simulator (`config/examples/sim-fips.config`) first - it exercises the whole flow (module boundary, POST, in-core seal, verify, A/B update) with no hardware.
## Verifying operation
- POST/CASTs run at module initialization; `wc_RunAllCast_fips()` runs the conditional algorithm self-tests and `wolfCrypt_GetStatus_fips()` reports the module status (0 = operational).
- wolfBoot treats a non-zero FIPS status as a hard failure and refuses to boot (`src/loader.c`).
- A deliberately corrupted module boundary (flip a byte) makes the in-core check fail and blocks the boot - the negative test for the integration.
## See also
- [Targets.md](Targets.md) - Raspberry Pi Compute Module 4 (BCM2711) target. FIPS 140-3 authenticated boot (module operational -> SHA-384 integrity -> ECDSA-P384 verify seeded by the BCM2711 hardware TRNG -> handoff) is validated on CM4 hardware.

View File

@ -11,6 +11,7 @@ See also: [wolfBoot Product Overview](https://www.wolfssl.com/products/wolfboot/
- [**encrypted_partitions.md**](./encrypted_partitions.md) - Creating and managing encrypted firmware/data partitions.
- [**firmware_image.md**](./firmware_image.md) - wolfBoot firmware image format, layout, and metadata.
- [**firmware_update.md**](./firmware_update.md) - Update flow: slots, verification, rollback, and recovery.
- [**FIPS.md**](./FIPS.md) - Building wolfBoot with the wolfCrypt FIPS 140-3 module: approved algorithms, DRBG entropy, and in-core integrity sealing.
- [**flash-OTP.md**](./flash-OTP.md) - Using One-Time Programmable (OTP) regions in flash for secure data.
- [**flash_partitions.md**](./flash_partitions.md) - Flash partitioning schemes and configuration guidance.
- [**HAL.md**](./HAL.md) - Hardware Abstraction Layer notes and porting considerations.

View File

@ -3871,7 +3871,7 @@ Booting at 0x3080000
### FIPS 140-3
The CM4 target uses `SIGN=ECC384 HASH=SHA384` (FIPS-approved) and can perform its signature verification with the wolfCrypt FIPS 140-3 validated module. The on-target harness in `test-app/app_cm4.c` registers a FIPS callback and prints the runtime in-core integrity hash over the UART for the `verifyCore[]` bootstrap. See [FIPS.md](FIPS.md) for the full build and hash-sealing procedure.
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.
## Xilinx Zynq UltraScale

186
hal/cm4.c
View File

@ -42,6 +42,11 @@
/* Fixed addresses (provided by the linker script) */
extern void *kernel_addr, *update_addr, *dts_addr;
#if defined(HAVE_FIPS)
void cm4_mmu_enable(void); /* defined below; called from hal_init */
void cm4_mmu_disable(void); /* defined below; called from hal_prepare_boot */
#endif
#if defined(DEBUG_UART)
static void uart_tx(char c)
{
@ -129,13 +134,175 @@ void hal_init(void)
wolfBoot_printf("wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL%d\n",
(int)((el >> 2) & 0x3));
#endif
#if defined(HAVE_FIPS)
/* Bring up Normal cacheable memory before the FIPS POST, which uses
* unaligned / SIMD accesses that the MMU-off Device memory rejects. */
cm4_mmu_enable();
#endif
}
void hal_prepare_boot(void)
{
#if defined(HAVE_FIPS)
/* Undo cm4_mmu_enable() before handoff: flush the app out of the D-cache
* and return to the MMU-off state the application expects. */
cm4_mmu_disable();
#endif
}
#if defined(HAVE_FIPS)
/* Bounded heap for the FIPS module's malloc. wolfBoot builds the FIPS target
* with --specs=nosys.specs, whose newlib _sbrk grows unbounded from the linker
* 'end' symbol - toward the unverified image staged at kernel_addr (0x140000).
* Provide our own _sbrk over a fixed static buffer (in .bss, well below the
* image) so heap growth is bounded and can never reach kernel_addr. */
#ifndef CM4_FIPS_HEAP_SIZE
#define CM4_FIPS_HEAP_SIZE (128 * 1024)
#endif
static unsigned char cm4_fips_heap[CM4_FIPS_HEAP_SIZE];
void* _sbrk(int incr);
void* _sbrk(int incr)
{
static unsigned char* brk = cm4_fips_heap;
unsigned char* prev = brk;
if (incr < 0)
return (void*)-1;
if ((size_t)(brk - cm4_fips_heap) + (size_t)incr > sizeof(cm4_fips_heap))
return (void*)-1; /* out of heap */
brk += incr;
return (void*)prev;
}
/* Minimal identity-mapped MMU + caches for the CM4. wolfBoot's simple startup
* runs with the MMU off, so all memory is Device-nGnRnE, which faults on the
* unaligned / 128-bit SIMD accesses the FIPS module and newlib printf perform.
* Mapping DDR as Normal (cacheable) permits those accesses and speeds up the
* crypto; the peripheral region (incl. 0xFE000000) stays Device.
* Four 1GB block descriptors cover the 32-bit VA space at translation level 1. */
#define MMU_BLOCK_NORMAL 0x0000000000000701ULL /* block, AttrIdx0, AF, SH inner */
#define MMU_BLOCK_DEVICE 0x0000000000000405ULL /* block, AttrIdx1, AF, SH none */
static volatile uint64_t cm4_l1_table[512] __attribute__((aligned(4096)));
/* Data-cache maintenance by set/way over all levels to the point of coherency.
* clean != 0 -> clean+invalidate (dc cisw); else invalidate-only (dc isw). */
static void cm4_dcache_maint(int clean)
{
uint64_t clidr, ccsidr;
unsigned int level, loc, ctype, linesize, ways, sets, way, set, wayshift;
__asm__ volatile("dsb sy");
__asm__ volatile("mrs %0, clidr_el1" : "=r"(clidr));
loc = (unsigned int)((clidr >> 24) & 0x7); /* Level of Coherency */
for (level = 0; level < loc; level++) {
ctype = (unsigned int)((clidr >> (level * 3)) & 0x7);
if (ctype < 2) /* no data/unified cache at this level */
continue;
__asm__ volatile("msr csselr_el1, %0" :: "r"((uint64_t)(level << 1)));
__asm__ volatile("isb");
__asm__ volatile("mrs %0, ccsidr_el1" : "=r"(ccsidr));
linesize = (unsigned int)(ccsidr & 0x7) + 4; /* log2(bytes) */
ways = (unsigned int)((ccsidr >> 3) & 0x3FF); /* assoc - 1 */
sets = (unsigned int)((ccsidr >> 13) & 0x7FFF); /* sets - 1 */
/* __builtin_clz(0) is UB; a direct-mapped cache (ways==0) never uses
* the way field (way stays 0), so the shift amount is irrelevant. */
wayshift = (ways == 0) ? 32u : (unsigned int)__builtin_clz(ways);
for (set = 0; set <= sets; set++) {
for (way = 0; way <= ways; way++) {
uint64_t val = ((uint64_t)(level << 1))
| ((uint64_t)way << wayshift)
| ((uint64_t)set << linesize);
if (clean)
__asm__ volatile("dc cisw, %0" :: "r"(val));
else
__asm__ volatile("dc isw, %0" :: "r"(val));
}
}
}
__asm__ volatile("dsb sy");
__asm__ volatile("isb");
}
void cm4_mmu_enable(void)
{
unsigned long sctlr;
int i;
/* 0-3GB DDR -> Normal; 3-4GB peripherals (0xFE000000) -> Device. */
for (i = 0; i < 4; i++) {
uint64_t base = (uint64_t)i << 30;
cm4_l1_table[i] = base | ((i == 3) ? MMU_BLOCK_DEVICE : MMU_BLOCK_NORMAL);
}
/* MAIR: Attr0 = 0xFF Normal WB write-alloc, Attr1 = 0x00 Device-nGnRnE. */
__asm__ volatile("msr mair_el2, %0" :: "r"(0x00000000000000FFUL));
__asm__ volatile("msr ttbr0_el2, %0"
:: "r"((uint64_t)(uintptr_t)cm4_l1_table));
/* TCR_EL2: T0SZ=32 (32-bit VA), 4KB granule, WB cacheable inner-shareable
* table walks, 36-bit PA. */
__asm__ volatile("msr tcr_el2, %0" :: "r"(0x0000000000013520UL));
__asm__ volatile("isb");
__asm__ volatile("tlbi alle2");
__asm__ volatile("dsb sy");
/* Invalidate the D-cache (and I-cache) before enabling them, so no stale
* lines left by an earlier boot stage surface once caching is on. */
cm4_dcache_maint(0);
__asm__ volatile("ic iallu");
__asm__ volatile("dsb sy");
__asm__ volatile("isb");
/* SCTLR_EL2: enable MMU (M), data cache (C), instruction cache (I). */
__asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr));
sctlr |= (1UL << 0) | (1UL << 2) | (1UL << 12);
__asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr));
__asm__ volatile("isb");
}
/* Tear down the MMU/caches before boot handoff: clean the freshly-copied app
* out of the D-cache to memory, disable the MMU and caches, and invalidate the
* I-cache/TLB. Returns the CPU to the MMU-off state the application (and the
* ARM64 Linux boot protocol) expects. */
void cm4_mmu_disable(void)
{
unsigned long sctlr;
cm4_dcache_maint(1); /* clean+invalidate: flush the loaded app to memory */
__asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr));
sctlr &= ~((1UL << 0) | (1UL << 2) | (1UL << 12)); /* clear M, C, I */
__asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr));
__asm__ volatile("isb");
__asm__ volatile("ic iallu");
__asm__ volatile("tlbi alle2");
__asm__ volatile("dsb sy");
__asm__ volatile("isb");
}
#endif /* HAVE_FIPS */
#if defined(DEBUG) && defined(DEBUG_UART)
/* CM4 bring-up diagnostic: exception handler invoked from cm4_vectors in
* src/boot_aarch64_start.S. Dumps the fault syndrome so a data/instruction
* abort shows up over UART instead of hanging silently. Built only with
* DEBUG + DEBUG_UART. ESR_EL2[31:26] = exception class. */
void cm4_fault_handler(unsigned long esr, unsigned long elr, unsigned long far);
void cm4_fault_handler(unsigned long esr, unsigned long elr, unsigned long far)
{
wolfBoot_printf("\n*** CM4 EXCEPTION ***\n");
wolfBoot_printf("ESR_EL2=0x%08x EC=0x%02x\n",
(unsigned)esr, (unsigned)((esr >> 26) & 0x3F));
wolfBoot_printf("ELR_EL2=0x%08x%08x\n",
(unsigned)(elr >> 32), (unsigned)elr);
wolfBoot_printf("FAR_EL2=0x%08x%08x\n",
(unsigned)(far >> 32), (unsigned)far);
}
#endif /* DEBUG && DEBUG_UART */
#if defined(HAVE_FIPS)
/* Upper bound on the busy-wait for the RNG200 FIFO to fill. This is a coarse,
* A72-clock-dependent spin count (not a wall-clock timeout); it only guards
* against a wedged RNG so the seed read cannot hang forever. Tune if needed. */
#ifndef RNG200_FIFO_WAIT_ITERS
#define RNG200_FIFO_WAIT_ITERS 200000000U
#endif
/* FIPS DRBG entropy seed from the BCM2711 RNG200 hardware TRNG. Registered via
* CUSTOM_RAND_GENERATE_SEED in include/user_settings.h. The RNG200 has NIST
* SP800-90B startup/continuous health tests in hardware. */
@ -163,7 +330,7 @@ int wolfBoot_fips_seed(unsigned char* output, unsigned int sz)
/* wait for at least one 32-bit word in the FIFO (bounded) */
guard = 0;
while ((*RNG_FIFO_COUNT & 0xFF) == 0) {
if (++guard > 200000000U) {
if (++guard > RNG200_FIFO_WAIT_ITERS) {
#if defined(DEBUG_UART)
wolfBoot_printf("RNG200 FIFO timeout int=0x%08x ctrl=0x%08x\n",
(unsigned)*RNG_INT_STATUS, (unsigned)*RNG_CTRL);
@ -176,18 +343,6 @@ int wolfBoot_fips_seed(unsigned char* output, unsigned int sz)
for (i = 0; i < n; i++)
output[pos++] = (unsigned char)(word >> (i * 8));
}
#if defined(DEBUG_UART)
{
static int traced = 0;
if (!traced) {
wolfBoot_printf("RNG200 seed %02x %02x %02x %02x %02x %02x %02x %02x\n",
output[0], output[1], output[2], output[3],
output[4], output[5], output[6], output[7]);
traced = 1;
}
}
#endif
return 0;
}
#endif /* HAVE_FIPS */
@ -304,11 +459,16 @@ void sdhci_platform_set_bus_mode(int is_emmc)
* Falls back to the BCM2711 system counter frequency if CNTFRQ_EL0 is 0. */
uint64_t hal_get_timer_us(void)
{
#if defined(__aarch64__)
uint64_t count, freq;
__asm__ volatile("mrs %0, CNTPCT_EL0" : "=r"(count));
__asm__ volatile("mrs %0, CNTFRQ_EL0" : "=r"(freq));
if (freq == 0)
freq = BCM2711_TIMER_CLK_FREQ;
return (uint64_t)(((__uint128_t)count * 1000000ULL) / freq);
#else
/* Non-AArch64 host build (unit tests): the generic timer is unavailable. */
return 0;
#endif
}
#endif /* DISK_SDCARD || DISK_EMMC */

View File

@ -36,7 +36,10 @@
#define BCM2711_MMIO_BASE 0xFE000000
#define BCM2711_GPIO_BASE (BCM2711_MMIO_BASE + 0x200000)
#define BCM2711_UART0_BASE (BCM2711_GPIO_BASE + 0x1000) /* PL011 */
/* Overridable so host unit tests can retarget the SDHCI glue at a mock buffer */
#ifndef BCM2711_EMMC2_BASE
#define BCM2711_EMMC2_BASE (BCM2711_MMIO_BASE + 0x340000) /* Arasan SDHCI */
#endif
#define BCM2711_RNG_BASE (BCM2711_MMIO_BASE + 0x104000) /* RNG200 TRNG */
/* RNG200 (iproc-rng200) register offsets */

View File

@ -122,6 +122,34 @@ static int sim_cryptocb(int devIdArg, wc_CryptoInfo* info, void* ctx)
#include "port/posix/posix_flash_file.h"
#endif /* WOLFBOOT_ENABLE_WOLFHSM_SERVER */
#if defined(HAVE_FIPS)
/* FIPS DRBG entropy seed for the simulator: read from /dev/urandom.
* Registered via CUSTOM_RAND_GENERATE_SEED in include/user_settings.h. */
int wolfBoot_fips_seed(unsigned char* output, unsigned int sz)
{
unsigned int pos = 0;
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0)
return -1;
while (pos < sz) {
ssize_t r = read(fd, output + pos, sz - pos);
if (r < 0) {
if (errno == EINTR)
continue; /* interrupted, retry */
close(fd);
return -1;
}
if (r == 0) { /* unexpected EOF on /dev/urandom */
close(fd);
return -1;
}
pos += (unsigned int)r;
}
close(fd);
return 0;
}
#endif /* HAVE_FIPS */
/* Global pointer to the internal and external flash base */
uint8_t *sim_ram_base;
static uint8_t *flash_base;

View File

@ -43,6 +43,73 @@
#define HAVE_EMPTY_AGGREGATES 0
#define HAVE_ANONYMOUS_INLINE_AGGREGATES 0
#ifdef HAVE_FIPS
/* wolfCrypt FIPS 140-3 module boundary algorithm set. FIPS requires the
* whole validated module (all approved algorithms) to be present so the
* power-on self-tests and in-core integrity check operate. See
* docs/FIPS.md. Enabled via FIPS=1 (-DHAVE_FIPS in options.mk). */
/* WOLFSSL_FIPS_READY selects the evaluation "FIPS-ready" bundle and forces
* HAVE_FIPS_VERSION 7 (settings.h). Gated on WOLFBOOT_FIPS_READY (set by
* FIPS_READY=1, the default) so a production build with the licensed
* validated bundle (FIPS_READY=0) keeps the bundle's own declared version. */
#ifdef WOLFBOOT_FIPS_READY
#define WOLFSSL_FIPS_READY
#endif
/* Single-threaded: the FIPS POST runs at init before any other access, so
* the module's thread-local state is a plain global (no pthread TLS). */
#define NO_THREAD_LS
/* wolfBoot's bare-metal startup does not run C constructors (.init_array),
* so the FIPS module's power-on self-test entry (fipsEntry) must be a
* normal callable function that wolfBoot invokes explicitly at startup. */
#define NO_ATTRIBUTE_CONSTRUCTOR
#define WOLFSSL_BASE16 /* fips_test.c hash hex encode/decode */
#define WOLFSSL_BASE64_ENCODE
#define WOLFSSL_SHA224
#define WOLFSSL_SHA384
#define WOLFSSL_SHA512
#define WOLFSSL_SHA3
#define HAVE_AESGCM
#define HAVE_AESCCM
#define HAVE_AES_ECB /* AES CAST uses wc_AesEcbEncrypt */
#define HAVE_AES_CBC /* AES-CBC CAST */
#define WOLFSSL_AES_COUNTER
#define WOLFSSL_AES_DIRECT
#define WOLFSSL_AES_CFB
#define WOLFSSL_AES_OFB
#define WOLFSSL_AES_XTS
#define WOLFSSL_CMAC
#define HAVE_HKDF
#define HAVE_ECC
#define WOLFSSL_ECDSA_SET_K
#define WOLFSSL_VALIDATE_ECC_IMPORT
#define WOLFSSL_VALIDATE_ECC_KEYGEN
#define WOLFSSL_KEY_GEN
#define WOLFSSL_PUBLIC_MP
#define WOLFSSL_SP_MATH_ALL
/* Use native 64-bit SP digits on 64-bit targets (portable C via __uint128_t;
* NOT the ARM64 SP asm, which sits outside the validated FIPS boundary and
* is disabled by NO_ARM_ASM). Without this, WOLFSSL_SP_MATH_ALL leaves the
* word-size gate further below - which keys off WOLFSSL_HAVE_SP_ECC/RSA,
* neither set in the FIPS build - unselected, so ECDSA verify would regress
* to 32-bit limbs (~4x the multiply work) on the boot-critical path. */
#if !defined(SP_WORD_SIZE) && \
(defined(__aarch64__) || defined(ARCH_RISCV64) || \
(defined(ARCH_x86_64) && !defined(FORCE_32BIT)))
#define HAVE___UINT128_T
#define SP_WORD_SIZE 64
#endif
/* FIPS DRBG entropy seed source. The RNG must NOT be disabled in FIPS mode
* (see the undef block below which removes WC_NO_RNG/WC_NO_HASHDRBG). The
* seed comes from a HAL-provided source: /dev/urandom on sim, the BCM2711
* RNG200 hardware TRNG on CM4. */
#if defined(ARCH_SIM) || defined(TARGET_cm4)
#define CUSTOM_RAND_GENERATE_SEED wolfBoot_fips_seed
extern int wolfBoot_fips_seed(unsigned char* output, unsigned int sz);
#elif !defined(CUSTOM_RAND_GENERATE_SEED)
#error "FIPS=1 needs a DRBG seed: define CUSTOM_RAND_GENERATE_SEED for this target (see docs/FIPS.md)"
#endif
#endif /* HAVE_FIPS */
/* Stdlib Types */
#define CTYPE_USER /* don't let wolfCrypt types.h include ctype.h */
@ -206,7 +273,7 @@ extern int tolower(int c);
!defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \
!defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \
!defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)
# if !defined(WOLFBOOT_TPM)
# if !defined(WOLFBOOT_TPM) && !defined(HAVE_FIPS)
# define NO_ECC_SIGN
# define NO_ECC_DHE
/* Old GNU RX 4.x miscompiled misc.c 64-bit constant-time ops;
@ -449,10 +516,12 @@ extern int tolower(int c);
# endif
#endif
/* Drop SHA-256 when neither the image hash nor any other user needs it */
/* Drop SHA-256 when neither the image hash nor any other user needs it.
* FIPS keeps SHA-256: the module's in-core integrity check is HMAC-SHA-256. */
#if (defined(WOLFBOOT_HASH_SHA384) || defined(WOLFBOOT_HASH_SHA3_384)) && \
!defined(WOLFBOOT_ENABLE_HASH_SHA256) && defined(NO_RSA) && \
!defined(WOLFBOOT_TPM) && !defined(WOLFCRYPT_SECURE_MODE) && \
!defined(HAVE_FIPS) && \
!defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK)
# define NO_SHA256
#endif
@ -605,7 +674,8 @@ extern int tolower(int c);
#if !defined(WOLFCRYPT_SECURE_MODE) && !defined(WOLFBOOT_TPM_PARMENC) && \
!defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \
!defined(WOLFCRYPT_MAX32666_TEST)
!defined(WOLFCRYPT_MAX32666_TEST) && \
!defined(HAVE_FIPS)
#if !(defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \
defined(WOLFBOOT_SIGN_ML_DSA)) && \
!defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)
@ -661,7 +731,8 @@ extern int tolower(int c);
#if !defined(ENCRYPT_WITH_AES128) && !defined(ENCRYPT_WITH_AES256) && \
!defined(WOLFBOOT_TPM_PARMENC) && !defined(WOLFCRYPT_SECURE_MODE) && \
!defined(SECURE_PKCS11) && !defined(WOLFCRYPT_TZ_PSA) && \
!defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK)
!defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \
!defined(HAVE_FIPS)
#define NO_AES
#endif
@ -726,6 +797,25 @@ extern int tolower(int c);
#define NO_CHECK_PRIVATE_KEY
#define NO_KDF
#ifdef HAVE_FIPS
/* The FIPS validated module requires its whole boundary present; undo the
* lean verify-only disables above so the module's approved algorithms and
* their power-on self-tests operate (see docs/FIPS.md). */
#undef NO_HMAC
#undef NO_CMAC
#undef NO_SHA
#undef NO_KDF
#undef NO_ASN
#undef NO_DEV_RANDOM
#undef NO_ECC_KEY_EXPORT
#undef WC_NO_RNG
#undef WC_NO_HASHDRBG
#undef NO_PWDBASED
#undef NO_CODING
#undef NO_AES_CBC /* FIPS AES-CBC CAST needs CBC mode */
#define HAVE_PBKDF2
#endif /* HAVE_FIPS */
/* wolfCrypt Test/Benchmark Configuration */
#ifdef WOLFCRYPT_TEST
/* Skip extended tests to save memory */

View File

@ -1903,3 +1903,89 @@ ifeq ($(ARCH),AARCH64)
endif
endif
endif
# ---------------------------------------------------------------------------
# wolfCrypt FIPS 140-3 module (FIPS=1)
#
# Point WOLFBOOT_LIB_WOLFSSL at an unpacked FIPS / FIPS-ready wolfSSL tree.
# The in-core integrity hash on GCC/ELF is enforced by LINK ORDER:
# wolfcrypt_first.o must be first and wolfcrypt_last.o last, with the FIPS
# boundary (crypto + fips.o + fips_test.o) between them. We therefore rebuild
# WOLFCRYPT_OBJS from scratch in that order, replacing the piecemeal per-SIGN
# selection above (the SIGN/HASH CFLAGS remain in effect). This also drops
# AUX_WOLFCRYPT_OBJS: hybrid / secondary-signature configurations are not
# supported with FIPS=1. See docs/FIPS.md.
ifeq ($(FIPS),1)
# Intercept impossible FIPS build cases early with a clear error. Skip for
# clean-style goals (which do not compile/link) so 'make clean' still works
# with FIPS=1 in .config.
ifeq ($(filter clean distclean keysclean,$(MAKECMDGOALS)),)
# NO_ARM_ASM is an ARM-only knob; the FIPS module is portable C on every
# arch, but only the ARM/AArch64 builds have an asm crypto path to disable
# (which would sit outside the validated module boundary). Force it on rather
# than erroring when unset, so this works regardless of include order
# (test-app/Makefile includes options.mk before arch.mk, so NO_ARM_ASM is
# still empty here). Only an explicit NO_ARM_ASM=0 is a hard error.
ifneq ($(filter ARM AARCH64,$(ARCH)),)
ifeq ($(NO_ARM_ASM),0)
$(error FIPS=1 on $(ARCH) requires NO_ARM_ASM=1 (the FIPS module uses portable-C crypto); NO_ARM_ASM=0 was set)
endif
NO_ARM_ASM=1
endif
# Only the ECDSA object set is wired into the FIPS boundary below. RSA-PSS
# would additionally need rsa.o inside the boundary and its own CI job.
ifeq ($(filter $(SIGN),ECC256 ECC384 ECC521),)
$(error FIPS=1 requires a FIPS-approved ECDSA SIGN (ECC256/ECC384/ECC521, see docs/FIPS.md); got $(SIGN))
endif
endif
CFLAGS+=-DHAVE_FIPS
# Evaluation "FIPS-ready" bundle by default: -DWOLFBOOT_FIPS_READY defines
# WOLFSSL_FIPS_READY, which forces HAVE_FIPS_VERSION 7 in settings.h.
FIPS_READY?=1
ifeq ($(FIPS_READY),1)
CFLAGS+=-DWOLFBOOT_FIPS_READY
else
# Production/validated-bundle path. wolfBoot builds with
# -DWOLFSSL_USER_SETTINGS, so settings.h includes only user_settings.h and
# never the configure-generated wolfssl/options.h where a validated bundle
# supplies HAVE_FIPS_VERSION. Without WOLFSSL_FIPS_READY it would therefore
# fall back to FIPS v1 (140-2) silently, so pin the module version
# explicitly. e.g. FIPS_VERSION=7 for a 140-3 validated bundle.
ifndef FIPS_VERSION
$(error FIPS_READY=0 requires FIPS_VERSION=<n> to pin the validated wolfCrypt FIPS module version (e.g. FIPS_VERSION=7); see docs/FIPS.md)
endif
CFLAGS+=-DHAVE_FIPS_VERSION=$(FIPS_VERSION)
endif
# The FIPS module pulls in libc malloc/printf. On bare-metal targets stub the
# newlib syscalls with nosys.specs; the HAL provides a bounded _sbrk so the
# heap cannot grow into the unverified image (see hal/cm4.c).
ifneq ($(ARCH),sim)
LDFLAGS += --specs=nosys.specs
endif
WCDIR=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src
WOLFCRYPT_OBJS := \
$(WCDIR)/wolfcrypt_first.o \
$(WCDIR)/hash.o \
$(WCDIR)/hmac.o \
$(WCDIR)/kdf.o \
$(WCDIR)/pwdbased.o \
$(WCDIR)/random.o \
$(WCDIR)/sha.o \
$(WCDIR)/sha256.o \
$(WCDIR)/sha512.o \
$(WCDIR)/sha3.o \
$(WCDIR)/aes.o \
$(WCDIR)/cmac.o \
$(WCDIR)/ecc.o \
$(WCDIR)/sp_int.o \
$(WCDIR)/wolfmath.o \
$(WCDIR)/memory.o \
$(WCDIR)/wc_port.o \
$(WCDIR)/logging.o \
$(WCDIR)/error.o \
$(WCDIR)/coding.o \
$(WCDIR)/asn.o \
$(WCDIR)/wc_encrypt.o \
$(WCDIR)/fips.o \
$(WCDIR)/fips_test.o \
$(WCDIR)/wolfcrypt_last.o
endif

View File

@ -1252,6 +1252,11 @@ _vector_table:
// EL == 2?
mov x2, #3 << 20
msr cptr_el2, x2 /* Enable FP/SIMD */
#if defined(TARGET_cm4) && defined(DEBUG) && defined(DEBUG_UART)
ldr x2, =cm4_vectors /* install fault-dump exception vectors (debug) */
msr VBAR_EL2, x2
isb /* ensure new vector base is in effect */
#endif
b 0f
1: mov x0, #3 << 20
@ -1282,6 +1287,45 @@ _vector_table:
#endif
bl boot_entry_C // boot_entry_C never returns
b 7b // go to sleep anyhow in case.
#if defined(TARGET_cm4) && defined(DEBUG) && defined(DEBUG_UART)
/* Minimal EL2 exception vectors for CM4 bring-up debug. On any exception
* (data/instruction abort, etc.) dump ESR/ELR/FAR over UART instead of the
* silent hang the simple startup would otherwise produce. */
.macro CM4_VEC label
.align 7
\label:
b cm4_fault_common
.endm
.align 11
.global cm4_vectors
cm4_vectors:
CM4_VEC cm4_v0 /* Current EL, SP0: Sync */
CM4_VEC cm4_v1 /* IRQ */
CM4_VEC cm4_v2 /* FIQ */
CM4_VEC cm4_v3 /* SError */
CM4_VEC cm4_v4 /* Current EL, SPx: Sync */
CM4_VEC cm4_v5 /* IRQ */
CM4_VEC cm4_v6 /* FIQ */
CM4_VEC cm4_v7 /* SError */
CM4_VEC cm4_v8 /* Lower EL, AArch64: Sync */
CM4_VEC cm4_v9 /* IRQ */
CM4_VEC cm4_v10 /* FIQ */
CM4_VEC cm4_v11 /* SError */
CM4_VEC cm4_v12 /* Lower EL, AArch32: Sync */
CM4_VEC cm4_v13 /* IRQ */
CM4_VEC cm4_v14 /* FIQ */
CM4_VEC cm4_v15 /* SError */
cm4_fault_common:
mrs x0, esr_el2
mrs x1, elr_el2
mrs x2, far_el2
bl cm4_fault_handler /* void cm4_fault_handler(esr, elr, far) */
9: wfi
b 9b
#endif /* TARGET_cm4 && DEBUG && DEBUG_UART */
#if 0
.section ".boot"
.global _vector_table

View File

@ -58,6 +58,22 @@ static volatile const uint32_t __attribute__((used)) wolfboot_version = WOLFBOOT
extern void (** const IV_RAM)(void);
#endif
#ifdef HAVE_FIPS
#include "printf.h"
#include <wolfssl/wolfcrypt/fips_test.h>
/* FIPS module power-on self-test entry. With NO_ATTRIBUTE_CONSTRUCTOR it is a
* plain function (not an .init_array constructor) that wolfBoot calls at boot. */
extern void fipsEntry(void);
/* wolfCrypt FIPS in-core integrity / CAST callback. On a hash mismatch
* (IN_CORE_FIPS_E) the module reports the runtime hash here; copy it into
* verifyCore[] in wolfcrypt/src/fips_test.c and rebuild (see docs/FIPS.md). */
static void wolfBoot_fipsCb(int ok, int err, const char* hash)
{
wolfBoot_printf("FIPS callback: ok=%d err=%d\n", ok, err);
wolfBoot_printf("hash = %s\n", hash != NULL ? hash : "(null)");
}
#endif
#ifdef TARGET_sim
/**
* @brief Command line arguments for the test-app in sim mode.
@ -106,6 +122,21 @@ int main(void)
wolfBoot_hook_preinit();
#endif
hal_init();
#ifdef HAVE_FIPS
/* Run the FIPS power-on self-test (in-core integrity + CASTs) and refuse
* to boot unless the module is operational. See docs/FIPS.md. */
wolfCrypt_SetCb_fips(wolfBoot_fipsCb);
fipsEntry();
if (wolfCrypt_GetStatus_fips() != 0) {
wolfBoot_printf("FIPS 140-3 module NOT operational (status=%d); halting\n",
wolfCrypt_GetStatus_fips());
/* On self-test failure this returns the runtime in-core hash to seal
* into verifyCore[] in fips_test.c. */
wolfBoot_printf("FIPS in-core hash = %s\n", wolfCrypt_GetCoreHash_fips());
wolfBoot_panic();
}
wolfBoot_printf("FIPS 140-3 module operational\n");
#endif
#ifdef TEST_FLASH
hal_flash_test();
#endif

View File

@ -69,12 +69,12 @@ static void uart_putdec(int v)
unsigned int u;
int i = 0;
/* Negate in the unsigned domain: -(unsigned)INT_MIN is well defined,
* unlike negating the signed int (UB for INT_MIN). */
u = (unsigned int)v;
if (v < 0) {
uart_putc('-');
u = (unsigned int)(-v);
}
else {
u = (unsigned int)v;
u = (unsigned int)(-u);
}
do {
buf[i++] = (char)('0' + (u % 10));

View File

@ -66,7 +66,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128
unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-image-dts \
unit-image-dts-sha384 unit-image-dts-sha3-384 unit-store-sbrk \
unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \
unit-sdhci-disk-unaligned unit-sdhci-dma-error unit-sign-encrypted-output \
unit-sdhci-disk-unaligned unit-sdhci-dma-error unit-cm4-sdhci unit-sign-encrypted-output \
unit-sign-hybrid-keyload \
unit-sign-header-failure \
unit-keygen-xmss-params
@ -549,6 +549,10 @@ unit-sdhci-dma-error: ../../include/target.h unit-sdhci-dma-error.c
gcc -o $@ $^ $(CFLAGS) -ffunction-sections -fdata-sections $(LDFLAGS) \
-Wl,--gc-sections
unit-cm4-sdhci: ../../include/target.h unit-cm4-sdhci.c
gcc -o $@ $^ $(CFLAGS) -I../.. -ffunction-sections -fdata-sections $(LDFLAGS) \
-Wl,--gc-sections
unit-aes128: ../../include/target.h unit-extflash.c
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)

View File

@ -0,0 +1,198 @@
/* unit-cm4-sdhci.c
*
* Unit tests for the CM4 (BCM2711 EMMC2) SDHCI register-translation glue in
* hal/cm4.c: sdhci_reg_read/sdhci_reg_write translate the generic driver's
* Cadence-style SRS offsets to the standard Arasan SDHCI layout.
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <check.h>
#include <stdint.h>
#include <string.h>
/* 256-byte mock of the EMMC2 standard SDHCI register window. Retarget the CM4
* HAL's EMMC2 base at it so sdhci_reg_read/write hit host memory, not MMIO. */
static uint8_t cm4_mock_emmc[0x100];
#define BCM2711_EMMC2_BASE ((uintptr_t)cm4_mock_emmc)
/* Compile the CM4 SDHCI glue for the host: pull in only the DISK_SDCARD block. */
#define ARCH_AARCH64
#define DISK_SDCARD 1
#include "../../hal/cm4.c"
/* Cadence SRS offset the generic driver uses (0x200 + std). */
#define SRS(n) (CADENCE_SRS_OFFSET + (n))
static void clear_mock(void)
{
memset(cm4_mock_emmc, 0, sizeof(cm4_mock_emmc));
}
/* SRS10 (std 0x28) write is decomposed into four 8-bit registers. */
START_TEST(test_srs10_byte_decomposition)
{
clear_mock();
sdhci_reg_write(SRS(0x28), 0x44332211);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_HOST_CTRL1], 0x11);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_POWER_CTRL], 0x22);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_BLKGAP_CTRL], 0x33);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_WAKEUP_CTRL], 0x44);
}
END_TEST
/* SRS11 (std 0x2C) write is a 16-bit clock reg + two 8-bit regs. */
START_TEST(test_srs11_split)
{
uint16_t clk;
clear_mock();
sdhci_reg_write(SRS(0x2C), 0xAA55BBCC);
memcpy(&clk, &cm4_mock_emmc[STD_SDHCI_CLK_CTRL], sizeof(clk));
ck_assert_uint_eq(clk, 0xBBCC);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_TIMEOUT_CTRL], 0x55);
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_SW_RESET], 0xAA);
}
END_TEST
/* SRS15 (std 0x3C) write must mask out the unsupported HV4E / A64 bits. */
START_TEST(test_srs15_masks_hv4e_a64)
{
uint32_t v;
clear_mock();
sdhci_reg_write(SRS(STD_SDHCI_HOST_CTRL2),
0x0000FFFF | SDHCI_SRS15_HV4E | SDHCI_SRS15_A64);
memcpy(&v, &cm4_mock_emmc[STD_SDHCI_HOST_CTRL2], sizeof(v));
ck_assert_uint_eq(v & (SDHCI_SRS15_HV4E | SDHCI_SRS15_A64), 0);
ck_assert_uint_eq(v & 0xFFFF, 0xFFFF);
}
END_TEST
/* SRS16 (std 0x40, Capabilities) read must mask out A64S. */
START_TEST(test_srs16_masks_a64s)
{
uint32_t caps;
clear_mock();
/* Pattern with bit 28 clear, so the OR is what sets A64S (load-bearing). */
caps = 0x02345678 | SDHCI_SRS16_A64S;
memcpy(&cm4_mock_emmc[0x40], &caps, sizeof(caps));
ck_assert_uint_eq(sdhci_reg_read(SRS(0x40)) & SDHCI_SRS16_A64S, 0);
ck_assert_uint_eq(sdhci_reg_read(SRS(0x40)), caps & ~SDHCI_SRS16_A64S);
}
END_TEST
/* SRS22 (std 0x58) maps to the legacy SDMA address at std 0x00. */
START_TEST(test_srs22_maps_to_sdma_addr)
{
uint32_t v;
clear_mock();
sdhci_reg_write(SRS(0x58), 0xDEADBEEF);
memcpy(&v, &cm4_mock_emmc[STD_SDHCI_SDMA_ADDR], sizeof(v));
ck_assert_uint_eq(v, 0xDEADBEEF);
ck_assert_uint_eq(sdhci_reg_read(SRS(0x58)), 0xDEADBEEF);
}
END_TEST
/* SRS23 (std 0x5C) has no 64-bit addressing: write is a no-op, read is 0. */
START_TEST(test_srs23_noop)
{
clear_mock();
sdhci_reg_write(SRS(0x5C), 0xFFFFFFFF);
ck_assert_uint_eq(sdhci_reg_read(SRS(0x5C)), 0);
/* nothing was written to the window */
ck_assert_uint_eq(cm4_mock_emmc[0x5C], 0);
}
END_TEST
/* A plain 32-bit SRS register (e.g. SRS01 std 0x04) round-trips unchanged. */
START_TEST(test_plain_reg_roundtrip)
{
uint32_t v;
clear_mock();
sdhci_reg_write(SRS(0x04), 0xCAFEF00D);
memcpy(&v, &cm4_mock_emmc[0x04], sizeof(v));
ck_assert_uint_eq(v, 0xCAFEF00D);
ck_assert_uint_eq(sdhci_reg_read(SRS(0x04)), 0xCAFEF00D);
}
END_TEST
/* Offsets below the SRS window are the HRS region, not present on this Arasan
* block: reads return 0 and do not index the mock. */
START_TEST(test_hrs_read_fallthrough)
{
clear_mock();
cm4_mock_emmc[0x10] = 0xAB; /* a would-be HRS byte */
ck_assert_uint_eq(sdhci_reg_read(0x10), 0);
}
END_TEST
/* Writes below the SRS window are silently dropped (the one place a stray
* offset is discarded). */
START_TEST(test_hrs_write_noop)
{
clear_mock();
sdhci_reg_write(0x10, 0xFFFFFFFF);
ck_assert_uint_eq(cm4_mock_emmc[0x10], 0);
}
END_TEST
/* sdhci_platform_init issues a controller soft reset: SRA is written to the
* standard software-reset register (the poll then spins to timeout on the mock,
* which never clears the bit). */
START_TEST(test_platform_init_soft_reset)
{
clear_mock();
sdhci_platform_init();
ck_assert_uint_eq(cm4_mock_emmc[STD_SDHCI_SW_RESET] & STD_SDHCI_SRA,
STD_SDHCI_SRA);
}
END_TEST
Suite *cm4_sdhci_suite(void)
{
Suite *s = suite_create("cm4-sdhci");
TCase *tc = tcase_create("reg_translation");
tcase_add_test(tc, test_srs10_byte_decomposition);
tcase_add_test(tc, test_srs11_split);
tcase_add_test(tc, test_srs15_masks_hv4e_a64);
tcase_add_test(tc, test_srs16_masks_a64s);
tcase_add_test(tc, test_srs22_maps_to_sdma_addr);
tcase_add_test(tc, test_srs23_noop);
tcase_add_test(tc, test_plain_reg_roundtrip);
tcase_add_test(tc, test_hrs_read_fallthrough);
tcase_add_test(tc, test_hrs_write_noop);
tcase_add_test(tc, test_platform_init_soft_reset);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
Suite *s = cm4_sdhci_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}