Add demo for wolfBoot + wolfIP on AMD UltraScale+ MPSoC (ZCU102)

pull/12/head
David Garske 2026-06-15 13:56:37 -07:00
parent 238aef965d
commit 835e7167e3
5 changed files with 187 additions and 0 deletions

View File

@ -0,0 +1 @@
out/

View File

@ -0,0 +1,78 @@
# wolfBoot + wolfIP on AMD/Xilinx ZynqMP (ZCU102)
Secure boot of a bare-metal **wolfIP** TCP/IP application with **wolfBoot** on the ZCU102, booting from SD card. wolfBoot verifies the application's signature before every boot and supports a signed firmware update that the running wolfIP app fetches over the network.
## Boot chain
```
BootROM -> FSBL -> PMUFW -> BL31 (ATF, EL3) -> wolfBoot (EL2) -> wolfIP app (EL2)
|
+-- verify RSA-4096 / SHA3 signature, then load
```
`BOOT.BIN` (on the FAT boot partition) carries FSBL + PMUFW + BL31 + wolfBoot. The wolfIP application is a **separate signed image** on the `OFP_A` SD partition; wolfBoot authenticates it and loads it to DDR `0x10000000` (matching the app's `LAYOUT=ddr` link address), then hands off at EL2.
The application is the wolfIP ZCU102 port from the `wolfip` repo, built `EL=2` (wolfBoot chain-loads at EL2) and `LAYOUT=ddr`. It runs DHCP + a UDP echo demo; see `../../../wolfip/src/port/amd/`.
## Build
```
./build.sh
```
This builds wolfBoot for the ZynqMP SD config (`zynqmp_sdcard.config`, RSA-4096 / SHA3, generating a fresh signing key), builds and signs the `EL=2 LAYOUT=ddr` wolfIP app, and assembles `out/BOOT.BIN`. Prerequisites: the `aarch64-none-elf` toolchain and `bootgen` (Vitis) on `PATH`, and a prebuilt FSBL/PMUFW/BL31 set (`FW=` env, default `~/GitHub/soc-prebuilt-firmware/zcu102-zynqmp`).
## Program the SD card
A stock PetaLinux ZCU102 SD card (MBR: boot / OFP_A / OFP_B / rootfs) works as-is.
```
SD=/dev/sdX ./program-sd.sh
```
Writes `BOOT.BIN` into the FAT boot partition and `dd`s the signed app to the raw `OFP_A` partition (needs root). Then put the card in the ZCU102, set boot-mode `SW6 = SD`, and power on.
## Run
On the serial console (PS-UART0, 115200 8N1) you should see FSBL -> wolfBoot (which prints the signature-verification result) -> the wolfIP banner, DHCP bind, and `Ready`. A modified or unsigned `OFP_A` image fails wolfBoot's check and is not booted.
## Signed firmware update (over the network)
The running wolfIP app fetches a newer signed image over **TFTP**, writes it to the `OFP_B` SD partition, and resets. Because the wolfBoot config is version-selecting (`WOLFBOOT_NO_PARTITIONS=1`, "boot the higher version"), no update flag is needed: wolfBoot verifies both `OFP_A` (v1) and `OFP_B` (v2) on the next boot, picks the higher version, and rolls back to `OFP_A` if `OFP_B` ever fails to verify.
The novel part is that the app re-uses **wolfBoot's own SD-host and disk drivers** (`$WOLFBOOT/src/sdhci.c`, `disk.c`, `gpt.c`) by compiling that same source straight into the application (the `OTA=1` path in the app `Makefile`), backed by a small platform shim (`boards/zcu102/sdhci_shim.c`) that supplies MMIO access, the timer, and SDMA cache maintenance from EL2. There is no runtime hand-off from wolfBoot - the app drives the SD controller itself.
Steps:
1. Build the higher-version update image:
```
VERSION=2 ./build.sh
```
This produces `out/wolfip_app_v2_signed.bin` (the same app, signed at version 2).
2. Serve it as `wolfip_update.bin` from a TFTP server on the same subnet as the board, e.g. with dnsmasq:
```
sudo dnsmasq --no-daemon --enable-tftp --tftp-root=$PWD/out \
--tftp-no-blocksize -i <iface>
cp out/wolfip_app_v2_signed.bin out/wolfip_update.bin
```
3. Trigger the update from any host - send a UDP datagram beginning `UPDATE` to the app's echo port (7); the app fetches `wolfip_update.bin` over TFTP from the **sender's** IP:
```
printf 'UPDATE' | nc -u -w1 <board-ip> 7
```
The serial console shows the TFTP fetch, the `disk_part_write` to `OFP_B`, and the reset; wolfBoot then prints a successful verify of the v2 image and boots it. A tampered or unsigned download simply fails wolfBoot's signature check on the next boot and the board stays on v1.
## Layout
| File | Purpose |
|------|---------|
| `build.sh` | Build wolfBoot + sign the app + assemble `BOOT.BIN` |
| `program-sd.sh` | Write `BOOT.BIN` + signed app to an SD card |
| `boot.bif.in` | bootgen template (FSBL/PMUFW/BL31/wolfBoot) |
| `out/` | Build output (`BOOT.BIN`, `wolfip_app_v<N>_signed.bin`, `wolfboot.elf`) |

View File

@ -0,0 +1,11 @@
// bootgen image for the ZCU102 wolfBoot + wolfIP demo.
// FSBL -> PMUFW -> BL31 (EL3) -> wolfBoot (EL2). The signed wolfIP app is NOT
// inside BOOT.BIN - it lives on the SD OFP_A partition and wolfBoot loads it.
// @FW@ and @WOLFBOOT@ are substituted by build.sh.
the_ROM_image:
{
[bootloader, destination_cpu=a53-0] @FW@/zynqmp_fsbl.elf
[destination_cpu=pmu] @FW@/pmufw.elf
[destination_cpu=a53-0, exception_level=el-3, trustzone] @FW@/bl31.elf
[destination_cpu=a53-0, exception_level=el-2] @WOLFBOOT@/wolfboot.elf
}

View File

@ -0,0 +1,54 @@
#!/usr/bin/env bash
#
# Build the ZCU102 wolfBoot + wolfIP secure-boot demo end to end:
# FSBL -> PMUFW -> BL31 (EL3) -> wolfBoot (EL2) -> signed wolfIP app (EL2).
#
# Produces out/BOOT.BIN (the bootloader chain) and out/wolfip_app_v<N>_signed.bin
# (the signed application image wolfBoot verifies + loads).
#
# Paths default to ~/GitHub/... ; override any with an env var. Needs the
# aarch64-none-elf toolchain and bootgen (Vitis) on PATH.
#
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WOLFBOOT="${WOLFBOOT:-$HOME/GitHub/wolfboot-alt3}"
WOLFIP="${WOLFIP:-$HOME/GitHub/wolfip-alt}"
FW="${FW:-$HOME/GitHub/soc-prebuilt-firmware/zcu102-zynqmp}" # zynqmp_fsbl.elf, pmufw.elf, bl31.elf
CROSS="${CROSS_COMPILE:-aarch64-none-elf-}"
VERSION="${VERSION:-1}"
MAC5="${MAC5:-0x33}"
OUT="$HERE/out"
APPDIR="$WOLFIP/src/port/amd/boards/zcu102"
mkdir -p "$OUT"
echo "== 1/3 wolfBoot (ZynqMP SD, RSA4096/SHA3) =="
cp "$WOLFBOOT/config/examples/zynqmp_sdcard.config" "$WOLFBOOT/.config"
# Build inside $WOLFBOOT (its Makefile resolves the sign tool via $(PWD)).
# Reuse the existing signing key so a VERSION=2 update verifies against the
# same wolfBoot. The first build generates it; if a stale key with a
# different algorithm is present, run 'make keysclean' in wolfBoot once.
( cd "$WOLFBOOT" && make keytools >/dev/null \
&& make clean >/dev/null 2>&1 || true )
( cd "$WOLFBOOT" && make CROSS_COMPILE="$CROSS" wolfboot.elf )
cp "$WOLFBOOT/wolfboot.elf" "$OUT/"
echo "== 2/3 wolfIP app (EL2, DDR, OTA) + sign =="
# OTA=1 compiles wolfBoot's own SD/disk drivers ($WOLFBOOT/src/{sdhci,disk,gpt}.c)
# straight into the app so the running image can fetch a signed update over
# TFTP and stage it to OFP_B itself - no runtime hand-off from wolfBoot.
make -C "$APPDIR" clean >/dev/null 2>&1 || true
make -C "$APPDIR" CROSS_COMPILE="$CROSS" EL=2 LAYOUT=ddr OTA=1 WOLFBOOT="$WOLFBOOT" \
CFLAGS_EXTRA="-DWOLFIP_MAC_5=$MAC5"
"${CROSS}objcopy" -O binary "$APPDIR/app.elf" "$OUT/wolfip_app.bin"
"$WOLFBOOT/tools/keytools/sign" --rsa4096 --sha3 \
"$OUT/wolfip_app.bin" "$WOLFBOOT/wolfboot_signing_private_key.der" "$VERSION"
echo "== 3/3 BOOT.BIN (FSBL+PMUFW+BL31+wolfBoot) =="
sed -e "s|@FW@|$FW|g" -e "s|@WOLFBOOT@|$OUT|g" "$HERE/boot.bif.in" > "$OUT/boot.bif"
bootgen -arch zynqmp -image "$OUT/boot.bif" -w on -o "$OUT/BOOT.BIN"
echo
echo "Done. Artifacts in $OUT:"
ls -la "$OUT"/BOOT.BIN "$OUT"/wolfip_app_v${VERSION}_signed.bin
echo "Next: ./program-sd.sh (then boot ZCU102 from SD, SW6=SD)"

View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
#
# Program an SD card for the ZCU102 wolfBoot + wolfIP demo.
#
# Expects an MBR card laid out like zynqmp_sdcard.config (a stock PetaLinux
# ZCU102 SD card works):
# p1 boot FAT32, bootable <- BOOT.BIN (FSBL+PMUFW+BL31+wolfBoot)
# p2 OFP_A raw <- signed app (wolfBoot's primary image)
# p3 OFP_B raw (update slot - written over the network at run time)
# p4 rootfs (unused by this bare-metal demo)
#
# wolfBoot reads the *raw* OFP_A partition (WOLFBOOT_NO_PARTITIONS, BOOT_PART_A=1),
# so the signed image is dd'd to the start of p2 (this needs root). Copying
# BOOT.BIN into the FAT p1 does not.
#
# Usage: SD=/dev/sdX ./program-sd.sh (X = your card reader, NOT a board)
#
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT="$HERE/out"
VERSION="${VERSION:-1}"
SIGNED="$OUT/wolfip_app_v${VERSION}_signed.bin"
SD="${SD:?set SD=/dev/sdX (your SD card reader block device - double-check with lsblk!)}"
[ -f "$OUT/BOOT.BIN" ] || { echo "missing $OUT/BOOT.BIN - run ./build.sh first" >&2; exit 1; }
[ -f "$SIGNED" ] || { echo "missing $SIGNED - run ./build.sh first" >&2; exit 1; }
[ -b "$SD" ] || { echo "$SD is not a block device" >&2; exit 1; }
echo "Target $SD:"; lsblk -o NAME,SIZE,TYPE,LABEL,FSTYPE "$SD"
read -r -p "Write BOOT.BIN to ${SD}1 (FAT) and the signed app to ${SD}2 (raw)? [y/N] " a
[ "$a" = y ] || { echo "aborted"; exit 1; }
echo "== BOOT.BIN -> ${SD}1 (FAT boot partition) =="
MNT="$(mktemp -d)"
sudo mount "${SD}1" "$MNT"
sudo cp "$OUT/BOOT.BIN" "$MNT/BOOT.BIN"
sync; sudo umount "$MNT"; rmdir "$MNT"
echo "== signed app -> ${SD}2 (OFP_A, raw) =="
sudo dd if="$SIGNED" of="${SD}2" bs=1M conv=fsync status=progress
sync
echo "Done. Put the card in the ZCU102, set SW6=SD, and power on."