zynqmp: wolfBoot ZynqMP FSBL with signed FIT Linux boot and EL3 security (eFuse/PUF/AES-CSU)

pull/829/head
David Garske 2026-07-10 13:17:38 -07:00 committed by Daniele Lacamera
parent 235bd820e6
commit 1ceaef3eee
28 changed files with 2658 additions and 149 deletions

View File

@ -13,6 +13,11 @@ on:
make-args:
required: false
type: string
psu-init-stub:
# Generate a build-only stub for the board psu_init_gpl.c (ZynqMP FSBL).
required: false
type: boolean
default: false
jobs:
@ -95,6 +100,29 @@ jobs:
run: |
make -C tools/keytools && make -C tools/bin-assemble
- name: Generate ZynqMP psu_init build-only stub
if: ${{ inputs.psu-init-stub }}
run: |
cat > hal/board/zynqmp/psu_init_gpl.c <<'STUB'
/* CI build-only stub for the board psu_init_gpl.c (XSA-generated, not
* tracked in-tree). Satisfies the hal/zynqmp_psu_shim.c externs so the
* FSBL links; performs NO real PS init -- never run on hardware. */
unsigned long psu_mio_init_data(void) { return 1UL; }
unsigned long psu_peripherals_pre_init_data(void) { return 1UL; }
unsigned long psu_pll_init_data(void) { return 1UL; }
unsigned long psu_clock_init_data(void) { return 1UL; }
unsigned long psu_ddr_init_data(void) { return 1UL; }
unsigned long psu_ddr_phybringup_data(void) { return 1UL; }
unsigned long psu_peripherals_init_data(void) { return 1UL; }
unsigned long psu_resetin_init_data(void) { return 1UL; }
unsigned long psu_serdes_init_data(void) { return 1UL; }
unsigned long psu_resetout_init_data(void) { return 1UL; }
int serdes_fixcal_code(void) { return 0; }
unsigned long psu_peripherals_powerdwn_data(void) { return 1UL; }
unsigned long psu_afi_config(void) { return 1UL; }
unsigned long psu_ddr_qos_init_data(void) { return 1UL; }
STUB
- name: Build wolfboot
run: |
make ${{inputs.make-args}}

View File

@ -750,6 +750,30 @@ jobs:
arch: aarch64
config-file: ./config/examples/zynqmp_sdcard.config
zynqmp_fsbl_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/zynqmp_fsbl.config
make-args: ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=hal/board/zynqmp
psu-init-stub: true
zynqmp_fsbl_sec_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/zynqmp_fsbl.config
make-args: ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=hal/board/zynqmp ZYNQMP_SEC=1 ZYNQMP_PUF_SELFTEST=1 ZYNQMP_AES_SELFTEST=1
psu-init-stub: true
zynqmp_fsbl_sd_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/zynqmp_fsbl_sd.config
make-args: ZYNQMP_FSBL=1 ZYNQMP_PSU_INIT_DIR=hal/board/zynqmp DISK_SDCARD=1
psu-init-stub: true
zynq7000_test:
uses: ./.github/workflows/test-build.yml
with:

52
arch.mk
View File

@ -83,6 +83,58 @@ 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
# FSBL security features (eFuse read, PUF, AES-CSU). At EL3 these access
# the CSU/eFuse controllers directly (pmu_mmio is direct MMIO in FSBL
# mode). Read-only eFuse dump today; PUF/AES land incrementally.
ifeq ($(ZYNQMP_SEC),1)
CFLAGS+=-DWOLFBOOT_ZYNQMP_FSBL_SEC
endif
# PUF register+regenerate self-test at boot (bring-up only; registers the
# PUF every boot). Opt-in, needs ZYNQMP_SEC=1.
ifeq ($(ZYNQMP_PUF_SELFTEST),1)
CFLAGS+=-DWOLFBOOT_ZYNQMP_PUF_SELFTEST
endif
# AES-CSU known-answer self-test at boot (KUP key; eFuse-safe). Opt-in.
ifeq ($(ZYNQMP_AES_SELFTEST),1)
CFLAGS+=-DWOLFBOOT_ZYNQMP_AES_SELFTEST
endif
endif
endif
ifeq ($(TARGET),versal)

View File

@ -0,0 +1,118 @@
# 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.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
# FSBL security features (eFuse read/PUF/AES-CSU) via direct EL3 CSU/eFuse
# access. Read-only eFuse dump today. Set 0 to exclude.
ZYNQMP_SEC?=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 # per-chunk QSPI/DDR debug spew; enable only for bring-up
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

View File

@ -0,0 +1,88 @@
# 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.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
# Bound the RAM load of the (not-yet-authenticated) disk image. Required with
# WOLFBOOT_NO_PARTITIONS=1 (update_disk.c). 64MB comfortably fits the signed
# FIT (kernel+atf+fdt) at WOLFBOOT_LOAD_ADDRESS. NOTE: the SD FSBL boot path is
# not yet HW-validated (QSPI is); this config is build-tested only.
WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000
# 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

View File

@ -3710,6 +3710,70 @@ 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`.
**Building BL31 (ARM Trusted Firmware).** BL31 is upstream Xilinx Arm Trusted Firmware - clone `https://github.com/Xilinx/arm-trusted-firmware.git` (validated on branch `xlnx_rebase_v2.12`); it is not vendored in the wolfBoot tree. Apply the in-tree DTB-forwarding patch and build for ZynqMP with the reset base relocated into DDR (so it does not overlap wolfBoot in OCM), for example:
```sh
git clone -b xlnx_rebase_v2.12 https://github.com/Xilinx/arm-trusted-firmware.git
cd arm-trusted-firmware
git apply /path/to/wolfboot/tools/scripts/zcu102/tf-a-zynqmp-wolfboot-dtb.patch
make CROSS_COMPILE=aarch64-none-elf- PLAT=zynqmp RESET_TO_BL31=1 \
PRELOADED_BL33_BASE=0x0 ZYNQMP_ATF_MEM_BASE=0x8000000 bl31
```
`ZYNQMP_ATF_MEM_BASE` sets the DDR link base; use the value your FIT `atf` sub-image loads to (must not overlap the OCM range wolfBoot runs in). The resulting `bl31.elf` is what you place in the signed FIT's `atf` sub-image. The `tf-a-zynqmp-wolfboot-dtb.patch` is the only wolfBoot-specific change; everything else is stock upstream TF-A.
**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 DTB patch) is hardware-validated end-to-end: wolfBoot verifies and loads the signed FIT, hands off to BL31, and boots the kernel all the way to a PetaLinux login prompt on the ZCU102.
### Prerequisites
1. **Xilinx Vitis 2024.1 or newer**

8
hal/board/zynqmp/.gitignore vendored 100644
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,98 @@
/* ZynqMP PMU firmware configuration object -- design-specific data.
*
* EEMI permission table the PMU firmware needs to grant the APU access to SoC
* nodes; loaded via PM_SET_CONFIGURATION -- see zynqmp_pm_set_configuration()
* in hal/zynq.c. Board-specific numeric data (xczu9eg / ZCU102), like
* psu_init_gpl.c, supplied from ZYNQMP_PSU_INIT_DIR. Regenerate by extracting
* the .sys_cfg_data section (symbol XPm_ConfigObject) from the design elf:
* aarch64-none-elf-objcopy -O binary --only-section=.sys_cfg_data
* zynqmp_fsbl.elf pm_cfg_obj.bin
* then emit the words below (little-endian u32).
*/
#include <stdint.h>
const uint32_t pm_cfg_obj[] = {
0x00000002, 0x00000008, 0x00000001, 0x00000101, 0x00000003, 0x00000001,
0x00000001, 0xFFFFFFFF, 0x00000300, 0x00000300, 0x00000007, 0x00000100,
0xFFFFFFFF, 0x00000201, 0x00000201, 0x00000008, 0x00000200, 0xFFFFFFFF,
0x00000101, 0x00000101, 0x00000102, 0x00000028, 0x0000000B, 0x00000001,
0x00000301, 0x0000000C, 0x00000001, 0x00000301, 0x0000000D, 0x00000001,
0x00000301, 0x0000000E, 0x00000001, 0x00000301, 0x0000000F, 0x00000001,
0x00000101, 0x00000010, 0x00000001, 0x00000101, 0x00000011, 0x00000001,
0x00000201, 0x00000012, 0x00000001, 0x00000201, 0x00000013, 0x00000001,
0x00000301, 0x00000014, 0x00000001, 0x00000301, 0x00000015, 0x00000001,
0x00000301, 0x00000016, 0x00000001, 0x00000301, 0x00000018, 0x00000001,
0x00000301, 0x00000019, 0x00000001, 0x00000301, 0x0000001A, 0x00000001,
0x00000301, 0x0000001B, 0x00000001, 0x00000301, 0x0000001C, 0x00000001,
0x00000301, 0x00000020, 0x00000001, 0x00000301, 0x00000021, 0x00000001,
0x00000301, 0x00000022, 0x00000001, 0x00000301, 0x00000023, 0x00000001,
0x00000301, 0x00000024, 0x00000001, 0x00000301, 0x00000025, 0x00000001,
0x00000301, 0x00000026, 0x00000001, 0x00000301, 0x00000028, 0x00000001,
0x00000301, 0x00000029, 0x00000001, 0x00000301, 0x0000002A, 0x00000001,
0x00000301, 0x0000002B, 0x00000001, 0x00000301, 0x0000002D, 0x00000001,
0x00000301, 0x0000002E, 0x00000001, 0x00000301, 0x00000030, 0x00000001,
0x00000301, 0x00000031, 0x00000001, 0x00000301, 0x00000037, 0x00000001,
0x00000301, 0x00000038, 0x00000000, 0x00000001, 0x00000039, 0x00000000,
0x00000100, 0x00000040, 0x00000000, 0x00000200, 0x0000003A, 0x00000001,
0x00000301, 0x0000003B, 0x00000001, 0x00000301, 0x0000003D, 0x00000001,
0x00000301, 0x00000045, 0x00000001, 0x00000301, 0x00000103, 0x00000003,
0x00000001, 0x0000000C, 0x00000037, 0x00000002, 0x00000003, 0x00000003,
0x00000013, 0x00000002, 0x00000003, 0x00000003, 0x0000000B, 0x00000002,
0x00000003, 0x00000003, 0x0000000C, 0x00000002, 0x00000003, 0x00000003,
0x0000000D, 0x00000002, 0x00000003, 0x00000003, 0x0000000E, 0x00000002,
0x00000003, 0x00000003, 0x00000025, 0x00000002, 0x00000003, 0x00000003,
0x00000026, 0x00000002, 0x00000003, 0x00000003, 0x00000028, 0x00000002,
0x00000003, 0x00000003, 0x0000002D, 0x00000002, 0x00000003, 0x00000003,
0x00000045, 0x00000002, 0x00000003, 0x00000003, 0x00000038, 0x00000002,
0x00000003, 0x00000003, 0x00000100, 0x00000003, 0x0000000F, 0x00000002,
0x00000003, 0x00000003, 0x00000010, 0x00000002, 0x00000003, 0x00000003,
0x00000039, 0x00000002, 0x00000003, 0x00000003, 0x00000200, 0x00000003,
0x00000011, 0x00000002, 0x00000003, 0x00000003, 0x00000012, 0x00000002,
0x00000003, 0x00000003, 0x00000040, 0x00000002, 0x00000003, 0x00000003,
0x00000104, 0x00000004, 0x00000001, 0x00000300, 0x00000006, 0x00000301,
0x0000000A, 0x00000300, 0x00000009, 0x00000301, 0x00000105, 0x00000078,
0x000003E8, 0x00000301, 0x000003E9, 0x00000301, 0x000003EA, 0x00000301,
0x000003EB, 0x00000301, 0x000003EC, 0x00000301, 0x000003ED, 0x00000301,
0x000003EE, 0x00000301, 0x000003EF, 0x00000301, 0x000003F0, 0x00000301,
0x000003F1, 0x00000301, 0x000003F2, 0x00000301, 0x000003F3, 0x00000301,
0x000003F4, 0x00000301, 0x000003F5, 0x00000301, 0x000003F6, 0x00000301,
0x000003F7, 0x00000301, 0x000003F8, 0x00000301, 0x000003F9, 0x00000301,
0x000003FA, 0x00000301, 0x000003FB, 0x00000301, 0x000003FC, 0x00000301,
0x000003FD, 0x00000301, 0x000003FE, 0x00000301, 0x000003FF, 0x00000301,
0x00000400, 0x00000301, 0x00000401, 0x00000301, 0x00000402, 0x00000301,
0x00000403, 0x00000301, 0x00000404, 0x00000301, 0x00000405, 0x00000301,
0x00000406, 0x00000301, 0x00000407, 0x00000301, 0x00000408, 0x00000301,
0x00000409, 0x00000301, 0x0000040A, 0x00000301, 0x0000040B, 0x00000301,
0x0000040C, 0x00000301, 0x0000040D, 0x00000301, 0x0000040E, 0x00000301,
0x0000040F, 0x00000301, 0x00000410, 0x00000301, 0x00000411, 0x00000301,
0x00000412, 0x00000301, 0x00000413, 0x00000301, 0x00000414, 0x00000301,
0x00000415, 0x00000301, 0x00000416, 0x00000301, 0x00000417, 0x00000301,
0x00000418, 0x00000301, 0x00000419, 0x00000301, 0x0000041A, 0x00000301,
0x0000041B, 0x00000301, 0x0000041C, 0x00000301, 0x0000041D, 0x00000301,
0x0000041E, 0x00000301, 0x0000041F, 0x00000301, 0x00000420, 0x00000301,
0x00000421, 0x00000301, 0x00000422, 0x00000301, 0x00000423, 0x00000301,
0x00000424, 0x00000301, 0x00000425, 0x00000301, 0x00000426, 0x00000301,
0x00000427, 0x00000301, 0x00000428, 0x00000301, 0x00000429, 0x00000301,
0x0000042A, 0x00000301, 0x0000042B, 0x00000301, 0x0000042C, 0x00000000,
0x0000042D, 0x00000301, 0x0000042E, 0x00000301, 0x0000042F, 0x00000100,
0x00000430, 0x00000301, 0x00000431, 0x00000301, 0x00000432, 0x00000301,
0x00000433, 0x00000301, 0x00000434, 0x00000301, 0x00000435, 0x00000301,
0x00000436, 0x00000301, 0x00000437, 0x00000301, 0x00000438, 0x00000301,
0x00000439, 0x00000301, 0x0000043A, 0x00000301, 0x0000043B, 0x00000301,
0x0000043C, 0x00000301, 0x0000043D, 0x00000301, 0x0000043E, 0x00000301,
0x0000043F, 0x00000301, 0x00000440, 0x00000301, 0x00000441, 0x00000301,
0x00000442, 0x00000301, 0x00000443, 0x00000301, 0x00000444, 0x00000301,
0x00000445, 0x00000301, 0x00000446, 0x00000301, 0x00000447, 0x00000301,
0x00000448, 0x00000301, 0x00000449, 0x00000301, 0x0000044A, 0x00000301,
0x0000044B, 0x00000301, 0x0000044C, 0x00000301, 0x0000044D, 0x00000301,
0x0000044E, 0x00000301, 0x0000044F, 0x00000301, 0x00000450, 0x00000301,
0x00000451, 0x00000301, 0x00000452, 0x00000301, 0x00000453, 0x00000301,
0x00000454, 0x00000301, 0x00000455, 0x00000301, 0x00000456, 0x00000301,
0x00000457, 0x00000301, 0x00000458, 0x00000301, 0x00000459, 0x00000301,
0x0000045A, 0x00000301, 0x0000045B, 0x00000301, 0x0000045C, 0x00000301,
0x0000045D, 0x00000301, 0x0000045E, 0x00000301, 0x0000045F, 0x00000301,
0x00000107, 0x00000000, 0x00000000, 0x00000106, 0x00000301, 0x00000108,
0x00003C04,
};
const uint32_t pm_cfg_obj_words =
(uint32_t)(sizeof(pm_cfg_obj) / sizeof(pm_cfg_obj[0]));

File diff suppressed because it is too large Load Diff

View File

@ -204,6 +204,14 @@
#define GQSPI_FIFO_WORD_SZ 4
#define QQSPI_DMA_ALIGN 64 /* L1 cache size */
/* Maximum bytes per GQSPI DMA transfer. The controller does not reliably
* complete a single very large (>=128KB) DMA read into DDR on ZynqMP -- the
* large chunk lands as zeros -- so cap each transfer and let the flash stream
* continuously across multiple RX gen-FIFO entries. 32KB is well within the
* working range and keeps the one-time boot read fast. */
#ifndef GQSPI_DMA_MAX_CHUNK
#define GQSPI_DMA_MAX_CHUNK 0x8000U
#endif
#ifndef GQSPI_DMA_TMPSZ
/* Use larger of WOLFBOOT_SHA_BLOCK_SIZE or IMAGE_HEADER_SIZE */
#if defined(WOLFBOOT_SHA_BLOCK_SIZE) && \

34
hal/zynqmp/sleep.h 100644
View File

@ -0,0 +1,34 @@
/* sleep.h
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* Minimal Xilinx-compatible <sleep.h> shim for the board-supplied
* psu_init_gpl.c. Implemented in hal/zynqmp_psu_shim.c using the ARMv8
* generic timer (CNTPCT/CNTFRQ), matching how the Xilinx FSBL provides
* usleep()/sleep() during early init. */
#ifndef WOLFBOOT_ZYNQMP_SLEEP_H
#define WOLFBOOT_ZYNQMP_SLEEP_H
void usleep(unsigned long useconds);
unsigned int sleep(unsigned int seconds);
#endif /* WOLFBOOT_ZYNQMP_SLEEP_H */

View File

@ -0,0 +1,92 @@
/* xil_io.h
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* Minimal Xilinx-compatible MMIO/types shim.
*
* This provides just enough of the Xilinx BSP <xil_io.h> surface for an
* unmodified, board-generated psu_init_gpl.c to compile and link into
* wolfBoot when it runs as the ZynqMP FSBL replacement (WOLFBOOT_ZYNQMP_FSBL).
* The board's psu_init_gpl.c / psu_init_gpl.h are supplied at build time and
* are NOT part of the wolfBoot tree (they are board/XSA specific and carry the
* Xilinx copyright). See docs and config/examples/zynqmp_fsbl.config.
*/
#ifndef WOLFBOOT_ZYNQMP_XIL_IO_H
#define WOLFBOOT_ZYNQMP_XIL_IO_H
#include <stdint.h>
/* Xilinx fixed-width type aliases used by psu_init_gpl.c */
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef uintptr_t UINTPTR;
typedef intptr_t INTPTR;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
static inline u32 Xil_In32(UINTPTR addr)
{
return *(volatile u32*)addr;
}
static inline void Xil_Out32(UINTPTR addr, u32 value)
{
*(volatile u32*)addr = value;
}
static inline u16 Xil_In16(UINTPTR addr)
{
return *(volatile u16*)addr;
}
static inline void Xil_Out16(UINTPTR addr, u16 value)
{
*(volatile u16*)addr = value;
}
static inline u8 Xil_In8(UINTPTR addr)
{
return *(volatile u8*)addr;
}
/* Xilinx BSP logging used by a few psu_init error paths (e.g. SERDES cal
* timeout). psu_init runs before uart_init(), so this is a no-op; see
* hal/zynqmp_psu_shim.c. */
int xil_printf(const char* ctrl1, ...);
static inline void Xil_Out8(UINTPTR addr, u8 value)
{
*(volatile u8*)addr = value;
}
#endif /* WOLFBOOT_ZYNQMP_XIL_IO_H */

99
hal/zynqmp_atf.c 100644
View File

@ -0,0 +1,99 @@
/* zynqmp_atf.c
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* ARM Trusted Firmware (BL31) handoff for wolfBoot as the ZynqMP FSBL.
*
* Matches the Xilinx FSBL handoff format (embeddedsw zynqmp_fsbl): a block of
* ASCII magic "XLNX", an entry count, and up to 8 entries {u64 entry_point,
* u64 flags}. Its address is published in PMU_GLOBAL.GEN_STORAGE6, which stock
* BL31 reads during early setup. wolfBoot writes a single entry for the BL33
* image at its requested EL; BL31 owns starting BL33. */
#include "zynqmp_atf.h"
/* PMU_GLOBAL base 0xFFD80000. GEN_STORAGE6 (0x48) carries the handoff-block
* address (read by stock BL31); GEN_STORAGE5 (0x44) carries the BL33 device
* tree address (consumed by a small TF-A patch). Both are device memory. */
#define PMU_GLOBAL_GLOB_GEN_STORAGE5 0xFFD80044UL
#define PMU_GLOBAL_GLOB_GEN_STORAGE6 0xFFD80048UL
/* PartitionFlags: bit0 exec-AA32, bit1 big-endian, bit2 secure (all 0 for
* non-secure AArch64 LE), bits[4:3] target EL, bits[6:5] A53 core (0). */
#define ZYNQMP_ATF_FLAG_EL_SHIFT 3U
#define ZYNQMP_ATF_FLAG_EL_MASK (3U << ZYNQMP_ATF_FLAG_EL_SHIFT)
#define ZYNQMP_ATF_MAX_ENTRIES 8
struct zynqmp_atf_entry {
uint64_t entry_point;
uint64_t flags;
};
struct zynqmp_atf_handoff_params {
char magic[4]; /* "XLNX" */
uint32_t num_entries;
struct zynqmp_atf_entry entry[ZYNQMP_ATF_MAX_ENTRIES];
};
/* Lives in OCM (wolfBoot's .bss). BL31 is loaded to DDR, so this block is
* untouched until BL31 reads it. */
static struct zynqmp_atf_handoff_params atf_handoff;
/* Generic AArch64 EL3 exit (cache/MMU teardown + branch), defined in
* src/boot_aarch64_start.S (built under WOLFBOOT_ZYNQMP_FSBL). */
extern void el3_jump_to_entry(uintptr_t entry);
extern void flush_dcache_range(uintptr_t start, uintptr_t end);
void zynqmp_atf_handoff(uintptr_t bl31_entry, uintptr_t bl33_entry,
uintptr_t dts_addr, uint32_t bl33_el)
{
volatile uint32_t* storage5 =
(volatile uint32_t*)PMU_GLOBAL_GLOB_GEN_STORAGE5;
volatile uint32_t* storage6 =
(volatile uint32_t*)PMU_GLOBAL_GLOB_GEN_STORAGE6;
uint64_t flags;
/* Non-secure, AArch64, little-endian, A53-0, at the requested EL. */
flags = ((uint64_t)(bl33_el << ZYNQMP_ATF_FLAG_EL_SHIFT))
& ZYNQMP_ATF_FLAG_EL_MASK;
atf_handoff.magic[0] = 'X';
atf_handoff.magic[1] = 'L';
atf_handoff.magic[2] = 'N';
atf_handoff.magic[3] = 'X';
atf_handoff.num_entries = 1;
atf_handoff.entry[0].entry_point = (uint64_t)bl33_entry;
atf_handoff.entry[0].flags = flags;
/* Publish the parameter-block address where BL31 reads it, and the BL33
* device tree address for the TF-A patch that forwards it to x0. */
*storage6 = (uint32_t)(uintptr_t)&atf_handoff;
*storage5 = (uint32_t)dts_addr;
/* Clean the parameter block to the PoC so BL31, entered with the MMU and
* caches off, observes it. */
flush_dcache_range((uintptr_t)&atf_handoff,
(uintptr_t)&atf_handoff + sizeof(atf_handoff));
/* Tear down EL3 MMU/caches and branch to BL31. Does not return. */
el3_jump_to_entry(bl31_entry);
}

55
hal/zynqmp_atf.h 100644
View File

@ -0,0 +1,55 @@
/* zynqmp_atf.h
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* ARM Trusted Firmware (BL31) handoff for wolfBoot running as the ZynqMP FSBL
* replacement (Milestone 1). wolfBoot loads + verifies BL31 and the next
* normal-world image (BL33: U-Boot or the Linux kernel) with its own keys,
* then hands off to BL31 at EL3. BL31 stays resident as the EL3 monitor and
* drops BL33 to the requested lower exception level. */
#ifndef ZYNQMP_ATF_H
#define ZYNQMP_ATF_H
#include <stdint.h>
/* Target exception level for the BL33 (normal-world) image. */
#define ZYNQMP_ATF_EL1 1U
#define ZYNQMP_ATF_EL2 2U
/* Build the BL31 handoff parameters for the given normal-world (BL33) entry
* point and exception level, publish the parameter-block address to the
* PMU_GLOBAL scratch register that BL31 reads, and hand off to BL31 at EL3.
* Does not return.
*
* bl31_entry: EL3 entry point of the loaded BL31 image (from its ELF entry).
* bl33_entry: entry point of the next normal-world image BL31 will start.
* dts_addr: device tree address for BL33. The standard ATF handoff block has
* no argument fields and stock ZynqMP TF-A enters BL33 with x0=0,
* so for a direct (no U-Boot) Linux BL33 wolfBoot also publishes
* dts_addr in PMU_GLOBAL.GLOBAL_GEN_STORAGE5. A small TF-A patch
* must read that register into the BL33 entrypoint x0; pass 0 when
* BL33 finds its own DTB (e.g. U-Boot). See docs.
* bl33_el: ZYNQMP_ATF_EL1 or ZYNQMP_ATF_EL2. */
void zynqmp_atf_handoff(uintptr_t bl31_entry, uintptr_t bl33_entry,
uintptr_t dts_addr, uint32_t bl33_el);
#endif /* ZYNQMP_ATF_H */

View File

@ -0,0 +1,79 @@
/dts-v1/;
/*
* Xilinx ZynqMP ZCU102 - wolfBoot-as-FSBL -> Linux boot FIT.
*
* wolfBoot (running as the FSBL replacement at EL3 in OCM) loads this FIT
* from QSPI, verifies the outer wolfBoot RSA4096/SHA3 signature, then:
* - gunzips the kernel to 0x00200000 (BL33 entry),
* - loads BL31 to 0x70000000 (found by the literal node name "atf"),
* - relocates the DTB to WOLFBOOT_LOAD_DTS_ADDRESS (0x11800000),
* - publishes DTB addr in PMU_GLOBAL_GEN_STORAGE5 and the XLNX handoff
* block addr in GEN_STORAGE6, and branches to BL31 at EL3.
* BL31 (xlnx_rebase_v2.12 + tf-a-zynqmp-wolfboot-dtb.patch) hands the kernel
* its DTB in x0 and enters BL33 at EL2.
*
* The incbin filenames are relative: copy the three artifacts into the
* directory you run mkimage from (or next to this .its). Sources:
* Image.gz, system.dtb - from the PetaLinux images/linux tree
* bl31-ddr-0x70000000.bin - BL31 (TF-A) built per docs/Targets.md at link
* base 0x70000000; it must equal the "atf" load/entry below.
*
* Build: mkimage -f zynqmp_fsbl.its fitImage
* Sign: sign --rsa4096 --sha3 fitImage <key>.der 1
*/
/ {
description = "Xilinx ZynqMP ZCU102 - wolfBoot Linux (FSBL)";
#address-cells = <1>;
images {
kernel-1 {
description = "Linux Kernel (gzip)";
data = /incbin/("Image.gz");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "gzip";
load = <0x00200000>;
entry = <0x00200000>;
hash-1 {
algo = "sha256";
};
};
atf {
description = "ARM Trusted Firmware BL31 (DDR-linked)";
data = /incbin/("bl31-ddr-0x70000000.bin");
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
load = <0x70000000>;
entry = <0x70000000>;
hash-1 {
algo = "sha256";
};
};
fdt-1 {
description = "Flattened Device Tree blob";
data = /incbin/("system.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
hash-1 {
algo = "sha256";
};
};
};
configurations {
default = "conf-1";
conf-1 {
description = "Linux kernel + FDT (BL31 loaded by literal name atf)";
kernel = "kernel-1";
fdt = "fdt-1";
hash-1 {
algo = "sha256";
};
};
};
};

327
hal/zynqmp_ocm.ld 100644
View File

@ -0,0 +1,327 @@
/* Linker Script for Zynq UltraScale+ (ZynqMP) - wolfBoot as FSBL replacement.
*
* The BootROM loads wolfBoot into the 256KB OCM at 0xFFFC0000 and enters at
* EL3; wolfBoot runs psu_init() to bring up DDR, so it must link/run entirely
* from OCM (DDR is unavailable at entry). Downstream image staging/verify
* buffers live in DDR (WOLFBOOT_LOAD_ADDRESS) once psu_init completes. Raise
* _HEAP_SIZE only if a config needs it (SPMATH keeps heap use small).
*/
/* EL3 stack 64KB for RSA 4096-bit verify; heap kept small to fit OCM. */
_STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x10000;
_HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x8000;
_EL0_STACK_SIZE = DEFINED(_EL0_STACK_SIZE) ? _EL0_STACK_SIZE : 1024;
_EL1_STACK_SIZE = DEFINED(_EL1_STACK_SIZE) ? _EL1_STACK_SIZE : 2048;
_EL2_STACK_SIZE = DEFINED(_EL2_STACK_SIZE) ? _EL2_STACK_SIZE : 1024;
/* Define Memories in the system */
MEMORY
{
/* wolfBoot links/runs from OCM (256KB) as the FSBL. */
psu_ocm_ram_0_MEM_0 : ORIGIN = 0xFFFC0000, LENGTH = 0x40000
psu_ddr_0_MEM_0 : ORIGIN = 0x0, LENGTH = 0x80000000
psu_ddr_1_MEM_0 : ORIGIN = 0x800000000, LENGTH = 0x80000000
psu_qspi_linear_0_MEM_0 : ORIGIN = 0xC0000000, LENGTH = 0x20000000
}
/* Specify the default entry point to the program */
ENTRY(_vector_table)
/* Define the sections, and where they are mapped in memory */
SECTIONS
{
PROVIDE (_DDR_ADDRESS = 0x80001000);
PROVIDE (_OCRAM_ADDRESS = ORIGIN(psu_ocm_ram_0_MEM_0));
PROVIDE (_MEMORY_SIZE = LENGTH(psu_ocm_ram_0_MEM_0));
.text : {
KEEP (*(.vectors))
*(.boot)
*(.text)
*(.text.*)
*(.gnu.linkonce.t.*)
*(.plt)
*(.gnu_warning)
*(.gcc_except_table)
*(.glue_7)
*(.glue_7t)
*(.ARM.extab)
*(.gnu.linkonce.armextab.*)
} > psu_ocm_ram_0_MEM_0
.init (ALIGN(64)) : {
KEEP (*(.init))
} > psu_ocm_ram_0_MEM_0
.fini (ALIGN(64)) : {
KEEP (*(.fini))
} > psu_ocm_ram_0_MEM_0
.interp : {
KEEP (*(.interp))
} > psu_ocm_ram_0_MEM_0
.note-ABI-tag : {
KEEP (*(.note-ABI-tag))
} > psu_ocm_ram_0_MEM_0
.rodata : {
. = ALIGN(64);
__rodata_start = .;
*(.rodata)
*(.rodata.*)
*(.gnu.linkonce.r.*)
__rodata_end = .;
} > psu_ocm_ram_0_MEM_0
.rodata1 : {
. = ALIGN(64);
__rodata1_start = .;
*(.rodata1)
*(.rodata1.*)
__rodata1_end = .;
} > psu_ocm_ram_0_MEM_0
.sdata2 : {
. = ALIGN(64);
__sdata2_start = .;
*(.sdata2)
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
__sdata2_end = .;
} > psu_ocm_ram_0_MEM_0
.sbss2 : {
. = ALIGN(64);
__sbss2_start = .;
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
__sbss2_end = .;
} > psu_ocm_ram_0_MEM_0
.data : {
. = ALIGN(64);
__data_start = .;
*(.data)
*(.data.*)
*(.gnu.linkonce.d.*)
*(.jcr)
*(.got)
*(.got.plt)
__data_end = .;
} > psu_ocm_ram_0_MEM_0
.data1 : {
. = ALIGN(64);
__data1_start = .;
*(.data1)
*(.data1.*)
__data1_end = .;
} > psu_ocm_ram_0_MEM_0
.got : {
*(.got)
} > psu_ocm_ram_0_MEM_0
.got1 : {
*(.got1)
} > psu_ocm_ram_0_MEM_0
.got2 : {
*(.got2)
} > psu_ocm_ram_0_MEM_0
.note.gnu.build-id : {
KEEP (*(.note.gnu.build-id))
} > psu_ocm_ram_0_MEM_0
.ctors : {
. = ALIGN(64);
__CTOR_LIST__ = .;
___CTORS_LIST___ = .;
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE(*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__CTOR_END__ = .;
___CTORS_END___ = .;
} > psu_ocm_ram_0_MEM_0
.dtors : {
. = ALIGN(64);
__DTOR_LIST__ = .;
___DTORS_LIST___ = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE(*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
__DTOR_END__ = .;
___DTORS_END___ = .;
} > psu_ocm_ram_0_MEM_0
.fixup : {
__fixup_start = .;
*(.fixup)
__fixup_end = .;
} > psu_ocm_ram_0_MEM_0
.eh_frame : {
*(.eh_frame)
} > psu_ocm_ram_0_MEM_0
.eh_framehdr : {
__eh_framehdr_start = .;
*(.eh_framehdr)
__eh_framehdr_end = .;
} > psu_ocm_ram_0_MEM_0
.gcc_except_table : {
*(.gcc_except_table)
} > psu_ocm_ram_0_MEM_0
.mmu_tbl0 (ALIGN(4096)) : {
__mmu_tbl0_start = .;
*(.mmu_tbl0)
__mmu_tbl0_end = .;
} > psu_ocm_ram_0_MEM_0
.mmu_tbl1 (ALIGN(4096)) : {
__mmu_tbl1_start = .;
*(.mmu_tbl1)
__mmu_tbl1_end = .;
} > psu_ocm_ram_0_MEM_0
.mmu_tbl2 (ALIGN(4096)) : {
__mmu_tbl2_start = .;
*(.mmu_tbl2)
__mmu_tbl2_end = .;
} > psu_ocm_ram_0_MEM_0
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx*)
*(.gnu.linkonce.armexidx.*.*)
__exidx_end = .;
} > psu_ocm_ram_0_MEM_0
.preinit_array : {
. = ALIGN(64);
__preinit_array_start = .;
KEEP (*(SORT(.preinit_array.*)))
KEEP (*(.preinit_array))
__preinit_array_end = .;
} > psu_ocm_ram_0_MEM_0
.init_array : {
. = ALIGN(64);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} > psu_ocm_ram_0_MEM_0
.fini_array : {
. = ALIGN(64);
__fini_array_start = .;
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array))
__fini_array_end = .;
} > psu_ocm_ram_0_MEM_0
.ARM.attributes : {
__ARM.attributes_start = .;
*(.ARM.attributes)
__ARM.attributes_end = .;
} > psu_ocm_ram_0_MEM_0
.sdata : {
. = ALIGN(64);
__sdata_start = .;
*(.sdata)
*(.sdata.*)
*(.gnu.linkonce.s.*)
__sdata_end = .;
} > psu_ocm_ram_0_MEM_0
.tdata : {
. = ALIGN(64);
__tdata_start = .;
*(.tdata)
*(.tdata.*)
*(.gnu.linkonce.td.*)
__tdata_end = .;
} > psu_ocm_ram_0_MEM_0
.tbss : {
. = ALIGN(64);
__tbss_start = .;
*(.tbss)
*(.tbss.*)
*(.gnu.linkonce.tb.*)
__tbss_end = .;
} > psu_ocm_ram_0_MEM_0
.sbss (NOLOAD) : {
. = ALIGN(64);
__sbss_start = .;
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
. = ALIGN(64);
__sbss_end = .;
} > psu_ocm_ram_0_MEM_0
.bss (NOLOAD) : {
. = ALIGN(64);
__bss_start__ = .;
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(64);
__bss_end__ = .;
} > psu_ocm_ram_0_MEM_0
_SDA_BASE_ = __sdata_start + ((__sbss_end - __sdata_start) / 2 );
_SDA2_BASE_ = __sdata2_start + ((__sbss2_end - __sdata2_start) / 2 );
/* Generate Stack and Heap definitions */
.heap (NOLOAD) : {
. = ALIGN(64);
_heap = .;
HeapBase = .;
_heap_start = .;
. += _HEAP_SIZE;
_heap_end = .;
HeapLimit = .;
} > psu_ocm_ram_0_MEM_0
.stack (NOLOAD) : {
. = ALIGN(64);
_el3_stack_end = .;
. += _STACK_SIZE;
__el3_stack = .;
_el2_stack_end = .;
. += _EL2_STACK_SIZE;
. = ALIGN(64);
__el2_stack = .;
_el1_stack_end = .;
. += _EL1_STACK_SIZE;
. = ALIGN(64);
__el1_stack = .;
_el0_stack_end = .;
. += _EL0_STACK_SIZE;
. = ALIGN(64);
__el0_stack = .;
} > psu_ocm_ram_0_MEM_0
PROVIDE(_stack_base = .);
_end = .;
}

View File

@ -0,0 +1,239 @@
/* zynqmp_psu_shim.c
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* Delay primitives backing the <sleep.h> shim used by the board-supplied
* psu_init_gpl.c when wolfBoot runs as the ZynqMP FSBL replacement.
*
* These use the ARMv8 generic timer (CNTPCT_EL0 / CNTFRQ_EL0). The EL3 startup
* (boot_aarch64_start.S) programs CNTFRQ_EL0 and the system counter runs from
* the PS reference clock out of reset, so these are usable from the very first
* psu_init() stage, before the PLLs/clocks have been reprogrammed. */
#include "sleep.h"
#include "xil_io.h"
/* Xilinx BSP logging shim. The board psu_init_gpl.c calls xil_printf() on a few
* error paths; psu_init runs before the UART is initialized, so this is a
* no-op. Route to wolfBoot_printf() once the UART is up if you need the
* diagnostics. */
int xil_printf(const char* ctrl1, ...)
{
(void)ctrl1;
return 0;
}
/* ZynqMP IOU system timestamp counter (drives CNTPCT_EL0) and its fixed
* timestamp clock frequency (XPAR_CPU_CORTEXA53_0_TIMESTAMP_CLK_FREQ = 100MHz,
* set by psu_clock_init_data's TIMESTAMP_REF_CTRL). */
#define IOU_SCNTRS_CNT_CONTROL 0xFF260000UL
#define IOU_SCNTRS_FREQ_REG 0xFF260020UL
#define IOU_SCNTRS_EN 0x00000001UL
#ifndef ZYNQMP_TIMESTAMP_HZ
#define ZYNQMP_TIMESTAMP_HZ 100000000UL
#endif
/* Equivalent of the Xilinx BSP XTime_StartTimer(): enable the IOU_SCNTRS
* system counter (so CNTPCT_EL0 increments) if it is not already enabled. The
* BootROM/early psu_init leaves it DISABLED -- the generated psu_init only
* enables it in psu_peripherals_init_data, which runs AFTER DDR training --
* yet psu_ddr_phybringup_data needs working usleep delays. The Xilinx BSP
* usleep calls this on every delay for exactly this reason. */
static void zynqmp_start_timer(void)
{
if ((Xil_In32(IOU_SCNTRS_CNT_CONTROL) & IOU_SCNTRS_EN) != IOU_SCNTRS_EN) {
Xil_Out32(IOU_SCNTRS_FREQ_REG, ZYNQMP_TIMESTAMP_HZ);
Xil_Out32(IOU_SCNTRS_CNT_CONTROL, IOU_SCNTRS_EN);
}
}
static unsigned long psu_timer_count(void)
{
unsigned long cntpct;
asm volatile("mrs %0, cntpct_el0" : "=r"(cntpct));
return cntpct;
}
void usleep(unsigned long useconds)
{
unsigned long start;
unsigned long ticks;
unsigned long freq = ZYNQMP_TIMESTAMP_HZ;
#ifdef WOLFBOOT_ZYNQMP_FSBL
/* Match the Xilinx BSP: ensure the system counter is running before use. */
zynqmp_start_timer();
#endif
#ifdef ZYNQMP_USLEEP_SCALE
useconds *= (unsigned long)(ZYNQMP_USLEEP_SCALE);
#endif
start = psu_timer_count();
/* ticks = useconds * freq / 1e6, split to avoid 64-bit overflow for the
* small delays psu_init uses. */
ticks = (useconds / 1000000UL) * freq
+ ((useconds % 1000000UL) * freq) / 1000000UL;
while ((psu_timer_count() - start) < ticks) {
/* busy-wait */
}
}
unsigned int sleep(unsigned int seconds)
{
unsigned int i;
for (i = 0; i < seconds; i++) {
usleep(1000000UL);
}
return 0;
}
#ifdef WOLFBOOT_ZYNQMP_FSBL
/* Board psu_init sub-stages (non-static in the XSA-generated psu_init_gpl.c).
* Declared here so we do not pull in the huge board psu_init_gpl.h. */
extern unsigned long psu_mio_init_data(void);
extern unsigned long psu_peripherals_pre_init_data(void);
extern unsigned long psu_pll_init_data(void);
extern unsigned long psu_clock_init_data(void);
extern unsigned long psu_ddr_init_data(void);
extern unsigned long psu_ddr_phybringup_data(void);
extern unsigned long psu_peripherals_init_data(void);
extern unsigned long psu_resetin_init_data(void);
extern unsigned long psu_serdes_init_data(void);
extern unsigned long psu_resetout_init_data(void);
/* serdes signal-integrity calibration. serdes_fixcal_code() is non-static in
* the board file; serdes_enb_coarse_saturation() is static (inlined below). */
extern int serdes_fixcal_code(void);
extern unsigned long psu_peripherals_powerdwn_data(void);
extern unsigned long psu_afi_config(void);
extern unsigned long psu_ddr_qos_init_data(void);
/* Diagnostic capture of the early (BootROM) timer/clock state, printed by
* hal_init() once the UART is up. Only CRL_APB registers are readable this
* early (the IOU_SCNTRS block is not yet clocked). */
unsigned long zynqmp_dbg_cntfrq_boot = 0; /* CNTFRQ_EL0 left by the BootROM */
unsigned long zynqmp_dbg_cntfrq_used = 0; /* value usleep() ends up using */
unsigned int zynqmp_dbg_ts_ctrl = 0; /* CRL TIMESTAMP_REF_CTRL 0xFF5E0128 */
unsigned int zynqmp_dbg_iopll_ctrl = 0; /* CRL IOPLL_CTRL 0xFF5E0020 */
static void zynqmp_fix_timer_freq(void)
{
unsigned long f = 0;
/* Capture the BootROM timer/clock state BEFORE psu_init reprograms it.
* Only CRL_APB registers (always clocked) are safe to read this early;
* the IOU_SCNTRS block (0xFF260000) is NOT accessible until psu_init
* clocks it and reading it here bus-stalls the core. */
__asm__ volatile("mrs %0, cntfrq_el0" : "=r"(f));
zynqmp_dbg_cntfrq_boot = f;
zynqmp_dbg_ts_ctrl = Xil_In32(0xFF5E0128UL);
zynqmp_dbg_iopll_ctrl = Xil_In32(0xFF5E0020UL);
/* The IOU_SCNTRS system counter runs at the fixed 100MHz timestamp clock
* (set by psu_clock_init_data) once enabled. usleep() enables it and uses
* 100MHz directly; set CNTFRQ_EL0 to match so hal_get_timer_us()/
* hal_timer_ms() are also correct. */
f = ZYNQMP_TIMESTAMP_HZ;
__asm__ volatile("msr cntfrq_el0, %0\n\t isb" : : "r"(f));
zynqmp_dbg_cntfrq_used = f;
}
/* Replacement for the board file's psu_init(): mirrors its call sequence (see
* psu_init_gpl.c) but calls zynqmp_fix_timer_freq() right after clock init so
* DDR training settle delays are accurate. The static init_serdes()/
* init_peripheral() are inlined via their non-static sub-stages. Returns 0 on
* success.
*
* PSU_STAGE(n) writes a progress marker to PMU_GLOBAL GEN_STORAGE0
* (0xFFD80030) before each sub-stage; after a cold-boot hang, read it over
* JTAG to see the last sub-stage that STARTED. */
#define ZYNQMP_PSU_STAGE_REG 0xFFD80030UL
#define PSU_STAGE(n) Xil_Out32(ZYNQMP_PSU_STAGE_REG, (u32)(n))
int zynqmp_psu_init(void)
{
int status = 1;
u32 smmu;
PSU_STAGE(0xA000);
/* Correct CNTFRQ_EL0 up front so EVERY psu_init stage (PLL/clock/DDR) gets
* accurate usleep settle delays. The BootROM leaves the system counter at
* the ~1.5 GHz pre-divider rate for the whole psu_init sequence (the /15
* divisor never re-latches the running counter), so this single correction
* holds across all stages. */
zynqmp_fix_timer_freq();
PSU_STAGE(0xA001); status &= psu_mio_init_data();
PSU_STAGE(0xA002); status &= psu_peripherals_pre_init_data();
PSU_STAGE(0xA003); status &= psu_pll_init_data();
PSU_STAGE(0xA004); status &= psu_clock_init_data();
PSU_STAGE(0xA005); status &= psu_ddr_init_data();
PSU_STAGE(0xA006); status &= psu_ddr_phybringup_data();
PSU_STAGE(0xA007);
PSU_STAGE(0xA008); status &= psu_peripherals_init_data();
/* init_serdes() equivalent: PS-GTR (USB/SATA/PCIe/DP) lane bringup. Not
* needed to boot QSPI/SD -> DDR -> Linux (Linux re-inits the PS-GTR), so
* skipped by default; define ZYNQMP_PSU_INIT_SERDES to run it -- required
* if the kernel drives a PS-GTR peripheral (e.g. USB3 dwc3, whose probe
* else hangs on an unclocked PHY). Replicates the board init_serdes() order:
* the calibration helpers must run first or the SERDES PLLs never lock.
* serdes_fixcal_code() is non-static (called); serdes_enb_coarse_saturation()
* is static so its four writes are inlined; serdes_illcalib() runs inside
* psu_serdes_init_data(). */
#ifdef ZYNQMP_PSU_INIT_SERDES
PSU_STAGE(0xA009);
status &= psu_resetin_init_data();
PSU_STAGE(0xB000);
status &= serdes_fixcal_code();
/* serdes_enb_coarse_saturation(): enable PLL coarse-code saturation logic */
Xil_Out32(0xFD402094UL, 0x00000010UL);
Xil_Out32(0xFD406094UL, 0x00000010UL);
Xil_Out32(0xFD40A094UL, 0x00000010UL);
Xil_Out32(0xFD40E094UL, 0x00000010UL);
PSU_STAGE(0xB001); status &= psu_serdes_init_data();
PSU_STAGE(0xB002); status &= psu_resetout_init_data();
PSU_STAGE(0xB003);
#endif
/* init_peripheral(): SMMU interrupt enable (read-modify-write set of
* bits 0x8000001F, matching the board PSU_Mask_Write). This is NOT part of
* init_serdes(), so it runs even when the PS-GTR serdes block is skipped. */
smmu = Xil_In32(0xFD5F0018UL);
smmu &= ~0x8000001FUL;
smmu |= 0x8000001FUL;
Xil_Out32(0xFD5F0018UL, smmu);
PSU_STAGE(0xB004);
PSU_STAGE(0xA00A);
status &= psu_peripherals_powerdwn_data();
status &= psu_afi_config();
psu_ddr_qos_init_data();
PSU_STAGE(0xA0DD); /* psu_init fully complete */
if (status == 0)
return 1;
return 0;
}
#endif /* WOLFBOOT_ZYNQMP_FSBL */

View File

@ -0,0 +1,82 @@
/* wolfboot_smc.h
*
* Copyright (C) 2025 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* SMC ABI between the wolfBoot EL3 secure monitor (server) and the normal
* world (client). This is the shared contract used by both sides:
*
* - The crypto / key / secure-storage surface is carried by the wolfHSM
* client/server protocol over a shared-memory transport (wh_transport_mem).
* SMC is used only as a doorbell: the client writes a request into the
* shared buffer, issues WOLFBOOT_SMC_FID_HSM_DOORBELL, and the EL3 server
* processes exactly one wolfHSM request and writes the response back.
* - A few small fixed-ABI calls (firmware update trigger/status, version)
* do not need the wolfHSM channel.
*
* Function IDs use the SMCCC Fast Call, SMC64, OEM range (0xC3000000+) to
* avoid the Xilinx SiP range (0xC2000000, used by PM_SIP_SVC in hal/zynq.c).
*/
#ifndef WOLFBOOT_SMC_H
#define WOLFBOOT_SMC_H
/* SMCCC Fast Call / SMC64 / OEM service range base. */
#define WOLFBOOT_SMC_FID_BASE 0xC3000000U
/* Doorbell: a wolfHSM request is ready in the shared buffer. The server runs
* one wh_Server_HandleRequestMessage() pass. Returns WOLFBOOT_SMC_OK on a
* processed request, WOLFBOOT_SMC_NOT_READY if none was pending. */
#define WOLFBOOT_SMC_FID_HSM_DOORBELL (WOLFBOOT_SMC_FID_BASE + 0x00U)
/* Firmware update / boot control (fixed ABI, no wolfHSM channel). */
#define WOLFBOOT_SMC_FID_FW_UPDATE (WOLFBOOT_SMC_FID_BASE + 0x01U)
#define WOLFBOOT_SMC_FID_FW_STATUS (WOLFBOOT_SMC_FID_BASE + 0x02U)
#define WOLFBOOT_SMC_FID_VERSION (WOLFBOOT_SMC_FID_BASE + 0x03U)
/* SMC return codes (in x0). Aligned with SMCCC conventions: 0 success,
* negative for errors. */
#define WOLFBOOT_SMC_OK (0)
#define WOLFBOOT_SMC_NOT_READY (-1)
#define WOLFBOOT_SMC_NOT_SUPPORTED (-2)
#define WOLFBOOT_SMC_ERROR (-3)
/* Shared-memory region for the wolfHSM transport (wh_transport_mem). Carved
* out of DDR and mapped on both sides; must be reserved from the OS via the
* device tree (/reserved-memory) so Linux does not use it. The region holds
* the request buffer followed by the response buffer.
*
* Override at build time with -DWOLFBOOT_HSM_SHM_BASE / _SIZE to match the
* DTB reservation and the wolfHSM whTransportMemConfig on both ends. */
#ifndef WOLFBOOT_HSM_SHM_BASE
#define WOLFBOOT_HSM_SHM_BASE 0x7F000000UL
#endif
#ifndef WOLFBOOT_HSM_SHM_SIZE
#define WOLFBOOT_HSM_SHM_SIZE 0x00010000UL /* 64 KB total */
#endif
/* Split the region into request and response halves for wh_transport_mem. */
#define WOLFBOOT_HSM_SHM_REQ_BASE (WOLFBOOT_HSM_SHM_BASE)
#define WOLFBOOT_HSM_SHM_REQ_SIZE (WOLFBOOT_HSM_SHM_SIZE / 2U)
#define WOLFBOOT_HSM_SHM_RESP_BASE (WOLFBOOT_HSM_SHM_BASE + \
WOLFBOOT_HSM_SHM_REQ_SIZE)
#define WOLFBOOT_HSM_SHM_RESP_SIZE (WOLFBOOT_HSM_SHM_SIZE / 2U)
#endif /* WOLFBOOT_SMC_H */

View File

@ -242,7 +242,37 @@ void RAMFUNCTION arch_reboot(void)
* ============================================================================
*/
#if defined(DEBUG_HARDFAULT) && defined(DEBUG_UART) && defined(EL2_HYPERVISOR)
#if defined(EL3_SECURE) && EL3_SECURE == 1 && defined(DEBUG_UART)
/* EL3 exception reporting. Without this, a data abort / SError taken at EL3
* lands in the silent wfi stub below and looks like a hang. Print the syndrome
* so the fault class (ESR_EL3.EC) and faulting address (FAR_EL3) are visible.
* Gated on DEBUG_UART so release EL3 targets keep the minimal silent stub. */
static void print_exception_info_el3(const char *type)
{
unsigned long esr = 0, elr = 0, far = 0;
__asm__ volatile("mrs %0, ESR_EL3" : "=r"(esr));
__asm__ volatile("mrs %0, ELR_EL3" : "=r"(elr));
__asm__ volatile("mrs %0, FAR_EL3" : "=r"(far));
wolfBoot_printf("\n*** %s EXCEPTION (EL3) ***\n", type);
wolfBoot_printf("ESR_EL3: 0x%08x%08x\n",
(uint32_t)(esr >> 32), (uint32_t)esr);
wolfBoot_printf("ELR_EL3: 0x%08x%08x\n",
(uint32_t)(elr >> 32), (uint32_t)elr);
wolfBoot_printf("FAR_EL3: 0x%08x%08x\n",
(uint32_t)(far >> 32), (uint32_t)far);
}
void SynchronousInterrupt(void)
{ print_exception_info_el3("SYNCHRONOUS"); while (1) { __asm__ volatile("wfi"); } }
void IRQInterrupt(void)
{ print_exception_info_el3("IRQ"); while (1) { __asm__ volatile("wfi"); } }
void FIQInterrupt(void)
{ print_exception_info_el3("FIQ"); while (1) { __asm__ volatile("wfi"); } }
void SErrorInterrupt(void)
{ print_exception_info_el3("SERROR"); while (1) { __asm__ volatile("wfi"); } }
#elif defined(DEBUG_HARDFAULT) && defined(DEBUG_UART) && defined(EL2_HYPERVISOR)
#define READ_SYSREG(_out, _reg) __asm__ volatile("mrs %0, " #_reg : "=r"(_out))

View File

@ -213,9 +213,15 @@ InitEL3:
#endif
msr S3_1_C15_C2_0, x0 /* CPUACTLR_EL1 */
/* Program the counter frequency */
/* Program the counter frequency.
* For the FSBL-replacement build, do NOT overwrite CNTFRQ_EL0: the BootROM
* sets it to the actual system-counter rate, which our usleep() needs for
* accurate psu_init DDR-training delays. Hardcoding 100MHz here is wrong
* when the counter runs at the BootROM (undivided) rate. */
#ifndef WOLFBOOT_ZYNQMP_FSBL
ldr x0,=counterfreq
msr CNTFRQ_EL0, x0
#endif
/* Enable hardware coherency between cores */
mrs x0, S3_1_c15_c2_1 /* Read EL1 CPU Extended Control Register */
@ -557,7 +563,13 @@ invalidatecaches_end:
*/
.set reserved, 0x0 /* Fault */
#if defined(EL1_NONSECURE) && EL1_NONSECURE == 1
#if defined(ZYNQMP_DDR_NC) && ZYNQMP_DDR_NC == 1
.set Memory, 0x401 | (0 << 8) | (0x0) /* DDR as Normal NON-cacheable (MAIR idx0=0x44) - diag */
#elif defined(ZYNQMP_DDR_DEVICE) && ZYNQMP_DDR_DEVICE == 1
.set Memory, 0x409 | (1 << 53) | (1 << 54) /* DDR as Device-nGnRnE (no speculation) - diag */
#elif defined(ZYNQMP_DDR_NONSHARED) && ZYNQMP_DDR_NONSHARED == 1
.set Memory, 0x405 | (0 << 8) | (0x0) /* normal writeback write allocate NON-shareable read write */
#elif defined(EL1_NONSECURE) && EL1_NONSECURE == 1
.set Memory, 0x405 | (2 << 8) | (0x0) /* normal writeback write allocate outer shared read write */
#else
.set Memory, 0x405 | (3 << 8) | (0x0) /* normal writeback write allocate inner shared read write */
@ -1212,6 +1224,74 @@ flush_dcache_range:
ret
/*
* void zynqmp_dcache_disable(void)
*
* Clean+invalidate the entire data cache to PoC by set/way, then disable the
* D-cache (SCTLR_EL3.C=0) and drop the core out of the coherency domain
* (CPUECTLR_EL1.SMPEN=0). Used to TEST whether the QSPI body-load failure is
* purely a coherency artifact: with no cache lines to allocate into and the
* core out of the snoop domain, the coherent QSPIDMA writes must land in DDR.
* Clobbers x0-x11. Modeled on the ARMv8 set/way routine used elsewhere here.
*/
.global zynqmp_dcache_disable
zynqmp_dcache_disable:
mrs x0, clidr_el1
and x3, x0, #0x07000000
lsr x3, x3, #23 /* x3 = LoC * 2 */
cbz x3, .Lzdd_done
mov x10, #0
.Lzdd_level:
add x2, x10, x10, lsr #1
lsr x1, x0, x2
and x1, x1, #7
cmp x1, #2
b.lt .Lzdd_skip
msr csselr_el1, x10
isb
mrs x1, ccsidr_el1
and x2, x1, #7
add x2, x2, #4 /* x2 = log2(line len) */
mov x4, #0x3ff
and x4, x4, x1, lsr #3 /* x4 = max way */
clz w5, w4
mov x7, #0x7fff
and x7, x7, x1, lsr #13 /* x7 = max set */
.Lzdd_set:
mov x9, x4
.Lzdd_way:
lsl x6, x9, x5
orr x11, x10, x6
lsl x6, x7, x2
orr x11, x11, x6
dc cisw, x11
subs x9, x9, #1
b.ge .Lzdd_way
subs x7, x7, #1
b.ge .Lzdd_set
.Lzdd_skip:
add x10, x10, #2
cmp x3, x10
b.gt .Lzdd_level
.Lzdd_done:
mov x10, #0
msr csselr_el1, x10
dsb sy
isb
/* disable D-cache (SCTLR_EL3.C, bit 2) */
mrs x1, sctlr_el3
bic x1, x1, #(1 << 2)
msr sctlr_el3, x1
isb
/* leave the SMP/coherency domain (CPUECTLR_EL1.SMPEN, bit 6) */
mrs x1, S3_1_C15_C2_1
bic x1, x1, #(1 << 6)
msr S3_1_C15_C2_1, x1
dsb sy
isb
ret
/* Initialize GIC 400 (GICv2) */
.global gicv2_init_secure
gicv2_init_secure:
@ -1448,4 +1528,90 @@ el2_flush_and_disable_mmu:
ret
#endif /* EL2_HYPERVISOR */
#if defined(WOLFBOOT_ZYNQMP_FSBL)
/*
* void el3_jump_to_entry(uintptr_t entry)
*
* Generic AArch64 EL3 exit primitive: cleans & invalidates the entire D-cache
* to the PoC, invalidates the I-cache, disables the EL3 MMU + I/D-caches, then
* branches to the given entry point at EL3 with the MMU off. Not ZynqMP-
* specific -- reusable by any AArch64 EL3-FSBL target that hands off to a
* next-stage image entered at EL3 (here BL31, mirroring the stock ZynqMP FSBL's
* XFsbl_Exit()). Any handoff parameters are the caller's responsibility (the
* ZynqMP caller publishes them via PMU_GLOBAL scratch in hal/zynqmp_atf.c
* beforehand), so no GP-register arguments are used here.
*
* x0: entry. The set/way loop below clobbers x0-x11, so the entry point is
* preserved in x19 (callee-saved, untouched by the loop). Does not return.
*/
.global el3_jump_to_entry
el3_jump_to_entry:
mov x19, x0 /* preserve entry point across the loop */
/* ---- 1. Clean & invalidate entire data cache to PoC by set/way ---- */
mrs x0, clidr_el1
and x3, x0, #0x07000000 /* x3 = LoC (level of coherency) */
lsr x3, x3, #23 /* x3 = LoC * 2 */
cbz x3, .Latf_dcache_done
mov x10, #0 /* x10 = current cache level << 1 */
.Latf_dcache_level_loop:
add x2, x10, x10, lsr #1 /* x2 = level * 3 */
lsr x1, x0, x2 /* x1 = ctype field for this level */
and x1, x1, #7
cmp x1, #2
b.lt .Latf_dcache_skip_level /* No data cache at this level */
msr csselr_el1, x10 /* Select cache level (instruction = 0) */
isb
mrs x1, ccsidr_el1
and x2, x1, #7 /* x2 = log2(line length) - 4 */
add x2, x2, #4 /* x2 = log2(line length) */
mov x4, #0x3ff
and x4, x4, x1, lsr #3 /* x4 = max way number */
clz w5, w4 /* x5 = bit position of way size */
mov x7, #0x7fff
and x7, x7, x1, lsr #13 /* x7 = max set number */
.Latf_dcache_set_loop:
mov x9, x4 /* x9 = current way */
.Latf_dcache_way_loop:
lsl x6, x9, x5
orr x11, x10, x6 /* level | way */
lsl x6, x7, x2
orr x11, x11, x6 /* level | way | set */
dc cisw, x11 /* clean & invalidate by set/way */
subs x9, x9, #1
b.ge .Latf_dcache_way_loop
subs x7, x7, #1
b.ge .Latf_dcache_set_loop
.Latf_dcache_skip_level:
add x10, x10, #2
cmp x3, x10
b.gt .Latf_dcache_level_loop
.Latf_dcache_done:
mov x10, #0
msr csselr_el1, x10
dsb sy
isb
/* ---- 2. Invalidate entire I-cache to PoU ---- */
ic iallu
dsb ish
isb
/* ---- 3. Disable MMU + I-cache + D-cache at EL3 ---- */
mrs x0, SCTLR_EL3
bic x0, x0, #(1 << 0) /* M */
bic x0, x0, #(1 << 2) /* C */
bic x0, x0, #(1 << 12) /* I */
msr SCTLR_EL3, x0
dsb sy
isb
/* ---- 4. Branch to BL31 (does not return) ---- */
br x19
#endif /* WOLFBOOT_ZYNQMP_FSBL */
.end

View File

@ -47,6 +47,9 @@
#ifdef WOLFBOOT_ELF
#include "elf.h"
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
#include "../hal/zynqmp_atf.h"
#endif
/* Disk encryption support for AES-256, AES-128, or ChaCha20 */
#if defined(ENCRYPT_WITH_AES256) || defined(ENCRYPT_WITH_AES128) || \
@ -282,6 +285,10 @@ void RAMFUNCTION wolfBoot_start(void)
#ifdef WOLFBOOT_FDT
uint32_t dts_size = 0;
#endif
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
/* BL31 (ARM-TF) entry, set when the boot FIT carries an "atf" sub-image. */
uintptr_t bl31_entry = 0;
#endif
char part_name[4] = {'P', ':', 'X', '\0'};
BENCHMARK_DECLARE();
@ -566,6 +573,9 @@ void RAMFUNCTION wolfBoot_start(void)
void* fit = (void*)load_address;
const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL;
const char *fpga = NULL;
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
void *atf_load;
#endif
wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n",
fdt_version(fit), fdt_totalsize(fit));
@ -600,6 +610,14 @@ void RAMFUNCTION wolfBoot_start(void)
}
load_address = new_load;
}
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
/* Load BL31 (ARM-TF) from the FIT "atf" sub-image, if present. */
atf_load = fit_load_image(fit, "atf", NULL);
if (atf_load != NULL) {
bl31_entry = (uintptr_t)atf_load;
wolfBoot_printf("FIT: BL31 (atf) loaded at %p\r\n", atf_load);
}
#endif
if (flat_dt != NULL) {
uint8_t *dts_ptr = fit_load_image(fit, flat_dt, (int*)&dts_size);
if (dts_ptr != NULL && wolfBoot_get_dts_size(dts_ptr) >= 0) {
@ -657,6 +675,14 @@ void RAMFUNCTION wolfBoot_start(void)
#ifdef DISK_ENCRYPT
disk_decrypted_header_clear(dec_hdr);
disk_crypto_clear();
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
if (bl31_entry != 0) {
wolfBoot_printf("Handing off to BL31 at %p (kernel %p)\r\n",
(void*)bl31_entry, (void*)load_address);
zynqmp_atf_handoff(bl31_entry, (uintptr_t)load_address,
(uintptr_t)dts_addr, ZYNQMP_ATF_EL2);
}
#endif
do_boot((uint32_t*)load_address
#ifdef MMU

View File

@ -41,6 +41,9 @@
#ifdef WOLFBOOT_ELF
#include "elf.h"
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
#include "../hal/zynqmp_atf.h"
#endif
extern void hal_flash_dualbank_swap(void);
extern int wolfBoot_get_dts_size(void *dts_addr);
@ -52,6 +55,20 @@ extern uint32_t dts_load_addr;
extern uint8_t _end[]; /* linker symbol: end of wolfBoot BSS */
#endif
/* Return non-zero if the RAM load region [img_lo, img_hi) overlaps wolfBoot's
* own region [wb_lo, wb_hi). wb_lo == 0 means the origin is unknown, so guard
* only that the image loads above wolfBoot's end (wb_hi). Pure arithmetic,
* exposed at file scope for unit testing (tools/unit-tests/unit-update-ram.c). */
static inline int ramboot_region_overlap(uintptr_t img_lo, uintptr_t img_hi,
uintptr_t wb_lo, uintptr_t wb_hi)
{
if (img_hi < img_lo)
return 1; /* header+size wrapped the end address: reject conservatively */
if (wb_lo == 0)
return (img_lo < wb_hi);
return (img_lo < wb_hi && img_hi > wb_lo);
}
#if ((defined(EXT_FLASH) && defined(NO_XIP)) || \
(defined(EXT_ENCRYPTED) && defined(MMU))) && \
!defined(WOLFBOOT_NO_RAMBOOT)
@ -114,12 +131,28 @@ int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst)
#endif
#if defined(__WOLFBOOT) && defined(WOLFBOOT_LOAD_ADDRESS)
/* Runtime overlap check: ensure image destination does not overwrite
* wolfBoot's own code/data/bss in RAM. */
if ((uintptr_t)dst < (uintptr_t)_end) {
wolfBoot_printf("Error: image dest %p overlaps wolfBoot end %p\n",
dst, _end);
return -1;
/* Overlap check: the image destination must not overwrite wolfBoot's own
* code/data/bss (ends at _end). The image occupies [dst, dst+header+size]. */
{
uintptr_t wb_hi = (uintptr_t)_end;
uintptr_t img_lo = (uintptr_t)dst;
uintptr_t img_hi = img_lo + (uintptr_t)IMAGE_HEADER_SIZE +
(uintptr_t)img_size;
#if defined(WOLFBOOT_ORIGIN)
/* wolfBoot spans [WOLFBOOT_ORIGIN, _end]; range-intersect so it holds
* whether wolfBoot is below or above the image -- e.g. ZynqMP FSBL runs
* from high OCM while the image loads to low DDR, where the plain
* "dst < _end" test gave a false positive. */
uintptr_t wb_lo = (uintptr_t)(WOLFBOOT_ORIGIN);
#else
/* Without WOLFBOOT_ORIGIN, wb_lo=0 keeps the original low-addr guard. */
uintptr_t wb_lo = 0;
#endif
if (ramboot_region_overlap(img_lo, img_hi, wb_lo, wb_hi)) {
wolfBoot_printf("Error: image %p-%p overlaps wolfBoot %p-%p\n",
(void*)img_lo, (void*)img_hi, (void*)wb_lo, (void*)wb_hi);
return -1;
}
}
#endif
@ -250,6 +283,12 @@ void RAMFUNCTION wolfBoot_start(void)
uint8_t *dts_addr = NULL;
uint32_t dts_size = 0;
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
/* When wolfBoot is the FSBL, the boot FIT carries an "atf" (BL31)
* sub-image. If present, hand off to BL31 instead of jumping to the
* kernel directly. */
uintptr_t bl31_entry = 0;
#endif
#if !defined(ALLOW_DOWNGRADE) && defined(WOLFBOOT_FIXED_PARTITIONS)
uint32_t boot_v = wolfBoot_current_firmware_version();
uint32_t update_v = wolfBoot_update_firmware_version();
@ -503,6 +542,9 @@ backup_on_failure:
void* fit = (void*)load_address;
const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL;
const char *fpga = NULL;
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
void *atf_load;
#endif
wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n",
fdt_version(fit), fdt_totalsize(fit));
@ -528,6 +570,16 @@ backup_on_failure:
}
load_address = new_load;
}
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
/* Load BL31 (ARM Trusted Firmware) to its DDR exec address. Its entry
* point is the FIT `load`/`entry` address returned here. Optional: if
* absent, fall through to the normal direct boot. */
atf_load = fit_load_image(fit, "atf", NULL);
if (atf_load != NULL) {
bl31_entry = (uintptr_t)atf_load;
wolfBoot_printf("FIT: BL31 (atf) loaded at %p\n", atf_load);
}
#endif
if (flat_dt != NULL) {
uint8_t *dts_ptr = fit_load_image(fit, flat_dt, (int*)&dts_size);
if (dts_ptr != NULL && wolfBoot_get_dts_size(dts_ptr) >= 0) {
@ -605,6 +657,17 @@ backup_on_failure:
#ifndef WOLFBOOT_SKIP_BOOT_VERIFY
PART_SANITY_CHECK(&os_image);
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
if (bl31_entry != 0) {
/* Hand off to BL31 (resident EL3 monitor). BL31 starts the kernel
* (BL33) at EL2; the DTB is forwarded via PMU_GLOBAL scratch (see
* hal/zynqmp_atf.c). Does not return. */
wolfBoot_printf("Handing off to BL31 at %p (kernel %p)\n",
(void*)bl31_entry, (void*)load_address);
zynqmp_atf_handoff(bl31_entry, (uintptr_t)load_address,
(uintptr_t)dts_addr, ZYNQMP_ATF_EL2);
}
#endif
#ifdef MMU
do_boot((uint32_t*)load_address,
(uint32_t*)dts_addr);

View File

@ -0,0 +1,59 @@
plat: xilinx: zynqmp: forward DTB to a direct-kernel BL33
When wolfBoot replaces the Xilinx FSBL and boots a Linux kernel directly as
BL33 (no U-Boot), BL31 must hand the kernel its device tree in x0 (the arm64
boot protocol requires the DTB pointer in x0). Stock ZynqMP TF-A leaves the
BL33 entry arguments zeroed, which is fine for a U-Boot BL33 but not for a
direct kernel.
wolfBoot publishes the DTB address in PMU_GLOBAL.GLOBAL_GEN_STORAGE5 (it already
uses GLOBAL_GEN_STORAGE6 for the standard ATF handoff-parameter block). This
patch reads GEN_STORAGE5 and, when non-zero, places it in BL33's arg0. When the
register is zero (JTAG/default path, or a U-Boot BL33), behaviour is unchanged.
Apply against the Xilinx ARM Trusted Firmware tree:
cd arm-trusted-firmware
git apply /path/to/tf-a-zynqmp-wolfboot-dtb.patch
make PLAT=zynqmp RESET_TO_BL31=1 bl31
Build BL31 with its link base in DDR (ZYNQMP_ATF_MEM_BASE in DDR), not the
default OCM 0xFFFE0000, because wolfBoot occupies OCM as the FSBL.
diff --git a/plat/xilinx/zynqmp/bl31_zynqmp_setup.c b/plat/xilinx/zynqmp/bl31_zynqmp_setup.c
index 65616e599..b9a4284e3 100644
--- a/plat/xilinx/zynqmp/bl31_zynqmp_setup.c
+++ b/plat/xilinx/zynqmp/bl31_zynqmp_setup.c
@@ -143,6 +143,21 @@ void bl31_early_platform_setup2(u_register_t arg0, u_register_t arg1,
panic();
}
}
+
+ /*
+ * wolfBoot (as the FSBL) publishes the BL33 device tree address in
+ * PMU_GLOBAL_GEN_STORAGE5. The arm64 boot protocol requires the DTB
+ * pointer in x0, but stock TF-A leaves BL33's args zeroed (fine for a
+ * U-Boot BL33, not for a direct kernel BL33). Forward it when set.
+ */
+ {
+ uint64_t dtb_addr = (uint64_t)mmio_read_32(PMU_GLOBAL_GEN_STORAGE5);
+
+ if (dtb_addr != 0U) {
+ bl33_image_ep_info.args.arg0 = dtb_addr;
+ }
+ }
+
if (bl32_image_ep_info.pc != 0U) {
NOTICE("BL31: Secure code at 0x%lx\n", bl32_image_ep_info.pc);
}
diff --git a/plat/xilinx/zynqmp/include/zynqmp_def.h b/plat/xilinx/zynqmp/include/zynqmp_def.h
index cd3bbbc64..19156a5fb 100644
--- a/plat/xilinx/zynqmp/include/zynqmp_def.h
+++ b/plat/xilinx/zynqmp/include/zynqmp_def.h
@@ -102,6 +102,7 @@
#define PMU_GLOBAL_BASE U(0xFFD80000)
#define PMU_GLOBAL_CNTRL (PMU_GLOBAL_BASE + 0)
#define PMU_GLOBAL_GEN_STORAGE6 (PMU_GLOBAL_BASE + U(0x48))
+#define PMU_GLOBAL_GEN_STORAGE5 (PMU_GLOBAL_BASE + U(0x44))
#define PMU_GLOBAL_REQ_PWRUP_STATUS (PMU_GLOBAL_BASE + U(0x110))
#define PMU_GLOBAL_REQ_PWRUP_EN (PMU_GLOBAL_BASE + U(0x118))
#define PMU_GLOBAL_REQ_PWRUP_DIS (PMU_GLOBAL_BASE + U(0x11c))

View File

@ -127,13 +127,20 @@ if &programnow
FLASHFILE.ReProgram off
)
; Flash signed application image at partition offset
DIALOG.YESNO "Flash signed application image now?"
; Flash signed FIT image at the wolfBoot boot partition offset.
; Offset must match WOLFBOOT_PARTITION_BOOT_ADDRESS in the active .config
; (config/examples/zynqmp_fsbl.config = 0x800000). This is the combined
; dual-parallel QSPI linear offset, same address space as BOOT.BIN@0x0.
; The signed FIT is produced by:
; mkimage -f hal/zynqmp_fsbl.its fitImage
; ./tools/keytools/sign --rsa4096 --sha3 fitImage <key>.der 1
; and must sit in the TRACE32 working directory (alongside BOOT.BIN).
DIALOG.YESNO "Flash signed FIT image (fitImage_v1_signed.bin) now?"
ENTRY &programnow
if &programnow
(
FLASHFILE.ReProgram ALL
FLASHFILE.Load "test-app/image_v1_signed.bin" 0x7000000
FLASHFILE.Load "fitImage_v1_signed.bin" 0x800000
FLASHFILE.ReProgram off
)

View File

@ -0,0 +1,23 @@
// Boot BIF for wolfBoot as the ZynqMP FSBL REPLACEMENT (ZCU102).
//
// wolfBoot is the [bootloader] partition: the BootROM loads it into OCM and
// enters it at EL3. There is NO zynqmp_fsbl.elf. PMUFW is still loaded by the
// BootROM via [pmufw_image]. wolfBoot itself runs psu_init() and then loads +
// verifies the downstream images (BL31, kernel, DTB) from its own QSPI/SD
// partitions using wolfBoot signing keys, so they are NOT listed here.
//
// The same image works for QSPI and SD boot; the boot device is selected by
// the ZCU102 boot-mode pins (SW6). See README for deployment.
//
// Usage:
// bootgen -arch zynqmp -image tools/scripts/zcu102/zynqmp_wolfboot_fsbl.bif \
// -w -o BOOT.BIN
//
// Required files (copy next to this BIF, or use full paths):
// wolfboot.elf - wolfBoot, linked at OCM 0xFFFC0000 (built ZYNQMP_FSBL=1)
// pmufw.elf - Platform Management Unit firmware (from PetaLinux/Vitis)
the_ROM_image:
{
[bootloader, destination_cpu=a53-0] wolfboot.elf
[pmufw_image] pmufw.elf
}

View File

@ -0,0 +1,41 @@
// Boot BIF for wolfBoot as the ZynqMP FSBL REPLACEMENT (ZCU102) with Xilinx
// hardware root-of-trust (RSA authentication via eFuse PPK).
//
// Same layout as zynqmp_wolfboot_fsbl.bif but the BootROM authenticates
// wolfBoot (and PMUFW) with RSA-4096/SHA3-384 before running them. This is the
// hardware root of trust: the BootROM verifies wolfBoot against the eFuse PPK
// hash; wolfBoot then verifies the downstream images with its OWN keys.
//
// Bring-up vs production:
// - [fsbl_config] bh_auth_enable below makes the BootROM trust the PPK in the
// boot header WITHOUT checking it against the eFuse PPK hash. Use this for
// development only. Remove it for production and program the eFuse PPK0
// hash + blow RSA_EN so the BootROM enforces the check.
// - Generate the PPK eFuse hash: bootgen -arch zynqmp -efuseppkbits ...
// - Program eFuses with the xilskey programming example.
//
// Usage:
// bootgen -arch zynqmp \
// -image tools/scripts/zcu102/zynqmp_wolfboot_fsbl_auth.bif \
// -w -o BOOT.BIN
//
// Required files:
// wolfboot.elf - wolfBoot linked at OCM 0xFFFC0000 (built ZYNQMP_FSBL=1)
// pmufw.elf - Platform Management Unit firmware
// pskf.pem - primary secret (private) key
// sskf.pem - secondary secret (private) key
the_ROM_image:
{
// Development only: trust the boot-header PPK without the eFuse check.
// Remove for production (program the eFuse PPK0 hash + RSA_EN instead).
[fsbl_config] bh_auth_enable
// Primary public key 0, secondary public key id 0
[auth_params] ppk_select=0; spk_id=0x00000000
[pskfile] pskf.pem
[sskfile] sskf.pem
[bootloader, destination_cpu=a53-0, authentication=rsa] wolfboot.elf
[pmufw_image, authentication=rsa] pmufw.elf
}

View File

@ -287,6 +287,36 @@ START_TEST (test_ramboot_success)
}
END_TEST
START_TEST (test_ramboot_overlap_predicate)
{
/* wolfBoot occupies [0x1000, 0x2000) for these checks (the two-sided
* range-intersection used by wolfBoot_ramboot's overlap guard). */
const uintptr_t lo = 0x1000, hi = 0x2000;
/* No overlap: image entirely below, entirely above, or exactly adjacent. */
ck_assert_int_eq(ramboot_region_overlap(0x0100, 0x0900, lo, hi), 0);
ck_assert_int_eq(ramboot_region_overlap(0x3000, 0x4000, lo, hi), 0);
ck_assert_int_eq(ramboot_region_overlap(0x0800, 0x1000, lo, hi), 0); /* img_hi==wb_lo */
ck_assert_int_eq(ramboot_region_overlap(0x2000, 0x2800, lo, hi), 0); /* img_lo==wb_hi */
/* Overlap: straddle low edge, straddle high edge, image inside wolfBoot,
* wolfBoot inside image. */
ck_assert_int_ne(ramboot_region_overlap(0x0800, 0x1800, lo, hi), 0);
ck_assert_int_ne(ramboot_region_overlap(0x1800, 0x2800, lo, hi), 0);
ck_assert_int_ne(ramboot_region_overlap(0x1400, 0x1C00, lo, hi), 0);
ck_assert_int_ne(ramboot_region_overlap(0x0800, 0x2800, lo, hi), 0);
/* Unknown-origin fallback (wb_lo == 0): guard only that img is below wb_hi. */
ck_assert_int_ne(ramboot_region_overlap(0x0100, 0x0900, 0, hi), 0);
ck_assert_int_eq(ramboot_region_overlap(0x2000, 0x2800, 0, hi), 0);
/* img_hi overflow (header+size wrapped so img_hi < img_lo) is rejected
* conservatively as an overlap, regardless of the wolfBoot range. */
ck_assert_int_ne(ramboot_region_overlap(0x0800, 0x0400, lo, hi), 0);
ck_assert_int_ne(ramboot_region_overlap(0x3000, 0x0400, lo, hi), 0);
}
END_TEST
START_TEST (test_sunnyday_noupdate)
{
@ -545,6 +575,7 @@ Suite *wolfboot_suite(void)
TCase *ramboot_invalid_header = tcase_create("Ramboot invalid header");
TCase *ramboot_oversize = tcase_create("Ramboot oversize");
TCase *ramboot_success = tcase_create("Ramboot success");
TCase *ramboot_overlap = tcase_create("Ramboot overlap predicate");
TCase *sunnyday_noupdate =
tcase_create("Sunny day test with no update available");
TCase *forward_update_samesize =
@ -573,6 +604,7 @@ Suite *wolfboot_suite(void)
tcase_add_test(ramboot_invalid_header, test_ramboot_invalid_header);
tcase_add_test(ramboot_oversize, test_ramboot_oversize_rejected);
tcase_add_test(ramboot_success, test_ramboot_success);
tcase_add_test(ramboot_overlap, test_ramboot_overlap_predicate);
tcase_add_test(sunnyday_noupdate, test_sunnyday_noupdate);
tcase_add_test(forward_update_samesize, test_forward_update_samesize);
tcase_add_test(forward_update_tolarger, test_forward_update_tolarger);
@ -595,6 +627,7 @@ Suite *wolfboot_suite(void)
suite_add_tcase(s, ramboot_invalid_header);
suite_add_tcase(s, ramboot_oversize);
suite_add_tcase(s, ramboot_success);
suite_add_tcase(s, ramboot_overlap);
suite_add_tcase(s, sunnyday_noupdate);
suite_add_tcase(s, forward_update_samesize);
suite_add_tcase(s, forward_update_tolarger);
@ -616,6 +649,7 @@ Suite *wolfboot_suite(void)
tcase_set_timeout(ramboot_invalid_header, 5);
tcase_set_timeout(ramboot_oversize, 5);
tcase_set_timeout(ramboot_success, 5);
tcase_set_timeout(ramboot_overlap, 5);
tcase_set_timeout(sunnyday_noupdate, 5);
tcase_set_timeout(forward_update_samesize, 5);
tcase_set_timeout(forward_update_tolarger, 5);