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
36 changes: 36 additions & 0 deletions arch.mk
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,42 @@ ifeq ($(ARCH),AARCH64)
CFLAGS+=-DWOLFBOOT_ZYNQMP_CSU
endif

ifeq ($(ZYNQMP_FSBL),1)
# wolfBoot fully replaces the Xilinx FSBL: the BootROM authenticates and
# loads wolfBoot into OCM at EL3, and wolfBoot runs psu_init() to bring up
# the PLLs/DDR/MIO/clocks before loading the downstream images. wolfBoot
# therefore links and runs entirely from OCM (DDR is not up at entry).
#
# The board-specific psu_init_gpl.c / psu_init_gpl.h (generated from the
# XSA, Xilinx copyright) are supplied at build time from
# ZYNQMP_PSU_INIT_DIR and are NOT part of the wolfBoot tree. The
# hal/zynqmp/ shim headers (xil_io.h, sleep.h) let that unmodified file
# compile. Set EL3_SECURE=1 in the target .config.
ZYNQMP_PSU_INIT_DIR?=hal/board/zynqmp
CFLAGS+=-DWOLFBOOT_ZYNQMP_FSBL
CFLAGS+=-Ihal/zynqmp -I$(ZYNQMP_PSU_INIT_DIR)
LSCRIPT_IN=hal/zynqmp_ocm.ld
OBJS+=hal/zynqmp_psu_shim.o
OBJS+=hal/zynqmp_atf.o
OBJS+=$(ZYNQMP_PSU_INIT_DIR)/psu_init_gpl.o

# Load the PMU configuration object (EEMI permission table) into PMU
# firmware so the APU can control the SoC nodes. Like psu_init_gpl.c the
# pm_cfg_obj.c is design-specific and supplied from ZYNQMP_PSU_INIT_DIR.
ifeq ($(ZYNQMP_PM_CFG),1)
CFLAGS+=-DWOLFBOOT_ZYNQMP_PM_CFG
OBJS+=$(ZYNQMP_PSU_INIT_DIR)/pm_cfg_obj.o
endif

# Run the PS-GTR serdes init (USB3/SATA/PCIe/DP PHY lanes). Required if the
# kernel drives a PS-GTR peripheral (e.g. USB3 dwc3, whose probe hangs on
# an unclocked PHY). Skipped by default since QSPI/SD/RGMII boot needs no
# serdes; the shim runs the full calibrated sequence when enabled.
ifeq ($(ZYNQMP_PSU_INIT_SERDES),1)
CFLAGS+=-DZYNQMP_PSU_INIT_SERDES
endif
endif

endif

ifeq ($(TARGET),versal)
Expand Down
114 changes: 114 additions & 0 deletions config/examples/zynqmp_fsbl.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# wolfBoot configuration for AMD ZynqMP ZCU102 - FSBL REPLACEMENT (QSPI boot)
# Zynq UltraScale+ MPSoC ZU9EG - Quad-core ARM Cortex-A53
#
# wolfBoot replaces the Xilinx First Stage Boot Loader (FSBL). The BootROM
# (optionally with eFuse PPK RSA secure boot) authenticates and loads wolfBoot
# directly into OCM at 0xFFFC0000 and enters it at EL3. wolfBoot then:
# 1. runs the board psu_init() (PLLs, DDR, MIO mux, clocks)
# 2. loads + verifies the downstream images with its OWN signing keys
# 3. (Milestone 1) hands off to real ARM Trusted Firmware (BL31) which keeps
# EL3/PSCI and drops to Linux.
# PMUFW is still loaded by the BootROM via the [pmufw_image] BIF tag.
#
# Boot flow: BootROM -> wolfBoot (OCM/EL3) -> psu_init -> verify+load
# BL31 + kernel + DTB -> BL31 -> Linux
#
# The board-specific psu_init_gpl.c / psu_init_gpl.h (generated from the XSA,
# Xilinx copyright) must be supplied at build time. By default they are read
# from hal/board/zynqmp/ (override with ZYNQMP_PSU_INIT_DIR=...). They are not
# part of the wolfBoot tree. Build with:
# make ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=/path/to/board
# Package with tools/scripts/zcu102/zynqmp_wolfboot_fsbl_qspi.bif

ARCH?=AARCH64
TARGET?=zynq

# Enable the FSBL-replacement build: link/run from OCM, run psu_init at boot,
# pull in the board psu_init_gpl.o + the hal/zynqmp shim.
ZYNQMP_FSBL?=1
ZYNQMP_PSU_INIT_DIR?=hal/board/zynqmp

# Load the PMU configuration object (EEMI permission table) into PMU firmware
# so the APU can control SoC nodes; needs hal/board/zynqmp/pm_cfg_obj.c.
ZYNQMP_PM_CFG?=1

# Run the PS-GTR serdes init so USB3/SATA/PCIe/DP PHY lanes are clocked.
# Needed for the kernel's USB (dwc3) probe not to hang. Set 0 to skip (QSPI/SD
# boot does not need serdes).
ZYNQMP_PSU_INIT_SERDES?=1

# BootROM enters the [bootloader] partition at EL3. Run wolfBoot at EL3.
CFLAGS_EXTRA+=-DEL3_SECURE=1

# DDR-training delay correction. The BootROM leaves the IOU_SCNTRS system
# counter running at the ~1.6GHz pre-divider rate during psu_init (the /15
# TIMESTAMP_REF_CTRL divisor does not re-latch the already-running counter),
# while usleep() computes its tick budget at the nominal 100MHz timestamp
# clock. Without correction every psu_ddr_phybringup settle delay is ~16x too
# short and DDR PHY training comes up marginal. Hardware-measured: a 16x scale
# restores correct training. See hal/zynqmp_psu_shim.c.
CFLAGS_EXTRA+=-DZYNQMP_USLEEP_SCALE=16

WOLFBOOT_VERSION?=0

# RSA 4096-bit with SHA3-384 (downstream image signing)
SIGN?=RSA4096
HASH?=SHA3
IMAGE_HEADER_SIZE?=1024

# Software ARMv8+Crypto assembly for SHA3 (no CSU/PMU SMC available at EL3
# until BL31 is resident).
NO_ARM_ASM?=0
HW_SHA3?=0

DEBUG?=0
DEBUG_SYMBOLS=1
DEBUG_UART=1
CFLAGS_EXTRA+=-DDEBUG_ZYNQ=1

VTOR?=1
CORTEX_M0?=0
NO_ASM?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=0
V?=0
SPMATH?=1
RAM_CODE?=0
DUALBANK_SWAP?=0
PKA?=0
WOLFTPM?=0

# Downstream images are read from QSPI (not XIP; wolfBoot executes from OCM).
EXT_FLASH?=1
SPI_FLASH?=0
NO_XIP=1
USE_GCC=1
ELF?=1

# Native gzip decompression for FIT subimages
GZIP?=1

# Flash Sector Size
WOLFBOOT_SECTOR_SIZE=0x20000
# Application Partition Size
WOLFBOOT_PARTITION_SIZE=0x2A00000
# wolfBoot self-location (OCM). Must match ORIGIN in hal/zynqmp_ocm.ld.
WOLFBOOT_ORIGIN=0xFFFC0000
# Location in QSPI for Primary Boot Partition (OS payload: FIT kernel+DTB)
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x800000
# Load Partition to RAM Address (DDR, after psu_init)
WOLFBOOT_LOAD_ADDRESS?=0x10000000
# Location in QSPI for Secondary (update) Partition
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x3A00000
# Location to store wolfBoot state
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x63E0000

# DTS (Device Tree)
WOLFBOOT_LOAD_DTS_ADDRESS?=0x11800000
WOLFBOOT_DTS_BOOT_ADDRESS?=0x7B0000
WOLFBOOT_DTS_UPDATE_ADDRESS?=0x39B0000

CROSS_COMPILE=aarch64-none-elf-

# Speed up reads from flash by using larger blocks
CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096
83 changes: 83 additions & 0 deletions config/examples/zynqmp_fsbl_sd.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# wolfBoot configuration for AMD ZynqMP ZCU102 - FSBL REPLACEMENT (SD boot)
# Zynq UltraScale+ MPSoC ZU9EG - Quad-core ARM Cortex-A53
#
# Same FSBL-replacement role as config/examples/zynqmp_fsbl.config, but the
# BootROM loads wolfBoot from BOOT.BIN on the SD card FAT partition, and
# wolfBoot reads the downstream images from MBR partitions on the same card.
#
# Boot flow: BootROM -> wolfBoot (OCM/EL3) -> psu_init -> verify+load
# BL31 + kernel + DTB (from SD) -> BL31 -> Linux
#
# Supply the board psu_init_gpl.c/.h at build time (see zynqmp_fsbl.config).
# Build: make ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=/path/to/board
# Package: tools/scripts/zcu102/zynqmp_wolfboot_fsbl_sd.bif

ARCH?=AARCH64
TARGET?=zynq

ZYNQMP_FSBL?=1
ZYNQMP_PSU_INIT_DIR?=hal/board/zynqmp

# BootROM enters the [bootloader] partition at EL3.
CFLAGS_EXTRA+=-DEL3_SECURE=1

WOLFBOOT_VERSION?=0

SIGN?=RSA4096
HASH?=SHA3
IMAGE_HEADER_SIZE?=1024

NO_ARM_ASM?=0
HW_SHA3?=0

DEBUG?=0
DEBUG_SYMBOLS=1
DEBUG_UART=1
CFLAGS_EXTRA+=-DDEBUG_ZYNQ=1

# SD card support - use SDHCI driver (SD1 external slot on ZCU102)
DISK_SDCARD?=1
DISK_EMMC?=0
CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT

# No QSPI in the SD configuration
EXT_FLASH?=0
NO_XIP=1

ELF?=1
GZIP?=1

VTOR?=1
CORTEX_M0?=0
NO_ASM?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=0
V?=0
SPMATH?=1
RAM_CODE?=0
DUALBANK_SWAP?=0
PKA?=0
WOLFTPM?=0

USE_GCC=1
CROSS_COMPILE=aarch64-none-elf-

# MBR partition layout (same as zynqmp_sdcard.config):
# part[0]=boot (FAT32, BOOT.BIN), part[1]=OFP_A, part[2]=OFP_B, part[3]=rootfs
WOLFBOOT_NO_PARTITIONS=1
CFLAGS_EXTRA+=-DBOOT_PART_A=1
CFLAGS_EXTRA+=-DBOOT_PART_B=2
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000
CFLAGS_EXTRA+=-DLINUX_BOOTARGS_ROOT=\"/dev/mmcblk0p4\"

# wolfBoot self-location (OCM). Must match ORIGIN in hal/zynqmp_ocm.ld.
WOLFBOOT_ORIGIN=0xFFFC0000
# Load Partition to RAM Address (DDR, after psu_init)
WOLFBOOT_LOAD_ADDRESS?=0x10000000
# DTS (Device Tree) load address
WOLFBOOT_LOAD_DTS_ADDRESS?=0x1000

# Required for test-app (even with WOLFBOOT_NO_PARTITIONS=1)
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000
WOLFBOOT_PARTITION_SIZE=0x4000000
WOLFBOOT_SECTOR_SIZE=0x1000
52 changes: 52 additions & 0 deletions docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -3709,6 +3709,58 @@ This target supports **two boot paths**:
- **QSPI boot** (primary, production-style): `config/examples/zynqmp.config`
- **SD card boot** (MBR, A/B images): `config/examples/zynqmp_sdcard.config`

In both of the above, wolfBoot is loaded *by* the Xilinx FSBL and runs at EL2. wolfBoot can also replace the FSBL entirely -- see the next section.

### wolfBoot as a full FSBL replacement (`ZYNQMP_FSBL=1`)

Instead of being loaded by the Xilinx FSBL, wolfBoot can *be* the FSBL. The BootROM authenticates and loads wolfBoot directly (as the boot-image `[bootloader]` partition) into the 256 KB OCM at `0xFFFC0000` and enters it at EL3. wolfBoot then runs the board `psu_init()` (PLLs, DDR controller + PHY training, MIO mux, clocks) and loads + verifies the downstream images with its own keys:

```
BootROM -> wolfBoot (OCM, EL3) -> psu_init -> verify+load BL31 + kernel + DTB -> BL31 (EL3) -> Linux
```

PMUFW is still loaded by the BootROM via the `[pmufw_image]` BIF tag. Milestone 1 keeps real ARM Trusted Firmware (BL31) as the resident EL3 monitor.

**Board PS init (psu_init) drop-in.** DDR controller init and PHY training are board/XSA specific and are generated by the Xilinx tools; they carry the Xilinx copyright and are not part of the wolfBoot tree. Copy your generated `psu_init_gpl.c` and `psu_init_gpl.h` into `hal/board/zynqmp/` (gitignored) or point `ZYNQMP_PSU_INIT_DIR` at them. For the ZCU102 these come from PetaLinux/Vitis (`project-spec/hw-description/psu_init_gpl.*`). See `hal/board/zynqmp/README.txt`. wolfBoot-owned shims at `hal/zynqmp/` (`xil_io.h`, `sleep.h`) let the unmodified file compile.

**Build.** Link/run from OCM and run psu_init at boot:

```sh
make ZYNQMP_FSBL=1 # QSPI: config/examples/zynqmp_fsbl.config
make ZYNQMP_FSBL=1 \
ZYNQMP_PSU_INIT_DIR=/path/to/hw-description # if not in hal/board/zynqmp/
```

The SD variant is `config/examples/zynqmp_fsbl_sd.config`. wolfBoot is ~197 KB of the 256 KB OCM (single-stage; wolfBoot's own code does not need DDR).

**Package the boot image.** wolfBoot is the bootloader; there is no `zynqmp_fsbl.elf`:

```sh
cp wolfboot.elf pmufw.elf tools/scripts/zcu102/ # pmufw.elf from PetaLinux/Vitis
cd tools/scripts/zcu102
bootgen -arch zynqmp -image zynqmp_wolfboot_fsbl.bif -w -o BOOT.BIN
```

Use `zynqmp_wolfboot_fsbl_auth.bif` for the Xilinx hardware root of trust (RSA authentication via the eFuse PPK; see comments in that file).

**Flash QSPI** (over JTAG, any boot mode; `program_flash` is under the Vitis `bin/`):

```sh
program_flash -f BOOT.BIN -fsbl zynqmp_fsbl.elf \
-flash_type qspi-x8-dual_parallel -flash_density 1024 \
-verify -url tcp:127.0.0.1:3121
```

`zynqmp_fsbl.elf` here is the stock Xilinx FSBL used only as the `program_flash` flash-writer bootstrap (it is not flashed). Then set SW6 to QSPI32 and power-cycle.

**SD card.** Write `BOOT.BIN` to the FAT (boot) partition of the SD card, set SW6 to SD, power-cycle.

**Boot mode switches (SW6).** See the SD-card SW6 table below (JTAG `0000`, QSPI32 `0010`, SD1 `1110`).

**Downstream images (direct-kernel BL33).** The boot image wolfBoot loads is a wolfBoot-signed FIT containing an `atf` (BL31) sub-image plus the kernel and DTB. Two requirements: build BL31 with its link base in DDR (the default OCM `0xFFFE0000` overlaps wolfBoot), and, because BL33 is the kernel directly (no U-Boot), apply `tools/scripts/zcu102/tf-a-zynqmp-wolfboot-dtb.patch` so BL31 forwards the DTB (which wolfBoot publishes in `PMU_GLOBAL.GLOBAL_GEN_STORAGE5`) into the kernel's `x0`.

**Validation status (ZCU102).** The FSBL path is hardware-validated: wolfBoot runs at `Current EL: 3`, cold-boot `psu_init` brings up DDR (read/write tested), clocks, MIO, UART, and QSPI (flash ID read), from both a JTAG load and a real BootROM QSPI cold boot. The full downstream chain to Linux (FIT + DDR-linked BL31 + the TF-A patch) is in progress.

### Prerequisites

1. **Xilinx Vitis 2024.1 or newer**
Expand Down
8 changes: 8 additions & 0 deletions hal/board/zynqmp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The board PS init is generated from your XSA by the Xilinx tools and carries
# the Xilinx copyright; it is board/design specific and is NOT tracked in the
# wolfBoot tree. Drop your generated files here (see README.txt).
psu_init_gpl.c
psu_init_gpl.h
psu_init.c
psu_init.h
*.o
42 changes: 42 additions & 0 deletions hal/board/zynqmp/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
wolfBoot ZynqMP FSBL replacement - board PS init drop-in
========================================================

When wolfBoot replaces the Xilinx FSBL (ZYNQMP_FSBL=1), it must run the
board-specific PS initialization (PLLs, DDR controller + PHY training, MIO pin
mux, clocks) before any DDR/UART/QSPI/SD access. That init is generated by the
Xilinx tools from your hardware design (XSA) and is specific to your board and
DDR configuration. It carries the Xilinx copyright and is therefore NOT part of
the wolfBoot source tree.

What to drop here
-----------------
Copy your generated files into this directory:

hal/board/zynqmp/psu_init_gpl.c
hal/board/zynqmp/psu_init_gpl.h

For the ZCU102 reference board these are produced by PetaLinux/Vitis, e.g.:

<petalinux-project>/project-spec/hw-description/psu_init_gpl.c
<petalinux-project>/project-spec/hw-description/psu_init_gpl.h

or from the FSBL app sources:

.../embeddedsw/lib/sw_apps/zynqmp_fsbl/misc/zcu102/psu_init_gpl.c

Use the GPL variant (psu_init_gpl.*). Do not edit it.

How it builds
-------------
The unmodified Xilinx file includes <xil_io.h> and <sleep.h>. wolfBoot supplies
minimal, wolfSSL-owned shims for those at hal/zynqmp/ (added to the include
path automatically). The Xilinx file defines the top-level psu_init(), which
hal/zynq.c calls at the start of hal_init().

Build (default location is this directory):

make ZYNQMP_FSBL=1

Or point at the files elsewhere:

make ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=/path/to/hw-description
Loading
Loading