diff --git a/.github/workflows/test-build-aarch64.yml b/.github/workflows/test-build-aarch64.yml index 2ace6bcada..090b3f1cc1 100644 --- a/.github/workflows/test-build-aarch64.yml +++ b/.github/workflows/test-build-aarch64.yml @@ -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: @@ -123,6 +129,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}} diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 3325566d6a..da0fd1ec97 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -13,6 +13,11 @@ on: make-args: required: false type: string + fips: + # Download the FIPS-ready wolfSSL tree into ../ before building. + required: false + type: boolean + default: false jobs: @@ -42,6 +47,17 @@ jobs: run: | make -C tools/keytools && make -C tools/bin-assemble + - 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: | make ${{inputs.make-args}} diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 411299218a..14279aa559 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -359,6 +359,42 @@ jobs: config-file: ./config/examples/raspi3-encrypted.config make-args: wolfboot.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_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: diff --git a/.gitignore b/.gitignore index 3139c130af..7d5e59c686 100644 --- a/.gitignore +++ b/.gitignore @@ -422,3 +422,8 @@ sdcard.img # wolfHSM STM32H5 TZ demo build output port/stmicro/stm32h5-tz-wolfhsm/out/ + +# CM4 disk-boot test artifacts (fetched RPi firmware, built/signed payloads) +tools/scripts/cm4/fw/ +tools/scripts/cm4/*.elf +tools/scripts/cm4/*.bin diff --git a/Makefile b/Makefile index a011889aac..367487a27a 100644 --- a/Makefile +++ b/Makefile @@ -310,6 +310,10 @@ ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif +ifeq ($(TARGET),cm4) + MAIN_TARGET:=wolfboot.bin +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie diff --git a/arch.mk b/arch.mk index a507f73226..bb80309c4c 100644 --- a/arch.mk +++ b/arch.mk @@ -169,7 +169,21 @@ ifeq ($(ARCH),AARCH64) SPI_TARGET=nxp endif - # Default ARM ASM setting for unrecognized AARCH64 targets + ifeq ($(TARGET),cm4) + # Raspberry Pi Compute Module 4 - Broadcom BCM2711, Cortex-A72 + ARCH_FLAGS=-mcpu=cortex-a72+crypto -march=armv8-a+crypto -mtune=cortex-a72 + # -mstrict-align: the plain RAM-boot config runs with the MMU off (simple + # startup), where all memory is Device-nGnRnE and unaligned access faults. + # The FIPS / disk configs bring up an identity MMU first (CM4_USE_MMU in + # hal/cm4.c). cm4 defaults to NO_ARM_ASM=1 (portable C, no NEON structure + # loads), so -mstrict-align keeps every config safe either way. + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A72 -mstrict-align + endif + + # Default ARM ASM setting for unrecognized AARCH64 targets. cm4 is excluded + # from the asm path (defaults NO_ARM_ASM=1): the plain config runs MMU-off + # where NEON multi-register loads would fault, and the FIPS path mandates + # portable-C crypto anyway. ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),) NO_ARM_ASM?=1 endif diff --git a/config/examples/cm4.config b/config/examples/cm4.config new file mode 100644 index 0000000000..317093c7d3 --- /dev/null +++ b/config/examples/cm4.config @@ -0,0 +1,28 @@ +# 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 +VTOR?=1 +SPMATH?=1 +IMAGE_HEADER_SIZE?=1024 +PKA?=0 +WOLFTPM?=0 +DEBUG_UART?=0 +NO_XIP?=1 +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE=0x400 +WOLFBOOT_NO_PARTITIONS=1 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x20000000 +WOLFBOOT_LOAD_ADDRESS?=0x3080000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x400000 diff --git a/config/examples/cm4_emmc.config b/config/examples/cm4_emmc.config new file mode 100644 index 0000000000..944d340ccc --- /dev/null +++ b/config/examples/cm4_emmc.config @@ -0,0 +1,54 @@ +# Raspberry Pi CM4 (BCM2711) - onboard eMMC A/B disk boot. +# +# wolfBoot drives the BCM2711 EMMC2 controller (Arasan SDHCI v3.0 @ 0xFE340000) +# to read GPT A/B image partitions from the onboard eMMC via update_disk.c, and +# boots the highest valid version with rollback. +# +# Transfer mode: SDHCI_SDMA_DISABLED forces PIO. The BCM2711 EMMC2 SDMA +# boundary-restart and cache coherency are not yet validated (same Arasan +# family as the Versal quirk); DISK_EMMC compiles in the PIO BRR-race +# workaround in src/sdhci.c. SDHCI_FORCE_CARD_DETECT: the onboard eMMC has no +# routed card-detect line. +# +# Bring-up: uncomment DEBUG_SDHCI (+ DEBUG_DISK / DEBUG_GPT) for verbose +# controller/partition tracing over the mini-UART console. +# +# For wolfCrypt FIPS 140-3, build with FIPS=1 pointing at a FIPS / FIPS-ready +# wolfSSL tree (NO_ARM_ASM required; entropy from the RNG200 TRNG). See +# docs/FIPS.md. 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 +IMAGE_HEADER_SIZE?=1024 +DEBUG?=0 +DEBUG_UART?=1 +DISK_SDCARD?=0 +DISK_EMMC?=1 +CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT +CFLAGS_EXTRA+=-DSDHCI_SDMA_DISABLED +#CFLAGS_EXTRA+=-DDEBUG_SDHCI +#CFLAGS_EXTRA+=-DDEBUG_DISK +#CFLAGS_EXTRA+=-DDEBUG_GPT +EXT_FLASH?=0 +NO_XIP=1 +NO_QNX?=1 +ELF?=1 +VTOR?=1 +SPMATH?=1 +PKA?=0 +WOLFTPM?=0 +WOLFBOOT_NO_PARTITIONS=1 +CFLAGS_EXTRA+=-DBOOT_PART_A=1 +CFLAGS_EXTRA+=-DBOOT_PART_B=2 +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000 +WOLFBOOT_LOAD_ADDRESS?=0x10000000 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x2BC00000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000 +WOLFBOOT_PARTITION_SIZE=0x4000000 +WOLFBOOT_SECTOR_SIZE=0x1000 diff --git a/config/examples/cm4_sdcard.config b/config/examples/cm4_sdcard.config new file mode 100644 index 0000000000..2275ff3e06 --- /dev/null +++ b/config/examples/cm4_sdcard.config @@ -0,0 +1,52 @@ +# 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 +# reads GPT A/B image partitions via update_disk.c and boots the highest valid +# version with rollback. +# +# The EMMC2 glue in hal/cm4.c is hardware-validated on the onboard eMMC +# (see cm4_emmc.config). The microSD path uses the same driver but needs a CM4 +# Lite - an eMMC module disables the carrier's microSD slot. Use +# SDHCI_FORCE_CARD_DETECT for media with no routed card-detect line. +# +# 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 +IMAGE_HEADER_SIZE?=1024 +DEBUG?=0 +DEBUG_UART?=1 +DISK_SDCARD?=1 +DISK_EMMC?=0 +CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT +# Force PIO: the BCM2711 EMMC2 SDMA path is unvalidated and hal/cm4.c provides no +# DMA cache-maintenance hooks, while the disk configs run with the D-cache on +# (CM4_USE_MMU), so SDMA could hash/verify stale DDR. Matches cm4_emmc.config. +CFLAGS_EXTRA+=-DSDHCI_SDMA_DISABLED +EXT_FLASH?=0 +NO_XIP=1 +NO_QNX?=1 +ELF?=1 +VTOR?=1 +SPMATH?=1 +PKA?=0 +WOLFTPM?=0 +WOLFBOOT_NO_PARTITIONS=1 +CFLAGS_EXTRA+=-DBOOT_PART_A=1 +CFLAGS_EXTRA+=-DBOOT_PART_B=2 +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000 +WOLFBOOT_LOAD_ADDRESS?=0x10000000 +WOLFBOOT_RAMBOOT_MAX_SIZE=0x2BC00000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000 +WOLFBOOT_PARTITION_SIZE=0x4000000 +WOLFBOOT_SECTOR_SIZE=0x1000 diff --git a/config/examples/sim-fips.config b/config/examples/sim-fips.config new file mode 100644 index 0000000000..360331ad51 --- /dev/null +++ b/config/examples/sim-fips.config @@ -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 diff --git a/docs/FIPS.md b/docs/FIPS.md new file mode 100644 index 0000000000..25792fa0cd --- /dev/null +++ b/docs/FIPS.md @@ -0,0 +1,87 @@ +# 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()`) before halting. +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. + +Two practical traps when re-sealing (both cost time on the CM4 bring-up): + +- `verifyCore[]` can be sealed via a build define instead of editing the FIPS tree: `CFLAGS_EXTRA="-DWOLFCRYPT_FIPS_CORE_HASH_VALUE="` (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. +- `make clean` removes `$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/*.o`. If you build with `WOLFBOOT_LIB_WOLFSSL=` 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. + +## 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. diff --git a/docs/README.md b/docs/README.md index d86720d9c7..cbe7a1f33a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. diff --git a/docs/Targets.md b/docs/Targets.md index 35324a7163..01e073c686 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -6,6 +6,7 @@ This README describes configuration of supported targets. * [Simulated](#simulated) * [Cortex-A53 / Raspberry PI 3](#cortex-a53--raspberry-pi-3-experimental) +* [Cortex-A72 / Raspberry Pi Compute Module 4](#cortex-a72--raspberry-pi-compute-module-4-bcm2711) * [Cypress PSoC-6](#cypress-psoc-6) * [Infineon AURIX TC3xx](#infineon-aurix-tc3xx) * [Intel x86-64 Intel FSP](#intel-x86_64-with-intel-fsp-support) @@ -3695,6 +3696,81 @@ qemu-system-aarch64 -M raspi3b -m 1024 -serial stdio -kernel wolfboot_linux_rasp ``` +## Cortex-A72 / Raspberry Pi Compute Module 4 (BCM2711) + +wolfBoot runs on the Raspberry Pi Compute Module 4 (CM4), a Broadcom BCM2711 with a quad-core Cortex-A72 (AArch64). wolfBoot takes the place of the second-stage OS loader: the BCM2711 boot ROM loads the VideoCore firmware, the firmware loads `kernel8.img` from the boot partition, and that `kernel8.img` is wolfBoot. wolfBoot then verifies the signed application image and boots it, extending the platform root of trust into the OS. + +``` +BCM2711 boot ROM -> SPI EEPROM bootloader -> VideoCore firmware (start4.elf) + -> kernel8.img (wolfBoot) -> verify (ECDSA/SHA) -> application +``` + +On CM4 modules with onboard eMMC the boot files live on the eMMC FAT boot partition; on CM4 Lite they live on a microSD. Either way the medium hangs off the BCM2711 EMMC2 controller (a standard SDHCI v3.0 Arasan block at `0xFE340000`). + +### Building + +``` +cp config/examples/cm4.config .config +make CROSS_COMPILE=aarch64-none-elf- DEBUG_UART=1 +``` + +`cm4.config` defaults `DEBUG_UART?=0` for a silent release build; the `DEBUG_UART=1` above enables the boot log shown under "Boot output". 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. + +### Signing and assembling the boot image + +Sign the application, then concatenate wolfBoot and the signed image so the signed image lands at `kernel_addr` (`0x2C0000` = `0x200000` load + `0xC0000`): + +``` +make keytools tools/bin-assemble/bin-assemble +IMAGE_HEADER_SIZE=1024 ./tools/keytools/sign --ecc384 --sha384 \ + app.bin wolfboot_signing_private_key.der 1 +tools/bin-assemble/bin-assemble kernel8.img \ + 0x0 wolfboot.bin \ + 0xC0000 app_v1_signed.bin +``` + +### config.txt + +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. + +``` +arm_64bit=1 +kernel=kernel8.img +enable_uart=1 +uart_2ndstage=1 +dtoverlay=disable-bt +init_uart_clock=48000000 +init_uart_baud=115200 +``` + +### Flashing + +- CM4 Lite: write `kernel8.img` + the RPi firmware (`start4.elf`, `fixup4.dat`) + `config.txt` to the microSD FAT boot partition. +- CM4 with eMMC: put the module in USB boot mode (nRPIBOOT), run `rpiboot` to expose the eMMC as USB mass storage, and write the same files to its FAT boot partition. See https://github.com/raspberrypi/usbboot . + +### Boot output + +With `DEBUG_UART=1`, a successful authenticated boot prints (115200 8N1): + +``` +wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL2 +Trying partition 0 at 0x2C0000 +Checking integrity...done +Verifying signature...done +Firmware Valid +Booting at 0x3080000 +``` + +### Optional: eMMC/SD A/B disk boot + +`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. + +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). + +### 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 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=` (a full rebuild shifts the module boundary and the hash); see [FIPS.md](FIPS.md) for the full build, entropy, and hash-sealing procedure. + ## Xilinx Zynq UltraScale AMD Zynq UltraScale+ MPSoC ZCU102 Evaluation Kit - Quad-core ARM Cortex-A53 (plus dual Cortex-R5). diff --git a/hal/cm4.c b/hal/cm4.c new file mode 100644 index 0000000000..8554c4b319 --- /dev/null +++ b/hal/cm4.c @@ -0,0 +1,549 @@ +/* cm4.c + * + * HAL for the Raspberry Pi Compute Module 4 (CM4): Broadcom BCM2711, + * quad-core Cortex-A72 (ARMv8-A). + * + * The VideoCore GPU firmware loads wolfBoot (an ARM64 kernel8.img carrying the + * Linux image header) to 0x200000 and enters it at EL2; wolfBoot verifies the + * signed payload and boots it from RAM, or from eMMC/SD A/B via the generic + * SDHCI driver (at the end of this file). hal_flash_* are no-ops (no in-place + * flash in this mode). + * + * 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 +#include +#include +#include +#include "image.h" +#include "printf.h" +#include "hal/cm4.h" +#ifndef ARCH_AARCH64 +# error "wolfBoot cm4 HAL: wrong architecture selected. Please compile with ARCH=AARCH64." +#endif + +/* Hardware register map (bases, UART, EMMC2/SDHCI) is in hal/cm4.h */ + +/* Fixed addresses (provided by the linker script) */ +extern void *kernel_addr, *update_addr, *dts_addr; + +/* Enable the identity MMU + caches when the build does more than the trivial + * RAM-boot: FIPS (unaligned/SIMD in the module), or the disk path (optimized + * code + SDHCI block-buffer memcpy fault on MMU-off Device memory). Normal + * cacheable memory permits those accesses and speeds up crypto/disk reads. */ +#if (defined(HAVE_FIPS) || defined(DISK_SDCARD) || defined(DISK_EMMC)) \ + && defined(__aarch64__) +#define CM4_USE_MMU +#endif + +#if defined(CM4_USE_MMU) +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) +/* Console UART select. On this bench CM4 the debug cable on GPIO14/15 is the + * BCM2711 mini-UART (AUX, Linux ttyS0), so that is the default. Boards where + * dtoverlay=disable-bt actually routes the PL011 onto GPIO14/15 can build with + * CM4_UART_PL011 to use the PL011 (0xFE201000) instead. */ +#if defined(CM4_UART_PL011) +static void uart_tx(char c) +{ + while (*UART0_FR & 0x20) /* TXFF: wait while FIFO full */ + ; + *UART0_DR = (unsigned int)(unsigned char)c; +} + +void uart_init(void) +{ + /* Program the PL011 for 115200 8N1 assuming a 48MHz UARTCLK + * (init_uart_clock=48000000): BAUDDIV = 48e6/(16*115200) -> IBRD 26 FBRD 3. */ + *UART0_CR = 0; + *UART0_ICR = 0x7FF; + *UART0_IBRD = 26; + *UART0_FBRD = 3; + *UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); /* FIFO, 8-bit */ + *UART0_CR = (1 << 0) | (1 << 8) | (1 << 9); /* enable UART, TX, RX */ +} +#else /* mini-UART (default) */ +static void uart_tx(char c) +{ + while ((*MU_LSR & MU_LSR_TXFF_EMPTY) == 0) /* wait until TX can accept */ + ; + *MU_IO = (unsigned int)(unsigned char)c; +} + +void uart_init(void) +{ + /* The firmware has already enabled the mini-UART at a stable baud + * (enable_uart=1 fixes core_freq), so - like the Linux 8250 console with + * "skip-init" - wolfBoot inherits that setup and just writes AUX_MU_IO. + * Reprogramming the baud here is unnecessary (and error-prone: the mini-UART + * clock is core_freq-derived, not a fixed rate). */ +} +#endif /* CM4_UART_PL011 */ + +void uart_write(const char* buf, uint32_t sz) +{ + while (sz-- > 0 && *buf) + uart_tx(*buf++); +} +#endif /* DEBUG_UART */ + +void* hal_get_primary_address(void) +{ + return (void*)&kernel_addr; +} + +void* hal_get_update_address(void) +{ + return (void*)&update_addr; +} + +void* hal_get_dts_address(void) +{ + return (void*)&dts_addr; +} + +void* hal_get_dts_update_address(void) +{ + return NULL; /* Not yet supported */ +} + +#ifdef EXT_FLASH +int ext_flash_read(unsigned long address, uint8_t *data, int len) +{ + XMEMCPY(data, (void *)address, len); + return len; +} + +int ext_flash_erase(unsigned long address, int len) +{ + XMEMSET((void *)address, 0xFF, len); + return len; +} + +int ext_flash_write(unsigned long address, const uint8_t *data, int len) +{ + XMEMCPY((void *)address, data, len); + return len; +} + +void ext_flash_lock(void) +{ +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + +void hal_init(void) +{ +#if defined(DEBUG_UART) + unsigned long el; + /* The banner is emitted before cm4_mmu_enable() on purpose - it is the + * earliest bring-up signal. This is safe because wolfBoot's uart_printf + * (src/string.c) is built -mstrict-align and performs no unaligned / SIMD + * access; do NOT route the banner through a libc printf, which would fault + * on the MMU-off Device memory this runs on. */ + uart_init(); + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + wolfBoot_printf("wolfBoot CM4 (BCM2711 Cortex-A72) hal_init, EL%d\n", + (int)((el >> 2) & 0x3)); +#endif +#if defined(CM4_USE_MMU) + /* Bring up Normal cacheable memory before any code that uses unaligned / + * SIMD accesses (FIPS module, optimized disk path) which the MMU-off + * Device memory rejects. */ + cm4_mmu_enable(); +#endif +} + +void hal_prepare_boot(void) +{ +#if defined(CM4_USE_MMU) + /* Undo cm4_mmu_enable() before handoff: flush the loaded image 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 (0x2C0000). + * 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) { + /* Heap trim: newlib's malloc returns memory on free() with a negative + * increment. Clamp to the heap base so brk cannot underflow. */ + if ((size_t)(-incr) > (size_t)(brk - cm4_fips_heap)) + brk = cm4_fips_heap; + else + brk += incr; + return (void*)prev; + } + 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; +} +#endif /* HAVE_FIPS */ + +#if defined(CM4_USE_MMU) +/* 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 that the FIPS module, newlib printf, and + * the optimized disk/SDHCI code paths perform. Mapping DDR as Normal + * (cacheable) permits those accesses and speeds up crypto/disk reads; 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"); +} + +/* MMU/cache setup uses EL2 system registers; wolfBoot enters at EL2 on the CM4. + * Guard against an EL1 entry (a custom armstub) so the msr *_el2 below do not + * trap silently before anything can be reported. */ +static void cm4_require_el2(void) +{ + unsigned long el; + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + if (((el >> 2) & 0x3) != 2) { +#if defined(DEBUG_UART) + wolfBoot_printf("cm4: MMU setup requires EL2 (running at EL%d); halting\n", + (int)((el >> 2) & 0x3)); +#endif + while (1) + __asm__ volatile("wfi"); + } +} + +void cm4_mmu_enable(void) +{ + unsigned long sctlr; + int i; + + cm4_require_el2(); + /* 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. Bits 31 and 23 are RES1 for TCR_EL2 (E2H==0) and + * must be written as 1. */ + __asm__ volatile("msr tcr_el2, %0" + :: "r"(0x0000000000013520UL | (1UL << 31) | (1UL << 23))); + __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_require_el2(); + /* Flush the loaded app to DRAM WHILE the D-cache is still enabled, then + * disable M/C/I together. The "textbook" order (clear SCTLR.C first, then + * flush) is UNSAFE here: cm4_dcache_maint() and this function use the stack, + * and once C is cleared, stack reads bypass the cache and return stale DRAM + * (the dirty lines - including this function's spilled return address - are + * not yet written back), so the function would return to garbage. That + * order is only safe in a pure-asm flush with no stack use (U-Boot). What + * must be coherent for the application is the loaded image, and it is fully + * flushed here with caches on. */ + 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 /* CM4_USE_MMU */ + +#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. */ +int wolfBoot_fips_seed(unsigned char* output, unsigned int sz) +{ + static int rng_inited = 0; + unsigned int pos = 0; + unsigned int guard; + + if (!rng_inited) { + /* iproc-rng200 bring-up: disable RBG, soft-reset the RBG then RNG + * cores, clear pending interrupt status, then re-enable the RBG. */ + *RNG_CTRL = 0; + *RNG_RBG_SOFT_RESET = 1; + *RNG_RBG_SOFT_RESET = 0; + *RNG_SOFT_RESET = 1; + *RNG_SOFT_RESET = 0; + *RNG_INT_STATUS = 0xFFFFFFFF; /* write-1-to-clear all pending status */ + *RNG_CTRL = RNG200_CTRL_RBGEN; + rng_inited = 1; + } + + while (pos < sz) { + unsigned int word, n, i; + /* wait for at least one 32-bit word in the FIFO (bounded) */ + guard = 0; + while ((*RNG_FIFO_COUNT & 0xFF) == 0) { + 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); +#endif + return -1; + } + } + word = *RNG_FIFO_DATA; + n = (sz - pos) < 4 ? (sz - pos) : 4; + for (i = 0; i < n; i++) + output[pos++] = (unsigned char)(word >> (i * 8)); + } + return 0; +} +#endif /* HAVE_FIPS */ + +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + (void)address; (void)data; (void)len; + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) +{ + (void)address; (void)len; + return 0; +} + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +/* BCM2711 EMMC2 platform glue for the generic SDHCI driver (src/sdhci.c). + * EMMC2 is a standard SDHCI v3.0 Arasan block at 0xFE340000. The driver uses + * Cadence-style SRS offsets (0x200 + std); translate them to the standard + * Arasan layout, mirroring the ZynqMP path in hal/zynq.c. The GPU firmware has + * already configured the EMMC2 clock/pinmux, so only a controller soft reset is + * needed here (hardware-validated on CM4 eMMC). */ +#include "sdhci.h" + +uint32_t sdhci_reg_read(uint32_t offset) +{ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + + if (offset >= CADENCE_SRS_OFFSET) { + uint32_t std_off = offset - CADENCE_SRS_OFFSET; + uint32_t val; + + if (std_off == 0x58) /* SRS22 -> legacy SDMA address (SRS00) */ + return *((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR)); + if (std_off == 0x5C) /* SRS23: no 64-bit addressing on v3.0 */ + return 0; + val = *((volatile uint32_t *)(base + std_off)); + if (std_off == 0x40) /* SRS16 Capabilities: mask A64S (no HV4E) */ + val &= ~SDHCI_SRS16_A64S; + return val; + } + return 0; /* HRS region not present on this Arasan block */ +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + uint32_t std_off; + + if (offset < CADENCE_SRS_OFFSET) + return; /* HRS region not present */ + std_off = offset - CADENCE_SRS_OFFSET; + + /* SRS10 (0x28): Host Control 1 / Power / Block Gap / Wakeup (8-bit each) */ + if (std_off == 0x28) { + *((volatile uint8_t *)(base + STD_SDHCI_HOST_CTRL1)) = (uint8_t)val; + *((volatile uint8_t *)(base + STD_SDHCI_POWER_CTRL)) = (uint8_t)(val >> 8); + *((volatile uint8_t *)(base + STD_SDHCI_BLKGAP_CTRL)) = (uint8_t)(val >> 16); + *((volatile uint8_t *)(base + STD_SDHCI_WAKEUP_CTRL)) = (uint8_t)(val >> 24); + return; + } + /* SRS11 (0x2C): Clock Control (16-bit) / Timeout / Software Reset */ + if (std_off == 0x2C) { + *((volatile uint16_t *)(base + STD_SDHCI_CLK_CTRL)) = (uint16_t)val; + *((volatile uint8_t *)(base + STD_SDHCI_TIMEOUT_CTRL)) = (uint8_t)(val >> 16); + *((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) = (uint8_t)(val >> 24); + return; + } + if (std_off == 0x58) { /* SRS22 -> legacy SDMA address; write restarts DMA */ + *((volatile uint32_t *)(base + STD_SDHCI_SDMA_ADDR)) = val; + return; + } + if (std_off == 0x5C) /* SRS23: no 64-bit addressing on v3.0 */ + return; + if (std_off == STD_SDHCI_HOST_CTRL2) /* SRS15: mask unsupported HV4E/A64 */ + val &= ~(SDHCI_SRS15_HV4E | SDHCI_SRS15_A64); + *((volatile uint32_t *)(base + std_off)) = val; +} + +void sdhci_platform_init(void) +{ + /* The GPU firmware already brought up the EMMC2 clock/pinmux; just issue a + * controller soft reset and wait for it to clear. */ + volatile uint8_t *base = (volatile uint8_t *)BCM2711_EMMC2_BASE; + volatile int i; + + *((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) = STD_SDHCI_SRA; + for (i = 0; i < 100000; i++) { + if ((*((volatile uint8_t *)(base + STD_SDHCI_SW_RESET)) & STD_SDHCI_SRA) == 0) + break; + } +} + +void sdhci_platform_irq_init(void) +{ + /* Polled mode; no IRQ wiring needed for the boot path. */ +} + +void sdhci_platform_set_bus_mode(int is_emmc) +{ + (void)is_emmc; /* handled by the generic driver via Host Control 1 */ +} + +/* Microseconds from the ARMv8 generic timer (SDHCI udelay + disk updater). + * 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 */ diff --git a/hal/cm4.h b/hal/cm4.h new file mode 100644 index 0000000000..ac64bfc125 --- /dev/null +++ b/hal/cm4.h @@ -0,0 +1,101 @@ +/* cm4.h + * + * 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 + */ + +/* Raspberry Pi Compute Module 4 (CM4) - Broadcom BCM2711, Cortex-A72. + * Hardware register map (bases, UART, EMMC2/SDHCI) shared by the HAL and the + * test application. This header is also included from boot_aarch64_start.S, so + * pointer-cast accessors are guarded with __ASSEMBLER__. */ + +#ifndef _CM4_H_ +#define _CM4_H_ + +/* Select the compact AArch64 startup path in boot_aarch64_start.S */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +/* BCM2711 peripheral base addresses (low-peripheral 0xFE000000 view). + * Integer literals - safe to include from assembly. */ +#define BCM2711_MMIO_BASE 0xFE000000 +#define BCM2711_GPIO_BASE (BCM2711_MMIO_BASE + 0x200000) +#define BCM2711_UART0_BASE (BCM2711_GPIO_BASE + 0x1000) /* PL011 */ +/* The debug cable on GPIO14/15 is the BCM2711 mini-UART (AUX / 8250-style, + * Linux ttyS0), NOT the PL011. The firmware leaves it enabled with a stable + * baud (enable_uart=1 fixes core_freq), so wolfBoot inherits it. */ +#define BCM2711_AUX_BASE (BCM2711_GPIO_BASE + 0x15000) /* AUX (mini-UART) */ +/* 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 */ +#define RNG200_CTRL 0x00 /* bit0 RBGEN = enable */ +#define RNG200_SOFT_RESET 0x04 +#define RNG200_RBG_SOFT_RESET 0x08 +#define RNG200_INT_STATUS 0x18 +#define RNG200_FIFO_DATA 0x20 +#define RNG200_FIFO_COUNT 0x24 /* [7:0] = words available */ +#define RNG200_CTRL_RBGEN 0x00000001 + +/* BCM2711 system counter frequency (fallback if CNTFRQ_EL0 reads 0) */ +#define BCM2711_TIMER_CLK_FREQ 54000000ULL + +/* SDHCI register offsets used by the EMMC2 glue. The generic SDHCI driver + * addresses registers with Cadence-style SRS offsets (0x200 + std); the glue + * translates to the standard Arasan layout below. Integers, assembler-safe. */ +#define CADENCE_SRS_OFFSET 0x200 +#define STD_SDHCI_SDMA_ADDR 0x00 /* SDMA System Address (32-bit) */ +#define STD_SDHCI_HOST_CTRL1 0x28 +#define STD_SDHCI_POWER_CTRL 0x29 +#define STD_SDHCI_BLKGAP_CTRL 0x2A +#define STD_SDHCI_WAKEUP_CTRL 0x2B +#define STD_SDHCI_CLK_CTRL 0x2C +#define STD_SDHCI_TIMEOUT_CTRL 0x2E +#define STD_SDHCI_SW_RESET 0x2F +#define STD_SDHCI_HOST_CTRL2 0x3C +#define STD_SDHCI_SRA 0x01 /* Software Reset for All */ + +#ifndef __ASSEMBLER__ +/* PL011 UART0 register accessors (not the debug console on this board) */ +#define UART0_DR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x00)) +#define UART0_FR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x18)) +#define UART0_IBRD ((volatile unsigned int*)(BCM2711_UART0_BASE+0x24)) +#define UART0_FBRD ((volatile unsigned int*)(BCM2711_UART0_BASE+0x28)) +#define UART0_LCRH ((volatile unsigned int*)(BCM2711_UART0_BASE+0x2C)) +#define UART0_CR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x30)) +#define UART0_ICR ((volatile unsigned int*)(BCM2711_UART0_BASE+0x44)) + +/* Mini-UART (AUX) register accessors - the actual debug console (Linux ttyS0). + * 8250/16550-style: IO at +0x40 (data), LSR at +0x54 (bit5 = TX can accept). */ +#define MU_IO ((volatile unsigned int*)(BCM2711_AUX_BASE+0x40)) +#define MU_LSR ((volatile unsigned int*)(BCM2711_AUX_BASE+0x54)) +#define MU_LSR_TXFF_EMPTY 0x20 /* transmit FIFO can accept a byte */ + +/* RNG200 hardware TRNG register accessors */ +#define RNG_CTRL ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_CTRL)) +#define RNG_SOFT_RESET ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_SOFT_RESET)) +#define RNG_RBG_SOFT_RESET ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_RBG_SOFT_RESET)) +#define RNG_INT_STATUS ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_INT_STATUS)) +#define RNG_FIFO_DATA ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_FIFO_DATA)) +#define RNG_FIFO_COUNT ((volatile unsigned int*)(BCM2711_RNG_BASE+RNG200_FIFO_COUNT)) +#endif /* !__ASSEMBLER__ */ + +#endif /* _CM4_H_ */ diff --git a/hal/cm4.ld b/hal/cm4.ld new file mode 100644 index 0000000000..015213c46a --- /dev/null +++ b/hal/cm4.ld @@ -0,0 +1,88 @@ +/* cm4.ld + * + * Linker script for the Raspberry Pi Compute Module 4 (BCM2711, Cortex-A72). + * + * The VideoCore GPU firmware loads kernel8.img (an ARM64 image with the Linux + * kernel header) to the 2MB-aligned address 0x200000 and enters it in place at + * EL2 - it does NOT relocate a header image down to 0x80000. So wolfBoot links + * at 0x200000. DDR_MEM length covers the 1GB low-peripheral view common to all + * CM4 RAM variants (1/2/4/8 GB); the peripheral block sits above at 0xFE000000 + * and is not mapped here. The initial stack grows down from 0x200000 into the + * ~2MB of free low DDR below the image. + */ +MEMORY +{ + DDR_MEM(rwx): ORIGIN = 0x00200000, LENGTH = 0x3be00000 +} +ENTRY(_vector_table); + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.boot*)) + *(.text*) + *(.rodata*) + KEEP(*(.keystore)) + *(.note.*) + . = ALIGN(4); + _end_text = .; + } > DDR_MEM + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > DDR_MEM + + .data : + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > DDR_MEM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > DDR_MEM + . = ALIGN(8); + /* Heap start for the newlib sbrk (FIPS module malloc). Grows up into free + * DDR; the stack lives at END_STACK (0x200000) and grows down. */ + PROVIDE(end = .); + PROVIDE(__end__ = .); + PROVIDE(_heap_start = .); +} + +END_STACK = _start_text; +/* RAM-boot staging addresses for the non-disk configs: the firmware loads + * kernel8.img (wolfBoot + concatenated signed app) to 0x200000, so the app + * lands at 0x200000 + its bin-assemble offset. These sit above the wolfBoot + * image/heap; the initial stack grows down from 0x200000. (The DISK_EMMC / + * DISK_SDCARD configs ignore these - they read A/B images from the eMMC/SD to + * WOLFBOOT_LOAD_ADDRESS instead.) */ +kernel_addr = 0x02C0000; /* 0x200000 load + 0xC0000 bin-assemble offset */ +update_addr = 0x12C0000; +dts_addr = 0x0280000; +kernel_load_addr = 0x20000000; +dts_load_addr = 0x21000000; + +_wolfboot_partition_boot_address = kernel_addr; +_wolfboot_partition_update_address = update_addr; + +/* Guard the RAM-boot staging addresses: the image (.text/.data/.bss, including + * the 128KB FIPS heap) must stay below dts_addr (the lower of dts_addr and + * kernel_addr). An overflow would not fail the link - it would silently corrupt + * the staged DTB or the unverified application at runtime. */ +ASSERT(_end <= dts_addr, + "cm4: image + heap overruns dts_addr - raise dts_addr/kernel_addr in hal/cm4.ld") diff --git a/hal/sim.c b/hal/sim.c index 63bf96d92f..6393e07e25 100644 --- a/hal/sim.c +++ b/hal/sim.c @@ -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; diff --git a/include/user_settings.h b/include/user_settings.h index 5c59ae3ed1..340b9e182e 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -43,6 +43,61 @@ #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 + /* 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 */ @@ -195,7 +250,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 /* For Renesas RX do not enable the misc.c constant time code @@ -434,10 +489,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 @@ -589,7 +646,8 @@ extern int tolower(int c); #endif #if !defined(WOLFCRYPT_SECURE_MODE) && !defined(WOLFBOOT_TPM_PARMENC) && \ - !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) + !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK) && \ + !defined(HAVE_FIPS) #if !(defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \ defined(WOLFBOOT_SIGN_ML_DSA)) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) @@ -636,7 +694,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 @@ -699,6 +758,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 */ diff --git a/options.mk b/options.mk index c3d6b75bd6..ffaff4b02f 100644 --- a/options.mk +++ b/options.mk @@ -1835,3 +1835,75 @@ endif # includers (test-app), where a self-referencing += would not terminate. AUX_WOLFCRYPT_OBJS_NEW:=$(filter-out $(WOLFCRYPT_OBJS),$(sort $(AUX_WOLFCRYPT_OBJS))) WOLFCRYPT_OBJS+=$(AUX_WOLFCRYPT_OBJS_NEW) +# --------------------------------------------------------------------------- +# 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). + ifneq ($(filter ARM AARCH64,$(ARCH)),) + ifneq ($(NO_ARM_ASM),1) + $(error FIPS=1 on $(ARCH) requires NO_ARM_ASM=1 (the FIPS module uses portable-C crypto)) + endif + 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 (defines WOLFSSL_FIPS_READY, which + # forces HAVE_FIPS_VERSION 7). A production build with the licensed validated + # bundle passes FIPS_READY=0 so the bundle declares its own module version. + FIPS_READY?=1 + ifeq ($(FIPS_READY),1) + CFLAGS+=-DWOLFBOOT_FIPS_READY + 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 diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 58066d1424..305570b916 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -38,6 +38,10 @@ #include "hal/raspi3.h" #endif +#ifdef TARGET_cm4 +#include "hal/cm4.h" +#endif + /* GICv2 Register Offsets */ #ifndef GICD_BASE #define GICD_BASE 0xF9010000 @@ -1147,10 +1151,45 @@ FPUStatus: .align 8 #elif defined(USE_SIMPLE_STARTUP) .section ".boot" + .global _vector_table _vector_table: +#if defined(TARGET_cm4) + /* ARM64 Linux kernel image header (Documentation/arm64/booting.rst). The RPi + * VideoCore firmware (start4.elf) only enters a 64-bit kernel that carries + * this header (branch at off 0, "ARM\x64" magic at off 56); it then runs the + * image in place at its 2MB-aligned load address (0x200000) rather than + * relocating to 0x80000. code0 branches to the real entry. */ + b _cm4_entry + .long 0 /* code1 */ + .quad 0 /* text_offset */ + .quad _end - _vector_table /* image_size */ + .quad 0xA /* flags: LE, 4KB pages, load anywhere */ + .quad 0 /* res2 */ + .quad 0 /* res3 */ + .quad 0 /* res4 */ + .ascii "ARM\x64" /* magic 0x644d5241 at offset 56 */ + .long 0 /* res5 */ +_cm4_entry: +#endif mov x21, x0 // read ATAG/FDT address +#if defined(TARGET_cm4) + /* Load-address self-check: wolfBoot is linked absolutely at 0x200000 and is + * NOT position independent (the ldr =_vector_table below loads the absolute + * stack top). Compare the runtime address (PC-relative adr) with the linked + * address (from the literal pool); a mismatch means the firmware placed the + * image at a different 2MB base, so halt loudly instead of executing from the + * wrong VMA. The generated config.txt pins kernel_address=0x200000. */ + adr x2, _vector_table + ldr x3, =_vector_table + cmp x2, x3 + b.eq 5f +6: wfi + b 6b +5: +#endif + 4: ldr x1, =_vector_table // get start of .text in x1 // Read current EL mrs x0, CurrentEL @@ -1172,6 +1211,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 @@ -1188,6 +1232,58 @@ _vector_table: 8: mov sp, x1 // set stack pointer 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: + /* Switch to a dedicated fault stack before calling the C handler: the + * exception may have been taken with SP unset (the window before 'mov sp,x1' + * or on a secondary core that never sets SP) or with SP overflowed, and + * cm4_fault_handler needs a valid stack for its wolfBoot_printf calls. */ + ldr x3, =cm4_fault_stack_top + mov sp, x3 + 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 + +.section ".bss" +.balign 16 +cm4_fault_stack: + .skip 1024 +cm4_fault_stack_top: +.section ".boot" +#endif /* TARGET_cm4 && DEBUG && DEBUG_UART */ #if 0 .section ".boot" .global _vector_table diff --git a/src/loader.c b/src/loader.c index 3c26f4db42..211b58cf93 100644 --- a/src/loader.c +++ b/src/loader.c @@ -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 +/* 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 diff --git a/test-app/app_cm4.c b/test-app/app_cm4.c new file mode 100644 index 0000000000..d88f13eefd --- /dev/null +++ b/test-app/app_cm4.c @@ -0,0 +1,83 @@ +/* app_cm4.c + * + * Test application for Raspberry Pi CM4 (BCM2711). Prints a banner over the + * mini-UART (the console wired to GPIO14/15; build with CM4_UART_PL011 to use + * the PL011 instead) and halts. wolfBoot's FIPS power-on self-test and in-core + * integrity check run in the bootloader (src/loader.c), not in the app. + * + * 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 +#include "wolfboot/wolfboot.h" +#include "hal/cm4.h" /* BCM2711 UART register map */ + +#ifdef TARGET_cm4 + +#if defined(CM4_UART_PL011) +static void uart_init(void) +{ + /* PL011 for 115200 8N1 from the 48 MHz UART clock (see hal/cm4.c) */ + *UART0_CR = 0; + *UART0_ICR = 0x7FF; + *UART0_IBRD = 26; + *UART0_FBRD = 3; + *UART0_LCRH = (1 << 4) | (1 << 5) | (1 << 6); + *UART0_CR = (1 << 0) | (1 << 8) | (1 << 9); +} + +static void uart_putc(char c) +{ + while (*UART0_FR & 0x20) /* wait while TX FIFO full */ + ; + *UART0_DR = (unsigned int)c; +} +#else /* mini-UART (default) - inherit the firmware's enabled console */ +static void uart_init(void) +{ + /* The firmware leaves the mini-UART enabled at a stable baud; wolfBoot ran + * on it too. Nothing to program - just write AUX_MU_IO. */ +} + +static void uart_putc(char c) +{ + while ((*MU_LSR & MU_LSR_TXFF_EMPTY) == 0) /* wait until TX can accept */ + ; + *MU_IO = (unsigned int)c; +} +#endif /* CM4_UART_PL011 */ + +static void uart_puts(const char* s) +{ + while (*s) { + if (*s == '\n') + uart_putc('\r'); + uart_putc(*s++); + } +} + +void main(void) +{ + uart_init(); + uart_puts("\n=== wolfBoot CM4 test-app ===\n"); + uart_puts("test-app done; halting.\n"); + while (1) + ; +} +#endif /* TARGET_cm4 */ diff --git a/tools/scripts/cm4/disk_app.S b/tools/scripts/cm4/disk_app.S new file mode 100644 index 0000000000..1558498e36 --- /dev/null +++ b/tools/scripts/cm4/disk_app.S @@ -0,0 +1,30 @@ +/* disk_app.S - minimal CM4 disk-boot test payload. + * + * Prints a banner over the BCM2711 mini-UART (AUX @ 0xFE215040 - the console + * wired to GPIO14/15 on the CM4, i.e. Linux ttyS0; NOT the PL011) and spins. + * Linked at 0x10000000 (WOLFBOOT_LOAD_ADDRESS for cm4_emmc.config). Signed and + * written raw into the eMMC A/B image partitions to prove wolfBoot's disk-boot + * path (read from eMMC -> verify -> ELF load -> handoff). + * + * Build: aarch64-none-elf-gcc -nostdlib -nostartfiles \ + * -Wl,-Ttext=0x10000000 -o disk_app.elf disk_app.S + * + * Copyright (C) 2026 wolfSSL Inc. GPLv3 (see wolfBoot COPYING). + */ +.section .text +.global _start +_start: + movz x1, #0xFE21, lsl #16 + movk x1, #0x5000 /* x1 = 0xFE215000 (AUX mini-UART base) */ + adr x0, msg +1: ldrb w2, [x0], #1 + cbz w2, 2f +3: ldr w3, [x1, #0x54] /* AUX_MU_LSR: bit5 = TX can accept */ + tbz w3, #5, 3b + str w2, [x1, #0x40] /* AUX_MU_IO (data) */ + b 1b +2: wfi + b 2b +.align 3 +msg: + .asciz "\r\n>>> CM4 DISK APP OK: wolfBoot read + verified + booted from eMMC <<<\r\n" diff --git a/tools/scripts/cm4/prepare_emmc.sh b/tools/scripts/cm4/prepare_emmc.sh new file mode 100755 index 0000000000..18f543aa7c --- /dev/null +++ b/tools/scripts/cm4/prepare_emmc.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# prepare_emmc.sh - build the eMMC/SD layout for the CM4 wolfBoot disk-boot test. +# +# Creates a GPT with: +# p1 = boot (FAT32) : RPi firmware (start4.elf, fixup4.dat), config.txt, +# kernel8.img (= wolfBoot, built from cm4_emmc.config) +# p2 = image_a (raw) : signed disk_app ELF, version 1 (BOOT_PART_A=1) +# p3 = image_b (raw) : signed disk_app ELF, version 2 (BOOT_PART_B=2) +# +# wolfBoot (loaded to RAM by the RPi firmware) then drives the BCM2711 EMMC2 +# controller to read p2/p3, verifies ECC384/SHA384, and boots the higher +# version. Prove-out prints ">>> CM4 DISK APP OK <<<" over the mini-UART (AUX). +# +# Usage: +# # 1) build wolfBoot first: +# # cp config/examples/cm4_emmc.config .config +# # make wolfboot.bin CROSS_COMPILE=aarch64-none-elf- (add DEBUG_SDHCI for bring-up) +# # 2) prep artifacts only (no root): +# # bash tools/scripts/cm4/prepare_emmc.sh +# # 3) with the eMMC presented via rpiboot as /dev/sdN, write it (root): +# # sudo bash tools/scripts/cm4/prepare_emmc.sh /dev/sdN +# +# Copyright (C) 2026 wolfSSL Inc. GPLv3 (see wolfBoot COPYING). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" # wolfboot repo root +HERE="$ROOT/tools/scripts/cm4" +CROSS="${CROSS_COMPILE:-aarch64-none-elf-}" +KEY="${PRIVATE_KEY:-$ROOT/wolfboot_signing_private_key.der}" +IMG_HDR="${IMAGE_HEADER_SIZE:-1024}" +# Pin the RPi firmware to a specific ref for reproducibility - these blobs are +# the first link of the boot chain. Override with FWREF= (a tag like +# 1.20240529, or a full commit sha). +FWREF="${FWREF:-1.20240529}" +FWBASE="https://raw.githubusercontent.com/raspberrypi/firmware/${FWREF}/boot" +DEV="${1:-}" + +# ---- artifact prep (no root) ---------------------------------------------- +prep_artifacts() { + # Idempotent: if everything is already staged (e.g. prepped without sudo, + # then re-run under sudo to write the device where the cross-toolchain is + # not in PATH), skip the rebuild. Set FORCE_PREP=1 to force. + if [ "${FORCE_PREP:-0}" != "1" ] && \ + [ -f "$HERE/disk_app_v1_signed.bin" ] && \ + [ -f "$HERE/disk_app_v2_signed.bin" ] && \ + [ -f "$HERE/fw/kernel8.img" ] && [ -f "$HERE/fw/start4.elf" ] && \ + [ -f "$HERE/fw/config.txt" ]; then + echo "== artifacts already staged in $HERE (skip prep; FORCE_PREP=1 to rebuild) ==" + return 0 + fi + + [ -f "$ROOT/wolfboot.bin" ] || { echo "!! build wolfboot.bin first (cm4_emmc.config)"; exit 1; } + [ -f "$KEY" ] || { echo "!! signing key not found: $KEY"; exit 1; } + + echo "== building + signing disk_app (v1, v2) ==" + "${CROSS}gcc" -nostdlib -nostartfiles -Wl,-Ttext=0x10000000 \ + -o "$HERE/disk_app.elf" "$HERE/disk_app.S" + ( cd "$ROOT" && \ + IMAGE_HEADER_SIZE="$IMG_HDR" ./tools/keytools/sign --ecc384 --sha384 \ + "$HERE/disk_app.elf" "$KEY" 1 >/dev/null && \ + IMAGE_HEADER_SIZE="$IMG_HDR" ./tools/keytools/sign --ecc384 --sha384 \ + "$HERE/disk_app.elf" "$KEY" 2 >/dev/null ) + ls -la "$HERE"/disk_app_v1_signed.bin "$HERE"/disk_app_v2_signed.bin + + echo "== fetching RPi BCM2711 firmware ==" + for f in start4.elf fixup4.dat bcm2711-rpi-cm4.dtb; do + [ -f "$HERE/fw/$f" ] || { mkdir -p "$HERE/fw"; curl -fsSL -o "$HERE/fw/$f" "$FWBASE/$f"; } + done + + cat > "$HERE/fw/config.txt" <<'EOF' +arm_64bit=1 +kernel=kernel8.img +# wolfBoot is linked absolutely at 0x200000; pin the load address to match. +kernel_address=0x200000 +enable_uart=1 +uart_2ndstage=1 +dtoverlay=disable-bt +init_uart_clock=48000000 +init_uart_baud=115200 +EOF + cp "$ROOT/wolfboot.bin" "$HERE/fw/kernel8.img" + echo "== artifacts ready in $HERE (fw/ + disk_app_v{1,2}_signed.bin) ==" +} + +# ---- device write (root) -------------------------------------------------- +write_device() { + local t root_src root_disk + [ "$(id -u)" -eq 0 ] || { echo "!! writing $DEV requires root"; exit 1; } + for t in sgdisk partprobe mkfs.vfat lsblk udevadm; do + command -v "$t" >/dev/null || { echo "!! missing required tool: $t"; exit 1; } + done + [ -b "$DEV" ] || { echo "!! not a block device: $DEV"; exit 1; } + # Refuse the disk currently backing '/' (a real safety check, not a + # bench-specific node name). Override with ALLOW_ANY_DISK=1. + root_src="$(findmnt -no SOURCE / 2>/dev/null || true)" + root_disk="/dev/$(lsblk -no PKNAME "$root_src" 2>/dev/null | head -1)" + if [ "${ALLOW_ANY_DISK:-0}" != "1" ] && [ "$DEV" = "$root_disk" ]; then + echo "!! refusing to erase the root disk $DEV (set ALLOW_ANY_DISK=1 to override)"; exit 1 + fi + echo "== target: $DEV =="; lsblk -o NAME,SIZE,FSTYPE,LABEL "$DEV" + read -rp "Repartition and ERASE $DEV? [type YES] " ok + [ "$ok" = "YES" ] || { echo "aborted"; exit 1; } + + # A/B partitions are 32M - far larger than the signed disk_app test payload + # (~64KB). They intentionally need not match WOLFBOOT_PARTITION_SIZE (0x4000000 + # = 64M) in cm4_emmc.config: the disk path reads only the image at the + # partition start, so the partition just has to be big enough to hold it. + echo "== GPT: p1 boot 128M / p2 image_a 32M / p3 image_b 32M ==" + sgdisk --zap-all "$DEV" >/dev/null + sgdisk -n 1:2048:+128M -t 1:0700 -c 1:boot "$DEV" >/dev/null + sgdisk -n 2:0:+32M -t 2:8300 -c 2:image_a "$DEV" >/dev/null + sgdisk -n 3:0:+32M -t 3:8300 -c 3:image_b "$DEV" >/dev/null + partprobe "$DEV"; udevadm settle + P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3" + # some kernels use pN naming for nvme/mmc; handle 'p' suffix + [ -b "$P1" ] || { P1="${DEV}p1"; P2="${DEV}p2"; P3="${DEV}p3"; } + + echo "== FAT boot partition ($P1) ==" + mkfs.vfat -n BOOT "$P1" >/dev/null + MP=$(mktemp -d) + # Clean up the mount point even if mount/cp fails (set -e stays active). + trap 'mountpoint -q "$MP" && umount "$MP"; rmdir "$MP" 2>/dev/null || true' RETURN + mount "$P1" "$MP" + cp "$HERE"/fw/start4.elf "$HERE"/fw/fixup4.dat "$HERE"/fw/bcm2711-rpi-cm4.dtb \ + "$HERE"/fw/config.txt "$HERE"/fw/kernel8.img "$MP"/ + sync; umount "$MP" + + echo "== raw signed images -> A ($P2, v1), B ($P3, v2) ==" + dd if="$HERE/disk_app_v1_signed.bin" of="$P2" bs=4k conv=fsync status=none + dd if="$HERE/disk_app_v2_signed.bin" of="$P3" bs=4k conv=fsync status=none + sync + echo "== done. Set BOOT switch OFF, power-cycle, and watch the CM4 mini-UART console. ==" +} + +prep_artifacts +# if/else (not an AND-OR list): calling write_device as the left operand of +# '&&' would disable set -e for its whole body, so a failed sgdisk/dd would be +# silently ignored and a half-written eMMC reported as success. +if [ -n "$DEV" ]; then + write_device +else + echo "(no device given; artifacts only. Re-run with: sudo bash $0 /dev/sdN)" +fi diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 5374d28a80..865b455637 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -60,7 +60,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-update-disk unit-update-disk-oob unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \ unit-image-nopart unit-image-sha384 unit-image-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-sign-encrypted-output \ + unit-sdhci-disk-unaligned unit-cm4-sdhci unit-sign-encrypted-output \ unit-keygen-xmss-params TESTS+=unit-tpm-check-rot-auth TESTS+=unit-tpm-api-names @@ -408,6 +408,10 @@ unit-sdhci-disk-unaligned: ../../include/target.h unit-sdhci-disk-unaligned.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) diff --git a/tools/unit-tests/unit-cm4-sdhci.c b/tools/unit-tests/unit-cm4-sdhci.c new file mode 100644 index 0000000000..1d206a2b3b --- /dev/null +++ b/tools/unit-tests/unit-cm4-sdhci.c @@ -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 +#include +#include + +/* 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; +}