From 835e7167e33ed3af6a7bc92c956f4ad9c2c0f8c6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Mon, 15 Jun 2026 13:56:37 -0700 Subject: [PATCH 1/3] Add demo for wolfBoot + wolfIP on AMD UltraScale+ MPSoC (ZCU102) --- zynqmp-zcu102-wolfip/.gitignore | 1 + zynqmp-zcu102-wolfip/README.md | 78 ++++++++++++++++++++++++++++++ zynqmp-zcu102-wolfip/boot.bif.in | 11 +++++ zynqmp-zcu102-wolfip/build.sh | 54 +++++++++++++++++++++ zynqmp-zcu102-wolfip/program-sd.sh | 43 ++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 zynqmp-zcu102-wolfip/.gitignore create mode 100644 zynqmp-zcu102-wolfip/README.md create mode 100644 zynqmp-zcu102-wolfip/boot.bif.in create mode 100755 zynqmp-zcu102-wolfip/build.sh create mode 100755 zynqmp-zcu102-wolfip/program-sd.sh 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." From dfdd01e823d20e55a8f1c931c4c84c5ac0420d2c Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 16 Jun 2026 16:09:37 -0700 Subject: [PATCH 2/3] ZCU102 demo: reproducible build (wolfBoot submodule + wolfip sibling) + turnkey update/partition helpers --- wolfBoot | 2 +- zynqmp-zcu102-wolfip/README.md | 97 +++++++++++++++++++--------- zynqmp-zcu102-wolfip/build.sh | 56 ++++++++++------ zynqmp-zcu102-wolfip/partition-sd.sh | 58 +++++++++++++++++ zynqmp-zcu102-wolfip/program-sd.sh | 27 ++++++-- zynqmp-zcu102-wolfip/update.sh | 37 +++++++++++ 6 files changed, 221 insertions(+), 56 deletions(-) create mode 100755 zynqmp-zcu102-wolfip/partition-sd.sh create mode 100755 zynqmp-zcu102-wolfip/update.sh diff --git a/wolfBoot b/wolfBoot index 75a7e57..61fd552 160000 --- a/wolfBoot +++ b/wolfBoot @@ -1 +1 @@ -Subproject commit 75a7e57279b1c70ca741111705e98aff6307c333 +Subproject commit 61fd55252627595cfff261c4572593286212a10e diff --git a/zynqmp-zcu102-wolfip/README.md b/zynqmp-zcu102-wolfip/README.md index 917ec60..69deb28 100644 --- a/zynqmp-zcu102-wolfip/README.md +++ b/zynqmp-zcu102-wolfip/README.md @@ -1,6 +1,6 @@ # 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. +Secure boot of a bare-metal **wolfIP** TCP/IP application with **wolfBoot** on the ZCU102, booting from SD card. wolfBoot verifies the application's RSA-4096 / SHA3 signature before every boot, and the running application fetches a signed firmware update over the network and applies it to the SD card itself. ## Boot chain @@ -10,9 +10,14 @@ 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. +`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 app runs DHCP, a UDP echo/control service, and the network update logic. -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/`. +## Prerequisites + +- **wolfBoot** - the `../wolfBoot` submodule: `git submodule update --init --recursive wolfBoot` +- **wolfIP** - a sibling clone of [wolfssl/wolfip](https://github.com/wolfSSL/wolfip) (the AMD/Xilinx ports + the ZCU102 OTA support): `git clone https://github.com/wolfSSL/wolfip ../../wolfip` (override with `WOLFIP=/path/to/wolfip`). +- **FSBL / PMUFW / BL31** - build these for the ZCU102 yourself; they are board- and tool-specific. FSBL + PMUFW come from Vitis/PetaLinux for the ZCU102; BL31 from Arm Trusted Firmware (`make PLAT=zynqmp RESET_TO_BL31=1`). Put `zynqmp_fsbl.elf`, `pmufw.elf`, `bl31.elf` in one directory and pass `FW=/that/dir`. +- **Toolchain + tools** - the `aarch64-none-elf-` (bare-metal newlib) GCC and `bootgen` (Vitis) on `PATH`. ## Build @@ -20,59 +25,91 @@ The application is the wolfIP ZCU102 port from the `wolfip` repo, built `EL=2` ( ./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`). +This builds wolfBoot for the ZynqMP SD config (`zynqmp_sdcard.config`, RSA-4096 / SHA3, generating a signing key), builds and signs the `EL=2 LAYOUT=ddr` wolfIP app at **both v1 and v2**, and assembles `out/BOOT.BIN`. Outputs: + +| File | Purpose | +|------|---------| +| `out/BOOT.BIN` | bootloader chain (FSBL+PMUFW+BL31+wolfBoot) | +| `out/wolfip_app_v1_signed.bin` | the app signed v1 - goes to `OFP_A` | +| `out/wolfip_app_v2_signed.bin` / `out/wolfip_update.bin` | signed v2 - the network update | ## Program the SD card -A stock PetaLinux ZCU102 SD card (MBR: boot / OFP_A / OFP_B / rootfs) works as-is. +A stock PetaLinux ZCU102 SD card (MBR: boot / OFP_A / OFP_B / rootfs) works as-is. For a blank or repurposed card, create that layout first (this ERASES the disk): + +``` +SD=/dev/sdX ./partition-sd.sh +``` + +Then write the bootloader + app: ``` 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. +This copies `BOOT.BIN` into the FAT boot partition and `dd`s the signed v1 app to the raw `OFP_A` partition (needs root). Add `WIPE_OFP_B=1` to also clear `OFP_B` so the board boots `A:v1` fresh (handy for demoing the update). 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. +On the serial console (PS-UART0, 115200 8N1) you should see FSBL -> wolfBoot (which verifies the signature) -> 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. +The running app fetches a newer signed image over **TFTP**, writes it to the `OFP_B` SD partition, and resets. The config is version-selecting (`WOLFBOOT_NO_PARTITIONS=1`, "boot the higher version"), so no update flag is needed: wolfBoot verifies both `OFP_A` (v1) and `OFP_B` (v2), boots the higher version, and rolls back to `OFP_A` if `OFP_B` ever fails to verify. -Steps: +What makes this notable is *how* the app reaches the SD card: it 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`), behind a small EL2 platform shim (`boards/zcu102/sdhci_shim.c`: register access, timer, SDMA cache maintenance). One driver, two consumers, no runtime hand-off. -1. Build the higher-version update image: +Run it once the app is at `Ready` (the app fetches from the sender's host, so run this on a machine on the board's subnet that has a TFTP server serving `TFTP_ROOT`): - ``` - VERSION=2 ./build.sh - ``` +``` +BOARD_IP= ./update.sh +``` - This produces `out/wolfip_app_v2_signed.bin` (the same app, signed at version 2). +`update.sh` stages `out/wolfip_update.bin` into the TFTP root (default `/srv/tftp`, override `TFTP_ROOT=`) and sends the `UPDATE` trigger to the board's port 7. -2. Serve it as `wolfip_update.bin` from a TFTP server on the same subnet as the board, e.g. with dnsmasq: +### Expected console - ``` - 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 - ``` +``` +Versions, A:1 B:0 +Attempting boot from P:A +Verifying image signature...done +Firmware Valid. +=== wolfIP ZCU102 (UltraScale+ A53-0 EL2) === +DHCP bound: + IP: 10.0.4.140 +Ready. Try: nc -u 7 + +UDP echo: 6 bytes from 10.0.4.24 +OTA: init SD card... +OTA: SD ready, reading MBR... +OTA: requesting 'wolfip_update.bin' from 10.0.4.24 +OTA: staging update to RAM +...... +OTA: writing 110208 bytes to OFP_B (part 2)... +OTA: update staged to OFP_B +OTA: transfer complete - resetting to apply update <- intentional reset, not a crash + +[board resets] +Versions, A:1 B:2 +Attempting boot from P:B <- now boots the v2 update +Verifying image signature...done +Firmware Valid. +``` -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: +The reset after "update staged" is intentional - the app reboots so wolfBoot re-evaluates and picks the higher version. A tampered or unsigned download simply fails wolfBoot's signature check on the next boot and the board stays on v1. - ``` - printf 'UPDATE' | nc -u -w1 7 - ``` +### Notes -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. +- **Security model** - the `UPDATE` trigger itself is unauthenticated, but every image is RSA-4096 / SHA3 verified by wolfBoot before it ever boots, so the trust boundary is the signature, not the trigger. The worst a rogue trigger can do is cause a download that wolfBoot then rejects. +- **TFTP options** - the client uses 512-byte blocks and windowsize 1: the most compatible settings, which also keep large UDP bursts off the app's poll-driven receive path. ## 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 | +| `build.sh` | Build wolfBoot + sign the app (v1 + v2) + assemble `BOOT.BIN` | +| `program-sd.sh` | Write `BOOT.BIN` + signed app to an SD card (`WIPE_OFP_B=1` for a clean slate) | +| `partition-sd.sh` | Create the demo MBR layout on a blank card | +| `update.sh` | Stage the update image + trigger it over the network | | `boot.bif.in` | bootgen template (FSBL/PMUFW/BL31/wolfBoot) | -| `out/` | Build output (`BOOT.BIN`, `wolfip_app_v_signed.bin`, `wolfboot.elf`) | +| `out/` | Build output | diff --git a/zynqmp-zcu102-wolfip/build.sh b/zynqmp-zcu102-wolfip/build.sh index f89e29b..e224375 100755 --- a/zynqmp-zcu102-wolfip/build.sh +++ b/zynqmp-zcu102-wolfip/build.sh @@ -3,46 +3,63 @@ # 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). +# Produces, in out/: +# BOOT.BIN - bootloader chain (FSBL+PMUFW+BL31+wolfBoot) +# wolfip_app_v1_signed.bin - the app signed v1 (programmed to OFP_A) +# wolfip_app_v2_signed.bin - the same app signed v2 (the network update) +# wolfip_update.bin - a copy of the v2 image (the name update.sh serves) # -# Paths default to ~/GitHub/... ; override any with an env var. Needs the -# aarch64-none-elf toolchain and bootgen (Vitis) on PATH. +# Dependencies (see README.md): +# - wolfBoot: the ../wolfBoot submodule +# (git submodule update --init --recursive) +# - wolfIP: a sibling clone of wolfssl/wolfip (default ../../wolfip) +# - FSBL/PMUFW/BL31: build for the ZCU102 with Vitis/PetaLinux; point FW= at them +# - aarch64-none-elf toolchain and bootgen (Vitis) on PATH +# Override any path with the matching env var. # 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 +WOLFBOOT="${WOLFBOOT:-$HERE/../wolfBoot}" +WOLFIP="${WOLFIP:-$HERE/../../wolfip}" +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}" +UPDATE_VERSION="${UPDATE_VERSION:-2}" MAC5="${MAC5:-0x33}" OUT="$HERE/out" APPDIR="$WOLFIP/src/port/amd/boards/zcu102" mkdir -p "$OUT" +# Fail fast on missing dependencies (clearer than a build error halfway through). +[ -d "$WOLFBOOT/src" ] || { echo "ERROR: wolfBoot not at $WOLFBOOT - run: git submodule update --init --recursive" >&2; exit 1; } +[ -d "$APPDIR" ] || { echo "ERROR: wolfIP port not at $APPDIR - clone wolfssl/wolfip as a sibling or set WOLFIP=" >&2; exit 1; } +[ -f "$FW/zynqmp_fsbl.elf" ] || { echo "ERROR: FSBL/PMUFW/BL31 not in $FW - build them (see README) and set FW=" >&2; exit 1; } + 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. +# Build inside $WOLFBOOT (its Makefile resolves the sign tool via $(PWD)). The +# build generates the signing key, reused so a v2 update verifies against the +# same wolfBoot. 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 ==" +echo "== 2/3 wolfIP app (EL2, DDR, OTA) + sign v1 + v$UPDATE_VERSION ==" # 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. +# 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" +# Sign the same image at v1 (boots from OFP_A) and at the update version (served +# over the network). wolfBoot boots the higher version, so v1 updates to v2. +KEY="$WOLFBOOT/wolfboot_signing_private_key.der" +"$WOLFBOOT/tools/keytools/sign" --rsa4096 --sha3 "$OUT/wolfip_app.bin" "$KEY" 1 +"$WOLFBOOT/tools/keytools/sign" --rsa4096 --sha3 "$OUT/wolfip_app.bin" "$KEY" "$UPDATE_VERSION" +cp "$OUT/wolfip_app_v${UPDATE_VERSION}_signed.bin" "$OUT/wolfip_update.bin" 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" @@ -50,5 +67,6 @@ 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)" +ls -la "$OUT"/BOOT.BIN "$OUT"/wolfip_app_v1_signed.bin "$OUT"/wolfip_update.bin +echo "Next: SD=/dev/sdX ./program-sd.sh (writes BOOT.BIN + the v1 app)" +echo " boot ZCU102 (SW6=SD), then BOARD_IP= ./update.sh to update to v$UPDATE_VERSION" diff --git a/zynqmp-zcu102-wolfip/partition-sd.sh b/zynqmp-zcu102-wolfip/partition-sd.sh new file mode 100755 index 0000000..9e8ad95 --- /dev/null +++ b/zynqmp-zcu102-wolfip/partition-sd.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# +# Partition a BLANK SD card for the ZCU102 wolfBoot + wolfIP demo, matching the +# zynqmp_sdcard.config MBR layout. A stock PetaLinux ZCU102 card already has +# this layout - use this script only when starting from a blank/repurposed card: +# p1 boot 128M FAT32, bootable <- BOOT.BIN +# p2 OFP_A 200M raw <- signed app (primary) +# p3 OFP_B 200M raw <- update slot +# p4 rootfs rest (unused by this bare-metal demo) +# +# DESTRUCTIVE: erases the entire target disk. Double-check with lsblk! +# +# Usage: SD=/dev/sdX ./partition-sd.sh +# +set -euo pipefail + +SD="${SD:?set SD=/dev/sdX (your card reader - NOT a board, NOT your system disk!)}" +[ -b "$SD" ] || { echo "$SD is not a block device" >&2; exit 1; } + +# Partition node suffix: /dev/sdX -> sdX1 ; /dev/mmcblkN|nvmeN|loopN -> ...p1 +case "$SD" in + *[0-9]) P="p" ;; + *) P="" ;; +esac + +# Refuse if anything on the device is mounted. +if lsblk -nro MOUNTPOINT "$SD" | grep -q .; then + echo "ERROR: $SD has mounted partitions - unmount them first:" >&2 + lsblk -o NAME,SIZE,TYPE,MOUNTPOINT "$SD" >&2 + exit 1 +fi + +echo "Target $SD:"; lsblk -o NAME,SIZE,TYPE,LABEL,FSTYPE "$SD" +echo +echo "This ERASES ALL DATA on $SD and writes the ZCU102 demo MBR layout." +read -r -p "Type the device path ($SD) to confirm: " a +[ "$a" = "$SD" ] || { echo "aborted"; exit 1; } + +echo "== writing MBR partition table ==" +# 1 MiB align; 128M boot (FAT32, bootable), 200M OFP_A, 200M OFP_B, rest rootfs. +sudo sfdisk "$SD" <<'EOF' +label: dos +unit: sectors +start=2048, size=262144, type=c, bootable +size=409600, type=83 +size=409600, type=83 +type=83 +EOF + +sudo partprobe "$SD" 2>/dev/null || true +sync; sleep 1 + +echo "== formatting ${SD}${P}1 as FAT32 (boot) ==" +sudo mkfs.vfat -F 32 -n BOOT "${SD}${P}1" >/dev/null + +sync +echo "Done. $SD now has boot / OFP_A / OFP_B / rootfs." +echo "Next: SD=$SD ./program-sd.sh" diff --git a/zynqmp-zcu102-wolfip/program-sd.sh b/zynqmp-zcu102-wolfip/program-sd.sh index b0b19bf..ada7f11 100755 --- a/zynqmp-zcu102-wolfip/program-sd.sh +++ b/zynqmp-zcu102-wolfip/program-sd.sh @@ -13,7 +13,9 @@ # 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) +# Usage: SD=/dev/sdX ./program-sd.sh (X = your card reader, NOT a board) +# WIPE_OFP_B=1 SD=/dev/sdX ./program-sd.sh also zero OFP_B so the board +# boots A:v1 fresh (for a clean A->B update demo) # set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -26,18 +28,31 @@ SD="${SD:?set SD=/dev/sdX (your SD card reader block device - double-check with [ -f "$SIGNED" ] || { echo "missing $SIGNED - run ./build.sh first" >&2; exit 1; } [ -b "$SD" ] || { echo "$SD is not a block device" >&2; exit 1; } +# Partition node suffix: /dev/sdX -> sdX1 ; /dev/mmcblkN|nvmeN|loopN -> ...p1 +case "$SD" in + *[0-9]) P="p" ;; + *) P="" ;; +esac + 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 +read -r -p "Write BOOT.BIN to ${SD}${P}1 (FAT) and the signed app to ${SD}${P}2 (raw)? [y/N] " a [ "$a" = y ] || { echo "aborted"; exit 1; } -echo "== BOOT.BIN -> ${SD}1 (FAT boot partition) ==" +echo "== BOOT.BIN -> ${SD}${P}1 (FAT boot partition) ==" MNT="$(mktemp -d)" -sudo mount "${SD}1" "$MNT" +sudo mount "${SD}${P}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 +echo "== signed app -> ${SD}${P}2 (OFP_A, raw) ==" +sudo dd if="$SIGNED" of="${SD}${P}2" bs=1M conv=fsync status=progress + +# Clean slate: zero the start of OFP_B so wolfBoot sees no valid update there +# (version 0) and boots A:v1, ready for a fresh A->B update demo. +if [ "${WIPE_OFP_B:-0}" = 1 ]; then + echo "== wiping OFP_B header -> ${SD}${P}3 ==" + sudo dd if=/dev/zero of="${SD}${P}3" bs=1M count=1 conv=fsync status=none +fi sync echo "Done. Put the card in the ZCU102, set SW6=SD, and power on." diff --git a/zynqmp-zcu102-wolfip/update.sh b/zynqmp-zcu102-wolfip/update.sh new file mode 100755 index 0000000..616749d --- /dev/null +++ b/zynqmp-zcu102-wolfip/update.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# Trigger the network firmware update for the ZCU102 wolfBoot + wolfIP demo. +# +# Stages the signed update image (out/wolfip_update.bin, from build.sh) into a +# TFTP root, then sends the "UPDATE" trigger to the running app. The app fetches +# the image from the *sender's* host over TFTP, writes it to OFP_B, and resets; +# wolfBoot then verifies and boots the higher version. +# +# Assumes a TFTP server is already running on THIS host and serving $TFTP_ROOT +# (e.g. tftpd-hpa at /srv/tftp), reachable from the board's subnet. (The app +# fetches from the sender's IP, so the TFTP server must be on the machine that +# runs this script.) +# +# Usage: BOARD_IP=10.0.4.140 [TFTP_ROOT=/srv/tftp] ./update.sh +# +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +BOARD_IP="${BOARD_IP:?set BOARD_IP=}" +TFTP_ROOT="${TFTP_ROOT:-/srv/tftp}" +IMG="${IMG:-$HERE/out/wolfip_update.bin}" +PORT="${PORT:-7}" # the app's UDP echo/control port + +[ -f "$IMG" ] || { echo "ERROR: $IMG not found - run ./build.sh first" >&2; exit 1; } +[ -d "$TFTP_ROOT" ] || { echo "ERROR: TFTP_ROOT $TFTP_ROOT is not a directory - start a TFTP server or set TFTP_ROOT=" >&2; exit 1; } +command -v nc >/dev/null || { echo "ERROR: 'nc' (netcat) not found" >&2; exit 1; } + +echo "Staging $(basename "$IMG") -> $TFTP_ROOT/wolfip_update.bin" +cp "$IMG" "$TFTP_ROOT/wolfip_update.bin" + +echo "Triggering update on $BOARD_IP:$PORT ..." +printf 'UPDATE' | nc -u -w1 "$BOARD_IP" "$PORT" + +echo +echo "Watch the board console: it fetches wolfip_update.bin over TFTP, writes" +echo "OFP_B, resets (intentional), and wolfBoot then boots the higher version." From b14fa4c27c9ead3ad716f41dd1540744d4e36730 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 17 Jun 2026 14:12:44 -0700 Subject: [PATCH 3/3] zcu102 demo: address review - keytools fail-fast, sed escaping, mount trap, TFTP writability, README link --- zynqmp-zcu102-wolfip/README.md | 2 +- zynqmp-zcu102-wolfip/build.sh | 15 ++++++++++----- zynqmp-zcu102-wolfip/program-sd.sh | 5 ++++- zynqmp-zcu102-wolfip/update.sh | 1 + 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/zynqmp-zcu102-wolfip/README.md b/zynqmp-zcu102-wolfip/README.md index 69deb28..c95dc62 100644 --- a/zynqmp-zcu102-wolfip/README.md +++ b/zynqmp-zcu102-wolfip/README.md @@ -15,7 +15,7 @@ BootROM -> FSBL -> PMUFW -> BL31 (ATF, EL3) -> wolfBoot (EL2) -> wolfIP app (EL2 ## Prerequisites - **wolfBoot** - the `../wolfBoot` submodule: `git submodule update --init --recursive wolfBoot` -- **wolfIP** - a sibling clone of [wolfssl/wolfip](https://github.com/wolfSSL/wolfip) (the AMD/Xilinx ports + the ZCU102 OTA support): `git clone https://github.com/wolfSSL/wolfip ../../wolfip` (override with `WOLFIP=/path/to/wolfip`). +- **wolfIP** - a sibling clone of [wolfSSL/wolfip](https://github.com/wolfSSL/wolfip) (the AMD/Xilinx ports + the ZCU102 OTA support): `git clone https://github.com/wolfSSL/wolfip ../../wolfip` (override with `WOLFIP=/path/to/wolfip`). - **FSBL / PMUFW / BL31** - build these for the ZCU102 yourself; they are board- and tool-specific. FSBL + PMUFW come from Vitis/PetaLinux for the ZCU102; BL31 from Arm Trusted Firmware (`make PLAT=zynqmp RESET_TO_BL31=1`). Put `zynqmp_fsbl.elf`, `pmufw.elf`, `bl31.elf` in one directory and pass `FW=/that/dir`. - **Toolchain + tools** - the `aarch64-none-elf-` (bare-metal newlib) GCC and `bootgen` (Vitis) on `PATH`. diff --git a/zynqmp-zcu102-wolfip/build.sh b/zynqmp-zcu102-wolfip/build.sh index e224375..9a99929 100755 --- a/zynqmp-zcu102-wolfip/build.sh +++ b/zynqmp-zcu102-wolfip/build.sh @@ -12,7 +12,7 @@ # Dependencies (see README.md): # - wolfBoot: the ../wolfBoot submodule # (git submodule update --init --recursive) -# - wolfIP: a sibling clone of wolfssl/wolfip (default ../../wolfip) +# - wolfIP: a sibling clone of wolfSSL/wolfip (default ../../wolfip) # - FSBL/PMUFW/BL31: build for the ZCU102 with Vitis/PetaLinux; point FW= at them # - aarch64-none-elf toolchain and bootgen (Vitis) on PATH # Override any path with the matching env var. @@ -32,7 +32,7 @@ mkdir -p "$OUT" # Fail fast on missing dependencies (clearer than a build error halfway through). [ -d "$WOLFBOOT/src" ] || { echo "ERROR: wolfBoot not at $WOLFBOOT - run: git submodule update --init --recursive" >&2; exit 1; } -[ -d "$APPDIR" ] || { echo "ERROR: wolfIP port not at $APPDIR - clone wolfssl/wolfip as a sibling or set WOLFIP=" >&2; exit 1; } +[ -d "$APPDIR" ] || { echo "ERROR: wolfIP port not at $APPDIR - clone wolfSSL/wolfip as a sibling or set WOLFIP=" >&2; exit 1; } [ -f "$FW/zynqmp_fsbl.elf" ] || { echo "ERROR: FSBL/PMUFW/BL31 not in $FW - build them (see README) and set FW=" >&2; exit 1; } echo "== 1/3 wolfBoot (ZynqMP SD, RSA4096/SHA3) ==" @@ -41,8 +41,8 @@ cp "$WOLFBOOT/config/examples/zynqmp_sdcard.config" "$WOLFBOOT/.config" # build generates the signing key, reused so a v2 update verifies against the # same wolfBoot. 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 keytools >/dev/null ) +( cd "$WOLFBOOT" && make clean >/dev/null 2>&1 || true ) ( cd "$WOLFBOOT" && make CROSS_COMPILE="$CROSS" wolfboot.elf ) cp "$WOLFBOOT/wolfboot.elf" "$OUT/" @@ -62,7 +62,12 @@ KEY="$WOLFBOOT/wolfboot_signing_private_key.der" cp "$OUT/wolfip_app_v${UPDATE_VERSION}_signed.bin" "$OUT/wolfip_update.bin" 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" +# Escape the replacement strings: '\', '&' and the '|' delimiter are special to +# sed's RHS, so a path containing them would otherwise corrupt boot.bif. +sed_escape() { printf '%s' "$1" | sed -e 's/[\\&|]/\\&/g'; } +sed -e "s|@FW@|$(sed_escape "$FW")|g" \ + -e "s|@WOLFBOOT@|$(sed_escape "$OUT")|g" \ + "$HERE/boot.bif.in" > "$OUT/boot.bif" bootgen -arch zynqmp -image "$OUT/boot.bif" -w on -o "$OUT/BOOT.BIN" echo diff --git a/zynqmp-zcu102-wolfip/program-sd.sh b/zynqmp-zcu102-wolfip/program-sd.sh index ada7f11..61713d1 100755 --- a/zynqmp-zcu102-wolfip/program-sd.sh +++ b/zynqmp-zcu102-wolfip/program-sd.sh @@ -40,9 +40,12 @@ read -r -p "Write BOOT.BIN to ${SD}${P}1 (FAT) and the signed app to ${SD}${P}2 echo "== BOOT.BIN -> ${SD}${P}1 (FAT boot partition) ==" MNT="$(mktemp -d)" +# Ensure the partition is unmounted and the temp dir removed even if a step +# below fails under 'set -e' or the script is interrupted. +trap 'sudo umount "$MNT" 2>/dev/null || true; rmdir "$MNT" 2>/dev/null || true' EXIT sudo mount "${SD}${P}1" "$MNT" sudo cp "$OUT/BOOT.BIN" "$MNT/BOOT.BIN" -sync; sudo umount "$MNT"; rmdir "$MNT" +sync echo "== signed app -> ${SD}${P}2 (OFP_A, raw) ==" sudo dd if="$SIGNED" of="${SD}${P}2" bs=1M conv=fsync status=progress diff --git a/zynqmp-zcu102-wolfip/update.sh b/zynqmp-zcu102-wolfip/update.sh index 616749d..71461ff 100755 --- a/zynqmp-zcu102-wolfip/update.sh +++ b/zynqmp-zcu102-wolfip/update.sh @@ -24,6 +24,7 @@ PORT="${PORT:-7}" # the app's UDP echo/control port [ -f "$IMG" ] || { echo "ERROR: $IMG not found - run ./build.sh first" >&2; exit 1; } [ -d "$TFTP_ROOT" ] || { echo "ERROR: TFTP_ROOT $TFTP_ROOT is not a directory - start a TFTP server or set TFTP_ROOT=" >&2; exit 1; } +[ -w "$TFTP_ROOT" ] || { echo "ERROR: TFTP_ROOT $TFTP_ROOT not writable by $(id -un) - fix perms or set TFTP_ROOT= to a writable dir" >&2; exit 1; } command -v nc >/dev/null || { echo "ERROR: 'nc' (netcat) not found" >&2; exit 1; } echo "Staging $(basename "$IMG") -> $TFTP_ROOT/wolfip_update.bin"