diff --git a/zynqmp-zcu102-wolfip/.gitignore b/zynqmp-zcu102-wolfip/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/zynqmp-zcu102-wolfip/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/zynqmp-zcu102-wolfip/README.md b/zynqmp-zcu102-wolfip/README.md new file mode 100644 index 0000000..917ec60 --- /dev/null +++ b/zynqmp-zcu102-wolfip/README.md @@ -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 + 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 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_signed.bin`, `wolfboot.elf`) | diff --git a/zynqmp-zcu102-wolfip/boot.bif.in b/zynqmp-zcu102-wolfip/boot.bif.in new file mode 100644 index 0000000..42bb1b6 --- /dev/null +++ b/zynqmp-zcu102-wolfip/boot.bif.in @@ -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 +} diff --git a/zynqmp-zcu102-wolfip/build.sh b/zynqmp-zcu102-wolfip/build.sh new file mode 100755 index 0000000..f89e29b --- /dev/null +++ b/zynqmp-zcu102-wolfip/build.sh @@ -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_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)" diff --git a/zynqmp-zcu102-wolfip/program-sd.sh b/zynqmp-zcu102-wolfip/program-sd.sh new file mode 100755 index 0000000..b0b19bf --- /dev/null +++ b/zynqmp-zcu102-wolfip/program-sd.sh @@ -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."