Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/test-configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,13 @@ jobs:
config-file: ./config/examples/polarfire_mpfs250_m.config
pre-build: sh tools/ci/gen_mpfs_libero_stub.sh tools/ci/mpfs_libero_stub
make-args: LIBERO_FPGA_CONFIG_DIR=tools/ci/mpfs_libero_stub
microchip_mpfs250_m_mldsa_test:
uses: ./.github/workflows/test-build-riscv.yml
with:
arch: riscv64
config-file: ./config/examples/polarfire_mpfs250_m.config
pre-build: sh tools/ci/gen_mpfs_libero_stub.sh tools/ci/mpfs_libero_stub
make-args: LIBERO_FPGA_CONFIG_DIR=tools/ci/mpfs_libero_stub SIGN=ML_DSA ML_DSA_LEVEL=5 IMAGE_SIGNATURE_SIZE=4627 IMAGE_HEADER_SIZE=12288 WOLFBOOT_SECTOR_SIZE=0x4000
microchip_mpfs250_m_qspi_test:
uses: ./.github/workflows/test-build-riscv.yml
with:
Expand Down
22 changes: 20 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ else
PRIVATE_KEY?=wolfboot_signing_private_key.der
endif
endif
ifeq ($(FLASH_OTP_KEYSTORE),1)
ifeq ($(SNVM_KEYSTORE),1)
# PolarFire SoC: trust anchor served from secure NVM at runtime.
MPFS_SNVM_OBJ=1
else ifeq ($(FLASH_OTP_KEYSTORE),1)
OBJS+=./src/flash_otp_keystore.o
else ifeq ($(WOLFBOOT_NO_KEYSTORE),1)
CFLAGS+=-DWOLFBOOT_NO_KEYSTORE
Expand All @@ -188,9 +191,22 @@ else
WOLFBOOT_SIGN_KEY_DEP=$(PRIVATE_KEY)
else
OBJS+=./src/keystore.o
# Provisioning build: compiled keys plus the helper that writes them to sNVM.
ifeq ($(SNVM_KEYSTORE_PROVISION),1)
MPFS_SNVM_OBJ=1
endif
endif
endif

# PolarFire SoC: sNVM keystore, PUF KEK and the PUF-wrapped encryption-key
# provider live in one object.
ifeq ($(SNVM_KEK),1)
MPFS_SNVM_OBJ=1
endif
ifeq ($(MPFS_SNVM_OBJ),1)
OBJS+=./hal/mpfs250_snvm.o
endif

WOLFCRYPT_OBJS:=
SECURE_OBJS:=
PUBLIC_KEY_OBJS:=
Expand Down Expand Up @@ -728,7 +744,7 @@ wolfboot_stage1.bin: wolfboot.elf stage1/loader_stage1.bin
$(Q) cp stage1/loader_stage1.bin wolfboot_stage1.bin

wolfboot.elf: include/target.h $(LSCRIPT) $(OBJS) $(BINASSEMBLE) $(WOLFBOOT_SIGN_KEY_DEP) FORCE
$(Q)(test $(SIGN) = NONE) || (test $(FLASH_OTP_KEYSTORE) = 1) || (test "$(WOLFBOOT_NO_KEYSTORE)" = "1") || (grep -q $(SIGN_ALG) src/keystore.c) || \
$(Q)(test $(SIGN) = NONE) || (test $(FLASH_OTP_KEYSTORE) = 1) || (test "$(WOLFBOOT_NO_KEYSTORE)" = "1") || (test "$(SNVM_KEYSTORE)" = "1") || (grep -q $(SIGN_ALG) src/keystore.c) || \
(echo "Key mismatch: please run 'make keysclean' to remove all keys if you want to change algorithm" && false)
@echo "\t[LD] $@"
@echo $(OBJS)
Expand Down Expand Up @@ -768,6 +784,8 @@ $(LSCRIPT): $(LSCRIPT_IN) FORCE
sed -e "s/@WOLFBOOT_L2LIM_SIZE@/$(WOLFBOOT_L2LIM_SIZE)/g" | \
sed -e "s/@L2SRAM_ADDR@/$(L2SRAM_ADDR)/g" | \
sed -e "s/@STACK_SIZE_PER_HART@/$(STACK_SIZE_PER_HART)/g" | \
sed -e "s/@WOLFBOOT_L2SCRATCH_SIZE@/$(WOLFBOOT_L2SCRATCH_SIZE)/g" | \
sed -e "s/@STACK_SIZE@/$(STACK_SIZE)/g" | \
sed -e 's/@WOLFHAL_FLASH_EXCLUDE_TEXT@/$(WOLFHAL_FLASH_EXCLUDE_TEXT)/g' | \
sed -e 's/@WOLFHAL_FLASH_EXCLUDE_RODATA@/$(WOLFHAL_FLASH_EXCLUDE_RODATA)/g' | \
sed -e 's/@WOLFHAL_FLASH_RAM_SECTIONS@/$(WOLFHAL_FLASH_RAM_SECTIONS)/g' \
Expand Down
21 changes: 15 additions & 6 deletions arch.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1167,9 +1167,16 @@ ifeq ($(ARCH),RISCV64)
endif
# Use M-mode specific linker script
LSCRIPT_IN:=hal/$(TARGET)-m.ld
# MPFS DDR init pulls LIBERO_SETTING_* values from a Libero/HSS-generated
# fpga_design_config.h. Setting LIBERO_FPGA_CONFIG_DIR enables DDR init
# and adds the directory to the include search path.
# LIBERO_FPGA_CONFIG_DIR supplies fpga_design_config.h and enables DDR init.
# An S-mode OS runs from DDR, so an empty value would silently build a
# bootloader with no DDR init at all -- fail instead.
ifeq ($(LIBERO_FPGA_CONFIG_DIR),)
ifneq (,$(findstring WOLFBOOT_MMODE_SMODE_BOOT,$(CFLAGS_EXTRA) $(CFLAGS)))
$(error WOLFBOOT_MMODE_SMODE_BOOT requires LIBERO_FPGA_CONFIG_DIR: \
point it at the board's fpga_design_config directory, e.g. \
<hss>/build/boards/mpfs-video-kit/fpga_design_config)
endif
endif
ifneq ($(LIBERO_FPGA_CONFIG_DIR),)
CFLAGS+=-DMPFS_DDR_INIT -I$(LIBERO_FPGA_CONFIG_DIR)
# Generic Cadence DDR controller driver + the MPFS PHY/PLL/training
Expand Down Expand Up @@ -1222,9 +1229,11 @@ ifeq ($(ARCH),RISCV64)
CFLAGS+=-march=rv64imac$(RISCV64_ZICSR)$(RISCV64_ZIFENCEI) -mabi=lp64 -mcmodel=medany
LDFLAGS+=-march=rv64imac -mabi=lp64 -mcmodel=medany
else
# U54 cores: rv64gc (with FPU)
CFLAGS+=-march=rv64imafd$(RISCV64_ZICSR)$(RISCV64_ZIFENCEI) -mabi=lp64d -mcmodel=medany
LDFLAGS+=-march=rv64imafd -mabi=lp64d -mcmodel=medany
# U54: soft-float lp64 to match Microchip's CAL archives, which an lp64d
# build cannot link against. rv64imac not rv64imafd because this toolchain
# ships no F/D-ISA + soft-float multilib; wolfBoot emits no FP anyway.
CFLAGS+=-march=rv64imac$(RISCV64_ZICSR)$(RISCV64_ZIFENCEI) -mabi=lp64 -mcmodel=medany
LDFLAGS+=-march=rv64imac -mabi=lp64 -mcmodel=medany

# FDT support for DDR S-mode (not needed for L2-LIM bare-metal boot)
ifneq ($(MPFS_L2LIM),1)
Expand Down
8 changes: 6 additions & 2 deletions config/examples/polarfire_mpfs250.config
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ WOLFBOOT_LOAD_ADDRESS?=0x8E000000
# Using update_disk loader we just need to specify the partition number or A/B
WOLFBOOT_NO_PARTITIONS=1
WOLFBOOT_RAMBOOT_MAX_SIZE=0x80000000
CFLAGS_EXTRA+=-DBOOT_PART_A=1
CFLAGS_EXTRA+=-DBOOT_PART_B=2
# 0-based GPT index. Index 1 on the stock Microchip layout is the BIOS-boot
# partition HSS reads its own payload from, leaving one usable boot slot: A and
# B name the same one, so failover has no rollback target until a p4 is added.
# For a two-slot layout see polarfire_mpfs250_m.config (A=0, B=1).
CFLAGS_EXTRA+=-DBOOT_PART_A=0
CFLAGS_EXTRA+=-DBOOT_PART_B=0

# ============================================================================
# Optional: read the signed image from a file on a read-only filesystem
Expand Down
24 changes: 12 additions & 12 deletions config/examples/polarfire_mpfs250_m.config
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ SPMATHALL?=1
DUALBANK_SWAP?=0
PKA?=0
ENCRYPT=0
# Decrypt via a staging buffer: see the SDHCI_BLOCK_VIA_PDMA note above.
DISK_DECRYPT_STAGING=1
WOLFTPM?=0
ELF?=1
#DEBUG_ELF?=1
Expand Down Expand Up @@ -77,8 +79,13 @@ CFLAGS_EXTRA+=-DWOLFBOOT_MMODE_SMODE_BOOT
DISK_SDCARD?=1
DISK_EMMC?=0

# wolfBoot in L2 SRAM (256KB available)
# wolfBoot in L2 SRAM: all 4 scratchpad ways (0x0A000000 - 0x0A07FFFF)
WOLFBOOT_ORIGIN?=0x0A000000
WOLFBOOT_L2SCRATCH_SIZE?=512k

# Boot-hart stack, sized for ML-DSA-87 verify on top of the two
# IMAGE_HEADER_SIZE buffers update_disk.c puts on the stack. ECC384 needs less.
STACK_SIZE?=64k

# 4KB sector size (SD card flow is partition-based, not flash-erase-based)
WOLFBOOT_SECTOR_SIZE?=0x1000
Expand Down Expand Up @@ -131,17 +138,10 @@ CFLAGS_EXTRA+=-DSDHCI_SDMA_DISABLED
# race on Arasan/Cadence-family controllers; single-block avoids it.
CFLAGS_EXTRA+=-DSDHCI_FORCE_SINGLE_BLOCK_READ

# Disk-load via PDMA staging. On this board, CPU AXI writes to DDR
# (cached or non-cached) do NOT reliably land at the address that
# subsequent cached reads will fetch from -- empirical alias probe
# showed CPU writes via the 0xC0000000 non-cached window are silently
# dropped, and cached PIO writes appear to allocate L2 lines that are
# never written back to DDR before the integrity-check read.
#
# Workaround: SDHCI PIO into a small L2 Scratch staging buffer, then
# mpfs_pdma_memcpy() copies the block into DDR via the PDMA master.
# PDMA-via-non-cached is the only AXI write path verified to land in
# DDR (the same path used by mpfs_clear_bootup_cache_ways pre-fill).
# Disk-load via PDMA staging: SDHCI PIO into an L2 Scratch staging buffer,
# then mpfs_pdma_memcpy() lands each block in DDR through the PDMA master.
# A CPU copy into DDR reads back wrong depending on access order and code
# layout (cause not identified); the PDMA path has never failed.
CFLAGS_EXTRA+=-DSDHCI_BLOCK_VIA_PDMA

# Video Kit routes the SD slot's Card Detect (CD#) signal through the FPGA
Expand Down
20 changes: 19 additions & 1 deletion docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,10 @@ target-independent `src/ddr_cadence.c` / `include/ddr_cadence.h` (controller bas
board's `LIBERO_SETTING_*` values, stay in `hal/mpfs250_ddr.c`, which builds the controller
register table and composes the generic calls. Both compile only when `MPFS_DDR_INIT` is set.

### PolarFire SoC hardware root of trust (PUF KEK, sNVM keystore, wrapped encryption key)

The System Controller SRAM-PUF, secure NVM (sNVM), and TeraFire crypto can anchor key material in hardware: serve the verification public keys from sNVM, derive a device-unique KEK from the PUF, and store the AES image-encryption key in sNVM wrapped by that KEK. See [polarfire_snvm_puf.md](polarfire_snvm_puf.md).

### PolarFire testing

This section describes how to build the test-application, create a custom uSD with required partitions and copying signed test-application to uSD partitions.
Expand Down Expand Up @@ -1596,7 +1600,7 @@ See the [Encrypted Partitions](encrypted_partitions.md) documentation for additi

#### Configuration

Update your `.config` file with the following ML-DSA settings:
Update your `.config` file with the following ML-DSA settings, or pass them as `make` arguments on top of `config/examples/polarfire_mpfs250_m.config` (the standalone M-mode E51 target verifies ML-DSA-87 this way, with SHA-384 as the image hash):

```makefile
# ML-DSA 87 (Category 5)
Expand Down Expand Up @@ -1651,6 +1655,20 @@ Boot time measurements on PolarFire SoC (RISC-V 64-bit U54 @ 625 MHz) for a 19MB
| ECC384 | SHA384 | ~800 ms | ~2900 ms | ~1500 ms | ~70 ms | ~5.3 seconds |
| ML-DSA 87 | SHA256 | ~835 ms | ~2900 ms | ~2100 ms | ~22 ms | ~5.9 seconds |

Standalone M-mode (`polarfire_mpfs250_m.config`, E51 @ 600 MHz, no HSS) measured from power-on on the Video Kit for the same 19.7 MB FIT, plaintext, ECC384/SHA384, with the DDR training and SD-card load included:

| Phase | Software crypto | Athena offload |
|-----------------------------------------------|-----------------|----------------|
| DDR training, SD init, GPT read | ~1.5 s | ~1.5 s |
| SD load of the FIT (CMD17 single block + PDMA staging) | ~15.5 s | ~15.5 s |
| SHA384 integrity (reads through the non-cached alias) | ~4.1 s | ~4.8 s |
| ECC384 signature verify | ~0.7 s | ~0.7 s |
| FIT kernel copy to its load address (PDMA + read-back verify) | ~9.1 s | ~9.1 s |
| M-mode -> S-mode handoff | ~31 s | ~32 s |
| Linux login prompt | ~53-62 s | ~53 s |

The offload does not shorten the integrity check on this path because the time is in reading the image through the non-cached DDR alias, not in the hashing; measured on a buffer in L2 scratch the Athena SHA384 is about 1.3x the software rate and AES-256-CTR about 4.4x. The SD read and the PDMA copy with its byte-wise verify are the dominant costs; both exist because of the CPU-write-to-DDR coherence workaround (`SDHCI_BLOCK_VIA_PDMA`). ML-DSA-87 on this target (`SIGN=ML_DSA` on `polarfire_mpfs250_m.config`) links to a smaller image than ECC384 because no big-number code is needed.

### PolarFire Soc Debugging

Start GDB server:
Expand Down
2 changes: 2 additions & 0 deletions docs/encrypted_partitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ You can use the `CUSTOM_ENCRYPT_KEY` option to implement your own functions for:
`wolfBoot_get_encrypt_key`, `wolfBoot_set_encrypt_key` and
`wolfBoot_erase_encrypt_key`.

For an example that stores the AES key in non-volatile memory wrapped by a device-unique hardware PUF key (Microchip PolarFire SoC), see [polarfire_snvm_puf.md](polarfire_snvm_puf.md). Note that disk/`NO_PARTITIONS` boot can use `CUSTOM_ENCRYPT_KEY` for image encryption without `EXT_FLASH` or `MMU`.

To enable:

1) Add `CUSTOM_ENCRYPT_KEY=1` to your `.config`
Expand Down
4 changes: 4 additions & 0 deletions docs/keystore.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ wolfBoot supports certain platforms that contain connected HSMs (Hardware Securi

To support this mode of operation, the `keygen` tool supports the `--nolocalkeys` option, which instructs the tool to generate a keystore entry with a zeroed key material. It still generates the `.der` files for private and public keys, so the wolfBoot key tools can sign images, but the `keystore.c` file that is linked into wolfBoot will contain all zeros in the `pubkey` field. Because the key material isn't present in the keystore, the keypair used to sign the image and stored on the HSM for verification can be updated in the field without needing to rebuild wolfBoot against a new `keystore.c`, as long as the signature algorithm and key size does not change. Most targets that use this option will automatically add it to the key generation options or explicitly mention this step in the build documentation.

### Using KeyStore in secure NVM (PolarFire SoC sNVM)

On Microchip PolarFire SoC the trust anchor can be served from the System Controller secure NVM (sNVM) instead of being compiled into the bootloader, using the `SNVM_KEYSTORE` backend. See [polarfire_snvm_puf.md](polarfire_snvm_puf.md).

## Build System Integration

By default, when running `make` to build the default target (`factory.bin`) for the first time, wolfBoot automatically generates a signing keypair and creates a single-key keystore as a "demonstration". This is distinct from using `keygen` directly with `-g` or `-i` options, which provides full control over keystore creation.
Expand Down
Loading
Loading