Add Xilinx Zynq-7000 (ZC702) wolfBoot port

pull/778/head
David Garske 2026-05-08 16:15:32 -07:00 committed by Daniele Lacamera
parent 7b0bee50de
commit b94954eab4
19 changed files with 2786 additions and 93 deletions

View File

@ -667,6 +667,18 @@ jobs:
arch: aarch64
config-file: ./config/examples/zynqmp_sdcard.config
zynq7000_test:
uses: ./.github/workflows/test-build.yml
with:
arch: arm
config-file: ./config/examples/zynq7000.config
zynq7000_sdcard_test:
uses: ./.github/workflows/test-build.yml
with:
arch: arm
config-file: ./config/examples/zynq7000_sdcard.config
versal_vmk180_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:

View File

@ -285,6 +285,10 @@ ifeq ($(TARGET),sama5d3)
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
endif
ifeq ($(TARGET),zynq7000)
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
endif
ifeq ($(TARGET),rp2350)
MAIN_TARGET:=include/target.h keytools wolfboot_signing_private_key.der pico-sdk-info
endif

77
arch.mk
View File

@ -312,6 +312,30 @@ ifeq ($(ARCH),ARM)
CFLAGS+=-DWOLFBOOT_USE_STDLIBC
endif
ifeq ($(TARGET),zynq7000)
# AMD/Xilinx Zynq-7000 (Cortex-A9, ARMv7-A) - ZC702 Evaluation Kit.
# Loaded by Xilinx FSBL into DDR; see hal/zynq7000.{c,h,ld}.
CORTEX_A9=1
UPDATE_OBJS:=src/update_ram.o
CFLAGS+=-DWOLFBOOT_DUALBOOT -fno-builtin -ffreestanding
# Do NOT define WOLFBOOT_USE_STDLIBC: newlib's memcpy uses unaligned
# LDRs which fault on ARMv7-A whenever the active mapping treats memory
# as Strongly-Ordered (typically MMU off, but also some boot-stage
# configurations). wolfBoot's startup keeps FSBL's MMU + flat 1:1
# mapping enabled to avoid that, but we still link against the
# aligned-safe memcpy in src/string.c so unaligned loads can never
# surprise us regardless of MMU state.
# Enable the legacy 64-byte uImage header strip unconditionally to
# match sibling Xilinx targets (zynqmp, versal). update_ram.c
# validates magic + ih_hcrc (header CRC32) + ih_size before
# stripping, so a non-uImage payload whose first 4 bytes happen to
# match UBOOT_IMG_HDR_MAGIC cannot be silently treated as a uImage
# -- the additional CRC32 + size checks drop the joint false-
# positive probability from ~2^-32 to ~2^-64, matching what U-Boot's
# own mkimage/bootm does.
CFLAGS+=-DWOLFBOOT_UBOOT_LEGACY
endif
ifeq ($(TARGET),va416x0)
CFLAGS+=-I$(WOLFBOOT_ROOT)/hal/vorago/ \
-I$(VORAGO_SDK_DIR)/common/drivers/hdr/ \
@ -353,6 +377,52 @@ ifeq ($(CORTEX_A5),1)
-DWOLFSSL_ARM_ARCH=7 -DWOLFSSL_ARMASM_INLINE -DWOLFSSL_ARMASM_NO_NEON
endif
endif
else
ifeq ($(CORTEX_A9),1)
# Cortex-A9 (ARMv7-A, 32-bit) - Zynq-7000.
# Build in ARM state (-marm); reset vector lands in ARM mode after FSBL.
# Note: do not filter out -mthumb from CFLAGS/LDFLAGS - that converts the
# variables to simple-expansion flavor and breaks lazy $(LSCRIPT) expansion
# in test-app/Makefile. -marm appended later wins over -mthumb anyway.
FPU=-mfpu=vfp3-d16
CFLAGS+=-mcpu=cortex-a9 -mtune=cortex-a9 -marm -mno-unaligned-access
LDFLAGS+=-mcpu=cortex-a9 -mtune=cortex-a9 -marm -static \
-Wl,-z,noexecstack
# Cortex-A9 uses the same generic ARMv7-A startup as Cortex-A5
# (src/boot_arm32_start.S handles VBAR, per-mode stacks, cache
# invalidate, async-abort enable for any ARMv7-A target).
OBJS+=src/boot_arm32.o src/boot_arm32_start.o
# Linux/U-Boot payload: enable MMU + FDT codepaths in update_ram.c so DTBs
# can be loaded from a separate signed PART_DTS_BOOT partition. The MMU
# itself stays inherited from FSBL's flat 1:1 mapping; wolfBoot does not
# manage page tables on Cortex-A9.
ifeq ($(MMU),1)
CFLAGS+=-DMMU -DWOLFBOOT_FDT
OBJS+=src/fdt.o
endif
# CRC32 helpers in src/gpt.c are reused by update_ram.c's uImage header
# validator (WOLFBOOT_UBOOT_LEGACY), so link gpt.o for every Cortex-A9
# build, not just the disk-boot variant below.
OBJS+=src/gpt.o
# SD card / eMMC boot: swap the update_ram loader for update_disk + GPT.
# The SDHCI HAL hooks live in hal/zynq7000.c and translate the generic
# Cadence-layout driver to the Arasan SDHCI v2.0 controller.
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
CFLAGS+=-DWOLFBOOT_UPDATE_DISK -DMAX_DISKS=1
UPDATE_OBJS:=src/update_disk.o
OBJS += src/disk.o
endif
ifeq ($(NO_ASM),1)
MATH_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_c32.o
else
MATH_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_arm32.o
ifneq ($(NO_ARM_ASM),1)
OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/arm/armv8-32-sha256-asm.o
OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/arm/armv8-32-sha256-asm_c.o
CFLAGS+=-DWOLFSSL_SP_ARM32_ASM -DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO \
-DWOLFSSL_ARM_ARCH=7 -DWOLFSSL_ARMASM_INLINE -DWOLFSSL_ARMASM_NO_NEON
endif
endif
else
# All others use boot_arm.o
OBJS+=src/boot_arm.o
@ -465,6 +535,7 @@ else
endif
endif
endif
endif
## Renesas RX
@ -1760,6 +1831,11 @@ endif
ifeq ($(ARCH),AARCH64)
CFLAGS+=-DMMU -DWOLFBOOT_FDT -DWOLFBOOT_DUALBOOT
OBJS+=src/fdt.o
# src/gpt.c provides the CRC32 helpers reused by update_ram.c's uImage
# header validator under WOLFBOOT_UBOOT_LEGACY (zynq, versal). Also
# needed for the disk variant. Targets without WOLFBOOT_UBOOT_LEGACY or
# disk support (e.g. nxp_ls1028a) GC the unused code via --gc-sections.
OBJS+=src/gpt.o
ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),)
# Disk-based boot (SD card or eMMC)
CFLAGS+=-DWOLFBOOT_UPDATE_DISK
@ -1768,7 +1844,6 @@ ifeq ($(ARCH),AARCH64)
endif
CFLAGS+=-DMAX_DISKS=$(MAX_DISKS)
UPDATE_OBJS:=src/update_disk.o
OBJS+=src/gpt.o
OBJS+=src/disk.o
else
# RAM-based boot from external flash (default)

View File

@ -0,0 +1,65 @@
ARCH?=ARM
TARGET?=zynq7000
SIGN?=ECC256
HASH?=SHA256
# Cortex-A9 (Zynq-7000) - selected automatically via TARGET=zynq7000 in arch.mk.
# wolfBoot replaces U-Boot in the Z7 boot flow (BootROM -> FSBL -> wolfBoot ->
# kernel/app, no U-Boot stage). This single config supports both bare-metal
# and Linux payloads from QSPI.
DEBUG?=0
DEBUG_UART?=1
V?=0
SPMATH?=1
# One config for both bare-metal and Linux payloads. do_boot in
# src/boot_arm32.c always emits the ARM Linux boot ABI (r0=0, r1=~0,
# r2=DTB_phys, r3=0) when MMU=1; bare-metal apps simply ignore r0..r3,
# so a single register-handoff covers both payload types.
# MMU=1 -> enables update_ram.c DTB-load codepath and pulls in
# src/fdt.o. wolfBoot itself does NOT manage page tables; it
# inherits FSBL's flat 1:1 DDR mapping.
# ELF=1 -> wolfBoot understands ELF inputs (e.g. vmlinux) and loads
# only their LOAD segments. Flat binaries (zImage, bare-metal
# .bin) fall through to raw-binary boot.
# Cost vs. a strictly bare-metal-only build: ~5 KB extra wolfBoot binary
# from the FDT/MMU/ELF support, in exchange for one config that covers
# both payload types.
MMU=1
ELF=1
# wolfBoot itself is staged by FSBL to DDR at 0x04000000 (hal/zynq7000.ld);
# the verified payload (kernel or bare-metal app) is staged at
# WOLFBOOT_LOAD_ADDRESS, well clear of wolfBoot. 1 GB DDR3 on ZC702
# starts at 0x00000000.
WOLFBOOT_LOAD_ADDRESS=0x10000000
# DTB load address (Linux only). Kernel reads it from r2. 16 MB clear of
# WOLFBOOT_LOAD_ADDRESS. Ignored for bare-metal payloads.
WOLFBOOT_LOAD_DTS_ADDRESS=0x11000000
# QSPI flash (16 MB N25Q128A on ZC702) via XQspiPs (hal/zynq7000.c).
# Override EXT_FLASH=0 on the make command line for JTAG-only dev builds.
EXT_FLASH?=1
NO_XIP=1
# QSPI partition layout (16 MB total) - sized for a full Linux kernel + DTB
# pair so the same layout also works for bare-metal payloads.
# 0x000000 - 0x07FFFF BOOT.BIN (FSBL + wolfboot, 512 KB)
# 0x080000 - 0x0FFFFF DTS_BOOT (signed DTB, 512 KB - Linux only)
# 0x100000 - 0x6FFFFF BOOT_A (~6 MB primary)
# 0x700000 - 0x77FFFF DTS_UPD (signed update DTB, 512 KB - Linux only)
# 0x780000 - 0xDFFFFF UPDATE_B (~6.5 MB update)
# 0xE00000 - 0xE0FFFF SWAP (64 KB scratch)
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00780000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x00E00000
WOLFBOOT_PARTITION_SIZE=0x00600000
WOLFBOOT_SECTOR_SIZE=0x10000
WOLFBOOT_DTS_BOOT_ADDRESS=0x00080000
WOLFBOOT_DTS_UPDATE_ADDRESS=0x00700000
IMAGE_HEADER_SIZE=1024
CROSS_COMPILE?=arm-none-eabi-

View File

@ -0,0 +1,114 @@
ARCH?=ARM
TARGET?=zynq7000
SIGN?=ECC256
HASH?=SHA256
# Cortex-A9 Zynq-7000 SD-card boot variant. Uses the generic SDHCI driver
# (src/sdhci.c) with HAL hooks in hal/zynq7000.c that translate between the
# driver's Cadence SD4HC register layout and the Arasan SDHCI v2.0 standard
# layout used by the Zynq-7000 controller (same IP family as ZynqMP's v3.0,
# just an older revision; the translation is reused from hal/zynq.c).
#
# wolfBoot replaces U-Boot in the Z7 boot flow (BootROM -> FSBL -> wolfBoot
# -> kernel/app, no U-Boot stage). This single config supports both
# bare-metal and Linux payloads from SD card -- see the MMU/ELF block below.
DEBUG?=0
DEBUG_UART?=1
V?=0
SPMATH?=1
# SD card boot - swaps update_ram.o for update_disk.o + GPT/disk support.
DISK_SDCARD=1
NO_XIP=1
# One config for both bare-metal and Linux payloads. do_boot in
# src/boot_arm32.c always emits the ARM Linux boot ABI (r0=0, r1=~0,
# r2=DTB_phys, r3=0) when MMU=1; bare-metal apps simply ignore r0..r3,
# so a single register-handoff covers both payload types.
# MMU=1 -> pulls in src/fdt.o for FDT-aware paths in update_disk.c.
# wolfBoot does NOT manage page tables; it inherits FSBL's
# flat 1:1 DDR mapping.
# ELF=1 -> wolfBoot understands ELF inputs (e.g. vmlinux) and loads
# only their LOAD segments. Flat binaries (zImage, bare-metal
# .bin) fall through to raw-binary boot.
# For Linux from SD use tools/scripts/zynq7000/prepare_linux.sh APPENDED=1
# (DTB concatenated to zImage and signed as one image). update_disk.c does
# not read a separate PART_DTS_BOOT partition; the appended-DTB path is
# what carries the device tree to the kernel via CONFIG_ARM_APPENDED_DTB.
MMU=1
ELF=1
# Stage payload at low DDR (clear of wolfBoot at 0x04000000-0x040FFFFF).
WOLFBOOT_LOAD_ADDRESS=0x10000000
# DTB load address (Linux only, used by update_disk.c when a FIT image
# carries a DTB). Ignored for bare-metal and for the appended-DTB Linux
# flow. 16 MB clear of WOLFBOOT_LOAD_ADDRESS.
WOLFBOOT_LOAD_DTS_ADDRESS=0x11000000
# MBR partition layout on the SD card. Pure MBR (no GPT) - the Zynq-7000
# BootROM (UG821 ch.6.3) only accepts MBR with the first partition as
# FAT32 and the Active flag set. wolfBoot's src/disk.c falls back to MBR
# parsing when no protective-GPT entry is present.
# MBR p1 (wolfBoot idx 0): FAT32-LBA Active - holds BOOT.BIN for BootROM.
# MBR p2 (wolfBoot idx 1): Linux raw (0x83) - signed boot image.
# MBR p3 (wolfBoot idx 2): Linux raw (0x83) - signed update image.
# tools/scripts/zynq7000/prepare_sdcard.sh lays this out; BOOT_PART_A/B tell
# update_disk.c which MBR entries (0-indexed) to use for boot/update.
CFLAGS_EXTRA+=-DBOOT_PART_A=1 -DBOOT_PART_B=2
# Arasan SDHCI v2.0 on Zynq-7000 is 3.3V-only, no UHS-I. The generic
# driver tries to push the card to UHS-I SDR25 / 50 MHz / High Speed mode
# which is invalid for our v2.0 + 3.3V combo and causes DTOE on the first
# data transfer (MBR read). Cap the post-init clock at SD default-speed
# 25 MHz; the HSE bit is also masked in hal/zynq7000.c sdhci_reg_write so
# the controller stays in single-edge timing the card matches.
# Cap the post-init SDHCI clock at 6 MHz. The Arasan SDHCI v2.0 on
# Zynq-7000 has a clock-dependent state-cleanup issue: at 12 MHz multi-
# block reads (CMD18) work, but a single-block read (CMD17) issued
# immediately after a CMD18+CMD12 sequence times out (DTOE) on the first
# data block. At 24 MHz even the very first CMD17 fails. 6 MHz / 4-bit
# bus is plenty fast for boot-time loading (~3 MB/s) and is well below
# the v2.0 quirk threshold; raise this if a future fix in src/sdhci.c
# adds an explicit DAT-line reset between transfers.
CFLAGS_EXTRA+=-DSDHCI_CLK_50MHZ=6000 -DSDHCI_CLK_25MHZ=6000
# update_disk.c reads images in DISK_BLOCK_SIZE chunks. Default 512 B = one
# disk_read = one CMD17 per 512 B, which makes a multi-MB Linux load issue
# thousands of CMDs and stall the card with per-CMD overhead. Bump to
# 512 KB so each disk_read pulls 1024 blocks via one CMD18 SDMA (matches
# ZynqMP). Verified on ZC702 with a 4.76 MB appended-DTB zImage: 9 CMD18s
# complete in well under a second. The default 4 KB SDMA buffer boundary
# is left in place -- overriding it to 512 KB stalled SDMA on Arasan v2.0.
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000
# Uncomment for verbose SDHCI driver logging when bringing up new boards
# or debugging timing issues.
#CFLAGS_EXTRA+=-DDEBUG_SDHCI
# Image-header partition addresses are unused for disk boot (kept for the
# Makefile sanity checks). update_disk.c finds images by GPT entry, not by
# memory address.
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00700000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x00D00000
WOLFBOOT_PARTITION_SIZE=0x00600000
# Sector size of WOLFBOOT_PARTITION (not the SD physical sector, which is
# always 512 B). Used as the smallest erase/copy unit for the BOOT/UPDATE
# partitions; must be > IMAGE_HEADER_SIZE.
WOLFBOOT_SECTOR_SIZE=0x1000
IMAGE_HEADER_SIZE=1024
# Required by image.c when MMU=1 is set, even though update_disk.c never
# opens PART_DTS_BOOT/PART_DTS_UPDATE on this target (the disk boot path
# selects images by partition index -- BOOT_PART_A / BOOT_PART_B above --
# not by memory-mapped address, and the DTB travels with the kernel via
# appended-DTB or FIT). src/disk.c parses either GPT or MBR (it falls
# back to MBR when there is no protective-GPT entry), so the layout
# choice is orthogonal to this knob. Set to dummy addresses to satisfy
# the build.
WOLFBOOT_DTS_BOOT_ADDRESS=0x0
WOLFBOOT_DTS_UPDATE_ADDRESS=0x0
CROSS_COMPILE=arm-none-eabi-

View File

@ -55,6 +55,7 @@ This README describes configuration of supported targets.
* [TI Hercules TMS570LC435](#ti-hercules-tms570lc435)
* [Vorago VA416x0](#vorago-va416x0)
* [Xilinx Zynq UltraScale](#xilinx-zynq-ultrascale)
* [Xilinx Zynq-7000 (ZC702)](#xilinx-zynq-7000-zc702)
* [Versal Gen 1 VMK180](#versal-gen-1-vmk180)
## STM32F4
@ -3709,6 +3710,313 @@ FDT: Set chosen (...), linux,initrd-end=...
```
## Xilinx Zynq-7000 (ZC702)
AMD/Xilinx Zynq-7000 (XC7Z020) on the ZC702 Evaluation Kit - dual ARM Cortex-A9 (ARMv7-A 32-bit), 1 GB DDR3, 16 MB QSPI NOR (N25Q128A), SDIO, dual UART. Older sibling of the ZynqMP family - distinct silicon, different controllers (`XQspiPs` not `XQspiPsu`, Arasan SDHCI v2.0 not v3.0, no CSU/PMU/PUF, PL310 L2).
wolfBoot replaces U-Boot in the Zynq-7000 boot flow -- there is no
U-Boot stage. wolfBoot is loaded by the Xilinx Zynq-7000 FSBL into
DDR:
```
BootROM -> FSBL -> wolfBoot -> signed app or Linux kernel
```
The FSBL handles all PS init (DDR, MIO, clocks, QSPI ref clock); wolfBoot only initializes UART, the QSPI controller, runs the verify/swap logic, and chain-loads the next stage.
This target supports:
- **QSPI boot** (primary): `config/examples/zynq7000.config` -- one config for both bare-metal and Linux payloads (MMU=1 + ELF=1; bare-metal apps don't pay any runtime cost beyond ~5 KB of unused FDT/MMU code).
- **SD card boot**: `config/examples/zynq7000_sdcard.config` -- bare-metal **and** Linux payloads from MBR-partitioned SD via the generic SDHCI driver and the Arasan v2.0 translation in `hal/zynq7000.c`.
- **JTAG-loaded dev** via Platform Cable II + xsdb (no flash required).
### Prerequisites
1. **Toolchain**: `arm-none-eabi-gcc` (Arm bare-metal). Tested with 13.2.
2. **Xilinx Vitis** (provides `bootgen`, `xsdb`, and `program_flash`). Source the env once per shell:
```sh
source /opt/Xilinx/2025.2/Vitis/settings64.sh
```
Vivado's `settings64.sh` works equivalently if you don't have Vitis installed.
3. **Platform Cable II USB drivers** (one-time, requires root). Without these the
cable enumerates as `03fd:0013` with empty descriptors and `xsdb` reports no
JTAG targets:
```sh
sudo /opt/Xilinx/2025.2/Vitis/data/xicom/cable_drivers/lin64/install_script/install_drivers/install_drivers
```
Unplug/replug the cable afterward so udev can load the firmware.
4. **Pre-built ZC702 FSBL + DTB** (clone next to your wolfboot working tree):
```sh
git clone https://github.com/wolfSSL/soc-prebuilt-firmware.git
export PREBUILT_DIR=$(pwd)/../soc-prebuilt-firmware/zc702-zynq
ls $PREBUILT_DIR/zynq_fsbl.elf # required
```
5. **Hardware**: ZC702 with Platform Cable II (USB JTAG) connected to J22 and powered.
### Configuration Options
Key options in `config/examples/zynq7000.config`:
- `ARCH=ARM` - 32-bit ARM
- `TARGET=zynq7000` - selects `hal/zynq7000.{c,h,ld}` and the `CORTEX_A9` arch.mk block
- `SIGN=ECC256` / `HASH=SHA256` - smaller and faster than RSA on Cortex-A9
- `MMU=1 ELF=1` - lets the same image boot Linux or bare-metal. `do_boot` always emits the ARM Linux boot ABI (`r0=0`, `r1=~0`, `r2=DTB_phys`, `r3=0`) on this target, which bare-metal apps simply ignore. `MMU=1` enables `update_ram.c`'s DTB-load codepath and pulls in `src/fdt.o`; wolfBoot does not manage page tables (it inherits FSBL's flat 1:1 DDR mapping). `ELF=1` lets wolfBoot understand ELF inputs (e.g. `vmlinux`) and load only their LOAD segments. Cost over a strictly bare-metal-only build: ~5 KB extra wolfBoot binary (31 KB -> 36 KB).
- `EXT_FLASH=1` - QSPI as external flash via `XQspiPs`
- `WOLFBOOT_LOAD_ADDRESS=0x10000000` - DDR offset 256 MB, where the verified app is staged before `do_boot`. Must be **above** wolfBoot's own region (`0x04000000`-`0x040FFFFF`) because `src/update_ram.c` enforces `dst > _end`.
- `WOLFBOOT_LOAD_DTS_ADDRESS=0x11000000` - DDR offset 272 MB, where a DTB read out of `PART_DTS_BOOT` would be relocated. Ignored for bare-metal payloads and for the appended-DTB Linux flow (where the DTB lives at the end of the signed kernel image).
- `WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000` - 16 MB QSPI layout below
- `CROSS_COMPILE=arm-none-eabi-`
DDR layout:
| Region | Address range | Contents |
|---|---|---|
| App stage | `0x10000000`+ | Verified signed image, app text/data/bss/stack |
| Image header staging | `0x0FFFFC00`-`0x0FFFFFFF` | wolfBoot copies the 1 KB header here just before the load address |
| wolfBoot | `0x04000000`-`0x040FFFFF` | Loaded by FSBL, runs in place |
| FSBL/BootROM/OCM | `0x00000000`-`0x000FFFFF` | OCM low-mapped during boot |
QSPI partition layout (16 MB on-board flash):
| Offset | Size | Contents |
|-------------|---------|-----------------------------------|
| `0x000000` | ~512 KB | BOOT.BIN (FSBL + wolfboot) |
| `0x100000` | 6 MB | BOOT_A (signed primary image) |
| `0x700000` | 6 MB | UPDATE_B (signed update slot) |
| `0xD00000` | 64 KB | SWAP scratch sector |
| `0xD10000`+ | | reserved |
### Building wolfBoot
```sh
cp config/examples/zynq7000.config .config
make keysclean && make keytools
make TARGET=zynq7000 wolfboot.elf
```
The result is a 32-bit ARM ELF with entry point `0x04000000` and `.text` start at the same address (vector table at the load base).
### Building BOOT.BIN (production QSPI boot)
```sh
cp ${PREBUILT_DIR}/zynq_fsbl.elf .
bootgen -arch zynq -image tools/scripts/zynq7000/zynq7000_qspi.bif -w -o BOOT.BIN
```
`bootgen` ships with Vitis. The `.bif` template at `tools/scripts/zynq7000/zynq7000_qspi.bif` is the minimum bootable image; add `download.bit` and a DTB if you also need to load the PL bitstream and a Linux device tree (see Milestone 5).
### Programming QSPI
The 16 MB QSPI flash holds two artifacts: `BOOT.BIN` at offset `0x0` (FSBL + wolfBoot, < 1 MB) and the signed payload at `WOLFBOOT_PARTITION_BOOT_ADDRESS` (default `0x100000` for the BOOT_A partition). Both go through `program_flash` over JTAG.
Set ZC702 boot mode straps to **JTAG** (SW16 all OFF) for programming. Then run two commands:
```sh
# 1. List JTAG targets and note the arm_dap target ID for the Xilinx Platform
# Cable USB II (skip this step if you only have one JTAG cable connected).
program_flash -jtagtargets -url TCP:127.0.0.1:3121
# 2. Program BOOT.BIN at offset 0
program_flash -f BOOT.BIN -offset 0 -flash_type qspi-x4-single \
-fsbl ${PREBUILT_DIR}/zynq_fsbl.elf \
-target_id <arm_dap_id> -url TCP:127.0.0.1:3121
# 3. Program the signed payload at the BOOT_A partition offset
program_flash -f test-app/image_v1_signed.bin -offset 0x100000 \
-flash_type qspi-x4-single -fsbl ${PREBUILT_DIR}/zynq_fsbl.elf \
-target_id <arm_dap_id> -url TCP:127.0.0.1:3121
```
`program_flash` ships with Vitis. The Vivado Hardware Manager UI works equivalently (Tools -> Add Configuration Memory Device -> select N25Q128 -> program two files at offsets 0 and 0x100000).
After programming, set boot mode to **QSPI** by turning **SW16-4 ON** (the four-position dip mapping is `SW16-4 = MIO[5]` MSB of the boot device strap, with SW16-1..3 = MIO[2..4]; per UG850 ch.1.2.4). Power-cycle the board (cold) so the BootROM re-samples the strap. Console comes up on UART1 (J17 USB-UART), 115200 8N1, and you should see the wolfBoot banner followed by `=== ZC702 test-app: BOOT OK ===` (or, with a signed kernel in the BOOT partition, the Linux boot log).
`program_flash` may print a segmentation fault on exit ("Flash Operation Successful" precedes it) -- that's a Vitis tool quirk on cleanup, not a programming failure; the flash content is correct.
### JTAG-loaded development (no flash)
For driver bring-up or quick iteration, skip bootgen and load directly via Platform Cable II:
```sh
source /opt/Xilinx/2025.2/Vitis/settings64.sh # once per shell
xsdb tools/scripts/zynq7000/jtag_load.tcl
```
The script runs the prebuilt FSBL (PS init: DDR/MIO/clocks/UART), then loads `wolfboot.elf` over the top, sets PC to `0x04000000` and CPSR to SVC with IRQ/FIQ masked, and resumes. Override paths via `FSBL_ELF=...` or `WOLFBOOT_ELF=...` env vars.
With a signed image programmed at QSPI offset `0x100000` (see "Building and flashing the signed test app" below), expected UART output is:
```
wolfBoot Zynq-7000 (ZC702) hal_init
Versions: Boot 1, Update 0
Trying Boot partition at 0x100000
Loading header 1024 bytes from 0x100000 to 0xFFFFC00
Loading image 396 bytes from 0x100400 to 0x10000000...done
Boot partition: 0xFFFFC00 (sz 396, ver 0x1, type 0x201)
Checking integrity...done
Verifying signature...done
Successfully selected image in part: 0
Firmware Valid
Booting at 0x10000000
=== ZC702 test-app: BOOT OK ===
wolfBoot verified + chain-loaded this image
.....
```
On a **blank** QSPI (no signed image yet), wolfBoot prints `Versions: Boot 0, Update 0 / No valid image found! / wolfBoot: PANIC!` instead - that is correct behavior, not a bug.
If `xsdb` reports `no targets found` or empty `jtag servers`, either:
- Cable USB drivers not installed - see step 3 of Prerequisites, OR
- A previous run left the CPU in a stuck JTAG state - power-cycle the ZC702 (SW10, the Pi4 GPIO 20 power relay, or your PSU control) and retry.
A separate JTAG-only dev build (no QSPI driver) can be produced with `make EXT_FLASH=0`.
### Building and flashing the signed test app
A minimal Cortex-A9 test app lives at `test-app/app_zynq7000.c` (UART banner + heartbeat dots). The top-level `make` target produces both `wolfboot.elf` and `test-app/image_v1_signed.bin` with the keys generated under `wolfboot_signing_private_key.der`:
```sh
cp config/examples/zynq7000.config .config
make keysclean && make # builds wolfboot.elf + test-app/image_v1_signed.bin
```
Program the signed image to QSPI offset `0x100000` (the BOOT_A partition):
```sh
program_flash -f test-app/image_v1_signed.bin \
-fsbl ${PREBUILT_DIR}/zynq_fsbl.elf \
-flash_type qspi-x4-single -offset 0x100000
```
`program_flash` ships with Vitis. Then run wolfBoot via `xsdb tools/scripts/zynq7000/jtag_load.tcl` - it should verify and chain-load the test app, producing the heartbeat output above.
### QSPI driver self-test (`TEST_EXT_FLASH`)
To exercise the `XQspiPs` driver in isolation - read JEDEC ID, sector erase + page program + linear-mode read-back of a 256-byte pattern at `0x200000`:
```sh
make CFLAGS_EXTRA=-DTEST_EXT_FLASH wolfboot.elf
xsdb tools/scripts/zynq7000/jtag_load.tcl
```
Expected output:
```
qspi: --- TEST_EXT_FLASH start ---
qspi: JEDEC ID = 0x20bb18 rc=00 <- Micron N25Q128
qspi: read @0x100000 = 574f4c468c010000 <- "WOLF" magic from a programmed signed image
qspi: erase sector @ 0x00200000 ...
qspi: page program ...
qspi: post-program JEDEC = 0x20bb18
qspi: rdback[0..7] = 0001020304050607
qspi: --- TEST_EXT_FLASH PASS ---
```
### QSPI driver design
The driver in `hal/zynq7000.c` splits read vs cmd-only paths similarly to how the ZynqMP HAL splits SDHCI CMD17 (single-block PIO) vs CMD18 (multi-block SDMA):
| Operation | Path | Why |
|---|---|---|
| JEDEC ID, RDSR, WREN, sector erase, page program | I/O mode (TXD0/TXD1/2/3 + auto-start) | Short, command-shaped transactions; needs precise byte counts on MOSI |
| Bulk reads (signed image, partition headers) | Linear/XIP mode (`memcpy` from `0xFC000000+offset`) | Hardware-accelerated; controller drives cmd+addr+dummy and presents data through the AXI window |
`qspi_linear_mode_setup()` configures `LQSPI_CR=0x8000010B` (single-bit `FAST_READ` 0x0B + 1 dummy byte) which avoids needing the flash QE bit set. A sacrificial first-byte read primes the linear-mode pipeline before the actual `memcpy`.
For TX-only commands sent without RX capture, `qspi_xfer4` picks `TXD1`/`TXD2`/`TXD3` so the controller clocks exactly *N* bytes on the wire (no 4-byte padding that some flash interprets as additional commands - this caused our WREN to fail in an early iteration).
### Boot flow notes
- **Cortex-A9 startup**: shared `src/boot_arm32_start.S` (generic ARMv7-A startup, also used by SAMA5D3) plus shared `src/boot_arm32.c` for `do_boot()`. Sets VBAR to wolfBoot's vector table at `0x04000000`, clears `SCTLR.{A,C,I,V}`, invalidates I-cache + branch predictor + TLB, sets stack pointers for IRQ/FIQ/ABT/UND/SVC modes, then unmasks async aborts and calls `main`.
- **MMU lifecycle**: wolfBoot **inherits** FSBL's flat 1:1 DDR mapping and **leaves the MMU enabled for the duration of its own run** so unaligned LDR/STR keep working (disabling the MMU on Cortex-A9 makes all memory Strongly-Ordered, which traps unaligned accesses and breaks any ARMv7-A unrolled `memcpy`). It only **disables the MMU at handoff**, inside `hal_prepare_boot()` right before `do_boot()` -- so the chain-loaded payload starts with MMU+caches off in a known-clean architectural state. wolfBoot does not manage its own page tables; it simply rides on FSBL's.
- **memcpy/memset**: do **not** define `WOLFBOOT_USE_STDLIBC` for this target. newlib's ARMv7-A `memcpy` uses unaligned word LDRs from arbitrary alignments and faults under any code path that runs without the MMU configured for Normal memory. wolfBoot's own byte-wise / aligned-word `memcpy` in `src/string.c` is used instead.
- **`ext_flash_read` returns bytes-read** (not 0 on success): `src/update_ram.c` checks `ret != IMAGE_HEADER_SIZE` for the header read and `ret < 0` for the body read.
- **Cache teardown** in `hal_prepare_boot()`: cleans+invalidates L1 D-cache by set/way, invalidates L1 I-cache and branch predictor, then disables MMU+caches via SCTLR before `do_boot()` performs `bx r4`.
- **Register handoff** (`do_boot` in `src/boot_arm32.c`): on this target (which always builds with `MMU=1`), `do_boot` always emits the ARM Linux boot ABI -- `r0 = 0`, `r1 = ~0` (no machine ID, use DTB), `r2 = DTB physical address`, `r3 = 0`, entry in `r4`. Bare-metal apps simply ignore `r0..r3`, so the same ABI covers both payload types and no per-config switch is needed.
- **L2 (PL310)**: not touched by wolfBoot. Stock ZC702 FSBLs do not enable PL310; if your customised FSBL does, extend `hal_prepare_boot()` with an L2x0 clean-invalidate + disable.
### SD card boot (Milestone 6)
`config/examples/zynq7000_sdcard.config` enables SD-card boot via the generic
SDHCI driver (`src/sdhci.c`) with HAL hooks in `hal/zynq7000.c` that
translate the driver's Cadence SD4HC register layout to the Arasan
SDHCI v2.0 standard layout used by the Zynq-7000 controller. The config
sets `MMU=1 ELF=1` so the same SD-card image can chain-
load either a bare-metal app or a signed appended-DTB Linux zImage --
Linux-from-SD is verified end-to-end on ZC702 (full kernel banner +
SMP + driver init through to rootfs panic).
**Strap**: SW16-3 + SW16-4 ON (others OFF). `BOOT_MODE_REG = 0x5`.
**Layout**: pure MBR (no GPT - the Zynq-7000 BootROM only accepts MBR-with-FAT32-Active for SD boot).
| Partition | Type | Size | Contents |
|-----------|-----------|-------|---------------------------------------|
| p1 | 0x0C FAT32-LBA, Active | 64 MB | `BOOT.BIN` for the BootROM |
| p2 | 0x83 Linux raw | 16 MB | Signed boot image (`BOOT_PART_A=1`) |
| p3 | 0x83 Linux raw | 16 MB | Signed update image (`BOOT_PART_B=2`) |
`tools/scripts/zynq7000/prepare_sdcard.sh` lays this out (parted msdos +
manual MBR type/active patch + dd of signed images).
**Bare-metal payload** (signed test app):
```sh
cp config/examples/zynq7000_sdcard.config .config
make TARGET=zynq7000
cp ${PREBUILT_DIR}/zynq_fsbl.elf .
bootgen -arch zynq -image tools/scripts/zynq7000/zynq7000_qspi.bif -w -o BOOT.BIN
sudo ./tools/scripts/zynq7000/prepare_sdcard.sh /dev/sdX
```
**Linux payload** (signed appended-DTB zImage):
```sh
cp config/examples/zynq7000_sdcard.config .config
make TARGET=zynq7000
cp ${PREBUILT_DIR}/zynq_fsbl.elf .
bootgen -arch zynq -image tools/scripts/zynq7000/zynq7000_qspi.bif -w -o BOOT.BIN
./tools/scripts/zynq7000/prepare_linux.sh # appends DTB to zImage and signs as one image
mv image_v1_signed.bin test-app/zImage_signed.bin
sudo ./tools/scripts/zynq7000/prepare_sdcard.sh /dev/sdX test-app/zImage_signed.bin
```
**Arasan SDHCI v2.0 quirks** (handled by the HAL/config):
- 3.3V-only, no UHS-I. The driver tries to enable UHS-I SDR25 / 1.8V
signaling on init; we mask out UMS, 1.8V Enable, sampling-clock,
HV4E, and A64 in `sdhci_reg_write` for SRS15.
- High Speed Enable (HSE bit in HostCtrl1) does not work reliably on
v2.0 with 3.3V cards that didn't switch to HS via CMD6 - we mask
HSE in HostCtrl1 writes.
- Post-init clock is capped at 6 MHz via `SDHCI_CLK_50MHZ=6000`. At
12 MHz the first single-block CMD17 issued after a multi-block
CMD18+CMD12 sequence times out (DTOE) - looks like a state-cleanup
quirk specific to v2.0. At 24 MHz even the very first CMD17 fails.
6 MHz / 4-bit yields ~3 MB/s, plenty for boot-time loads.
- Cortex-A9 Global Timer at PERIPHCLK = CPU_3x2x = 333.33 MHz on the
default ZC702 FSBL clock plan; `Z7_GTIMER_FREQ_HZ` in
`hal/zynq7000.h` defaults to that and feeds `hal_get_timer_us()`
used by the SDHCI driver's `udelay()`.
- `DISK_BLOCK_SIZE=0x80000` (512 KB) is set so `update_disk.c` issues
~10 CMD18 SDMA reads of 512 KB each instead of thousands of CMD17
PIO reads of 512 B each. The default 512 B causes the card to time
out (SRS12 bit 20 / EDT) on multi-MB Linux loads. The default 4 KB
SDMA buffer boundary is left in place; overriding it to 512 KB
stalled SDMA on Arasan v2.0 (TC never fired).
### Differences from the ZynqMP port
| Aspect | ZynqMP (`hal/zynq.c`) | Zynq-7000 (`hal/zynq7000.c`) |
|------------------|-------------------------------|------------------------------|
| CPU | Cortex-A53 quad, AArch64 | Cortex-A9 dual, ARMv7-A |
| QSPI controller | GQSPI (`XQspiPsu`) | Linear/Static (`XQspiPs`) |
| UART IP | XUartPs @ `0xFF000000` | XUartPs @ `0xE0001000` |
| SDHCI | Arasan v3.0 + Cadence shim | Arasan v2.0 + Cadence shim |
| Crypto HW | CSU (AES-GCM, SHA3, PUF) | none (DevC AES only) |
| Boot chain | FSBL + PMUFW + BL31 + wolfBoot| FSBL + wolfBoot |
| Linux EL | EL2 (hypervisor) | SVC (no exception levels) |
| `bootgen -arch` | `zynqmp` | `zynq` |
## Versal Gen 1 VMK180
AMD Versal Prime Series VMK180 Evaluation Kit - Versal Prime XCVM1802-2MSEVSVA2197 Adaptive SoC - Dual ARM Cortex-A72.

1124
hal/zynq7000.c 100644

File diff suppressed because it is too large Load Diff

217
hal/zynq7000.h 100644
View File

@ -0,0 +1,217 @@
/* zynq7000.h
*
* Copyright (C) 2026 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 3 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-1335, USA
*/
/* Xilinx Zynq-7000 (Cortex-A9, ARMv7-A 32-bit) HAL register map.
* Reference: UG585 (Zynq-7000 TRM), UG821 (Zynq-7000 SW Dev Guide).
* Target board: ZC702 Evaluation Kit (XC7Z020).
*/
#ifndef _ZYNQ7000_H_
#define _ZYNQ7000_H_
#include <stdint.h>
/* DDR memory range (PS DDR3 on ZC702: 1 GB) */
#define Z7_DDR_BASE 0x00000000UL
#define Z7_DDR_HIGH 0x3FFFFFFFUL
/* On-chip memory (OCM, 256 KB at high alias when remapped) */
#define Z7_OCM_BASE 0xFFFC0000UL
/* SLCR (System Level Control Registers) - UG585 ch.4 */
#define Z7_SLCR_BASE 0xF8000000UL
#define Z7_SLCR_UNLOCK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x008)))
#define Z7_SLCR_LOCK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x004)))
#define Z7_SLCR_UART_RST (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x228)))
#define Z7_SLCR_LQSPI_RST (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x204)))
#define Z7_SLCR_UART_CLK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x154)))
#define Z7_SLCR_LQSPI_CLK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x14C)))
#define Z7_SLCR_UNLOCK_KEY 0x0000DF0DUL
#define Z7_SLCR_LOCK_KEY 0x0000767BUL
/* UART (XUartPs) - UG585 ch.19. Same IP as ZynqMP, different base. */
#define Z7_UART0_BASE 0xE0000000UL
#define Z7_UART1_BASE 0xE0001000UL
#if defined(DEBUG_UART_NUM) && DEBUG_UART_NUM == 0
#define DEBUG_UART_BASE Z7_UART0_BASE
#elif defined(DEBUG_UART_NUM) && DEBUG_UART_NUM == 1
#define DEBUG_UART_BASE Z7_UART1_BASE
#endif
#ifndef DEBUG_UART_BASE
/* ZC702 console is wired to UART1 (MIO48/49) */
#define DEBUG_UART_BASE Z7_UART1_BASE
#endif
#define Z7_UART_CR (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x00)))
#define Z7_UART_MR (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x04)))
#define Z7_UART_IDR (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x0C)))
#define Z7_UART_ISR (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x14)))
#define Z7_UART_BR_GEN (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x18)))
#define Z7_UART_RXTOUT (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x1C)))
#define Z7_UART_RXWM (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x20)))
#define Z7_UART_SR (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x2C)))
#define Z7_UART_FIFO (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x30)))
#define Z7_UART_BR_DIV (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x34)))
#define Z7_UART_TXWM (*((volatile uint32_t*)(DEBUG_UART_BASE + 0x44)))
#define Z7_UART_CR_TX_DIS 0x00000020U
#define Z7_UART_CR_TX_EN 0x00000010U
#define Z7_UART_CR_RX_DIS 0x00000008U
#define Z7_UART_CR_RX_EN 0x00000004U
#define Z7_UART_CR_TXRST 0x00000002U
#define Z7_UART_CR_RXRST 0x00000001U
#define Z7_UART_ISR_MASK 0x00003FFFU
#define Z7_UART_MR_8N1 0x00000020U /* parity none, 8 data, 1 stop */
#define Z7_UART_SR_TXFULL 0x00000010U
#define Z7_UART_SR_TXEMPTY 0x00000008U
/* PS UART_REF_CLK on ZC702 is 50 MHz (IO_PLL / 20).
* BR_GEN = ref / (baud * (BR_DIV + 1)). For 115200 with BR_DIV=6 -> BR_GEN=62.
*/
#ifndef UART_CLK_REF
#define UART_CLK_REF 50000000U
#endif
#ifndef DEBUG_UART_BAUD
#define DEBUG_UART_BAUD 115200U
#define DEBUG_UART_DIV 6U
#endif
/* QSPI controller (XQspiPs - the older "Linear/Static" QSPI on Z7,
* NOT the GQSPI on ZynqMP). UG585 ch.12. */
#define Z7_QSPI_BASE 0xE000D000UL
#define Z7_QSPI_LINEAR_BASE 0xFC000000UL /* XIP window for linear-mode reads */
#define Z7_QSPI_CR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x00)))
#define Z7_QSPI_ISR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x04)))
#define Z7_QSPI_IER (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x08)))
#define Z7_QSPI_IDR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x0C)))
#define Z7_QSPI_IMR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x10)))
#define Z7_QSPI_EN (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x14)))
#define Z7_QSPI_DELAY (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x18)))
#define Z7_QSPI_TXD0 (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x1C)))
#define Z7_QSPI_RXD (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x20)))
#define Z7_QSPI_SICR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x24)))
#define Z7_QSPI_TXTHR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x28)))
#define Z7_QSPI_RXTHR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x2C)))
#define Z7_QSPI_GPIO (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x30)))
#define Z7_QSPI_LPBK (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x38)))
#define Z7_QSPI_TXD1 (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x80)))
#define Z7_QSPI_TXD2 (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x84)))
#define Z7_QSPI_TXD3 (*((volatile uint32_t*)(Z7_QSPI_BASE + 0x88)))
#define Z7_QSPI_LQSPI_CR (*((volatile uint32_t*)(Z7_QSPI_BASE + 0xA0)))
#define Z7_QSPI_LQSPI_STS (*((volatile uint32_t*)(Z7_QSPI_BASE + 0xA4)))
#define Z7_QSPI_MODID (*((volatile uint32_t*)(Z7_QSPI_BASE + 0xFC)))
/* QSPI Config Register (CR) bits.
* PCS is a 4-bit slave-select decode field [13:10]: 0xF = all CS deasserted,
* 0xE = CS0 active. We mask the whole 4-bit field, not just bit 10.
*/
#define Z7_QSPI_CR_IFMODE 0x80000000U /* flash mem interface mode */
#define Z7_QSPI_CR_HOLD_B 0x00080000U /* drive HOLD high */
#define Z7_QSPI_CR_MANSTRT 0x00010000U /* manual start command (kick) */
#define Z7_QSPI_CR_MANSTRTEN 0x00008000U /* manual start enable */
#define Z7_QSPI_CR_SSFORCE 0x00004000U /* manual CS control */
#define Z7_QSPI_CR_PCS_MASK 0x00003C00U /* PCS field [13:10] */
#define Z7_QSPI_CR_PCS_NONE 0x00003C00U /* all CS deasserted (0xF<<10) */
#define Z7_QSPI_CR_PCS_CS0 0x00003800U /* CS0 asserted (0xE<<10) */
#define Z7_QSPI_CR_REF_CLK 0x00000100U
#define Z7_QSPI_CR_FIFO_WIDTH 0x000000C0U /* must be 11 (32-bit) */
#define Z7_QSPI_CR_BAUD_DIV_MSK 0x00000038U
/* BAUDDIV field is value N in bits[5:3]; clock = ref_clk / 2^(N+1).
* N=1 -> /4, N=2 -> /8, N=3 -> /16. */
#define Z7_QSPI_CR_BAUD_DIV_4 0x00000008U /* /4 (BAUDDIV=1) */
#define Z7_QSPI_CR_BAUD_DIV_8 0x00000010U /* /8 (BAUDDIV=2) */
#define Z7_QSPI_CR_BAUD_DIV_16 0x00000018U /* /16 (BAUDDIV=3) */
#define Z7_QSPI_CR_CPHA 0x00000004U
#define Z7_QSPI_CR_CPOL 0x00000002U
#define Z7_QSPI_CR_MSTREN 0x00000001U
/* QSPI Interrupt Status Register (ISR) bits */
#define Z7_QSPI_ISR_TXUF 0x00000040U /* TX underflow */
#define Z7_QSPI_ISR_RXFULL 0x00000020U /* RX FIFO full */
#define Z7_QSPI_ISR_RXNEMPTY 0x00000010U /* RX FIFO not empty */
#define Z7_QSPI_ISR_TXFULL 0x00000008U /* TX FIFO full */
#define Z7_QSPI_ISR_TXNFULL 0x00000004U /* TX FIFO threshold */
#define Z7_QSPI_ISR_RXOVR 0x00000001U /* RX overrun */
#define Z7_QSPI_ISR_MASK 0x0000007DU
#define Z7_QSPI_EN_VAL 0x00000001U /* enable controller */
/* SLCR clock/reset for QSPI (FSBL normally pre-configures these) */
#define Z7_SLCR_LQSPI_CLK_DIV_MSK 0x00003F00U
#define Z7_SLCR_LQSPI_CLK_DIV_5 0x00000500U
#define Z7_SLCR_LQSPI_CLK_SRCSEL_M 0x00000030U
#define Z7_SLCR_LQSPI_CLK_CLKACT0 0x00000001U
#define Z7_SLCR_LQSPI_RST_REF 0x00000002U
#define Z7_SLCR_LQSPI_RST_CPU 0x00000001U
/* SDIO (Arasan SDHCI v2.0). UG585 ch.10. */
#define Z7_SDIO0_BASE 0xE0100000UL
#define Z7_SDIO1_BASE 0xE0101000UL
/* SDIO clock/reset via SLCR. UG585 ch.4. */
#define Z7_SLCR_SDIO_CLK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x150)))
#define Z7_SLCR_SDIO_RST (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x218)))
#define Z7_SLCR_APER_CLK (*((volatile uint32_t*)(Z7_SLCR_BASE + 0x12C)))
/* SDIO_CLK_CTRL: CLKACT0/1 (bits 0/1), SRCSEL (bits 5:4 = 00 IO_PLL),
* DIVISOR (bits 13:8). For 50 MHz SDIO ref from 1 GHz IO_PLL, DIVISOR=20. */
#define Z7_SLCR_SDIO_CLK_ACT0 0x00000001U
#define Z7_SLCR_SDIO_CLK_ACT1 0x00000002U
#define Z7_SLCR_SDIO_CLK_DIV_SH 8
#define Z7_SLCR_SDIO_CLK_DIV_MSK 0x00003F00U
#define Z7_SLCR_SDIO_RST_REF0 0x00000010U
#define Z7_SLCR_SDIO_RST_REF1 0x00000020U
#define Z7_SLCR_SDIO_RST_CPU0 0x00000001U
#define Z7_SLCR_SDIO_RST_CPU1 0x00000002U
#define Z7_SLCR_APER_SDIO0 0x00000400U /* SDIO0 AMBA APER clock enable */
#define Z7_SLCR_APER_SDIO1 0x00000800U
/* Cortex-A9 Global Timer (64-bit, increments at PERIPHCLK = CPU_3x2x).
* UG585 ch.3.5.4. */
#define Z7_GTIMER_LO (*((volatile uint32_t*)(Z7_GTIMER_BASE + 0x00)))
#define Z7_GTIMER_HI (*((volatile uint32_t*)(Z7_GTIMER_BASE + 0x04)))
#define Z7_GTIMER_CTRL (*((volatile uint32_t*)(Z7_GTIMER_BASE + 0x08)))
#define Z7_GTIMER_CTRL_EN 0x00000001U
/* The Cortex-A9 Global Timer runs at PERIPHCLK, which on Zynq-7000 is the
* CPU_3x2x clock = CPU_6x4x / 2. With the default ZC702 FSBL clock plan
* (ARM_PLL = 1.333 GHz, CPU_6x4x = ARM_PLL/2 = 666.67 MHz), PERIPHCLK is
* 333.33 MHz. Override at compile time if you reclock the CPU. */
#ifndef Z7_GTIMER_FREQ_HZ
#define Z7_GTIMER_FREQ_HZ 333333333UL
#endif
/* DevC (Device Configuration: AES + bitstream loader). UG585 ch.6. */
#define Z7_DEVC_BASE 0xF8007000UL
/* GIC (PL390 / GIC-400 v1) - per-CPU interface and distributor. */
#define Z7_GIC_CPUIF_BASE 0xF8F00100UL
#define Z7_GIC_DIST_BASE 0xF8F01000UL
/* PL310 L2 cache controller. UG585 ch.3. */
#define Z7_PL310_BASE 0xF8F02000UL
/* SCU + private timer/watchdog. UG585 ch.3. */
#define Z7_SCU_BASE 0xF8F00000UL
#define Z7_GTIMER_BASE 0xF8F00200UL
#define Z7_PTIMER_BASE 0xF8F00600UL
#endif /* _ZYNQ7000_H_ */

62
hal/zynq7000.ld 100644
View File

@ -0,0 +1,62 @@
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
/* wolfBoot is loaded by Xilinx FSBL into DDR at 0x04000000.
* Reserve 1 MB for code/data/bss/stack. */
MEMORY
{
DDR_MEM(rwx): ORIGIN = 0x04000000, LENGTH = 0x00100000
}
ENTRY(reset_vector_entry)
SECTIONS
{
.text : {
_start_text = .;
KEEP(*(start))
*(.text)
*(.text.*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
*(.glue_7)
. = ALIGN(4);
*(.eh_frame)
. = ALIGN(4);
_end_text = .;
} > DDR_MEM
. = ALIGN(4);
.dummy : {
_edummy = .;
} > DDR_MEM
.data : AT (LOADADDR(.dummy)) {
_start_data = .;
*(.vectors)
*(.data)
*(.data.*)
_end_data = .;
} > DDR_MEM
.bss (NOLOAD) : {
. = ALIGN(4);
_start_bss = .;
*(.bss)
*(.bss.*)
*(COMMON)
_end_bss = .;
_end = .;
} > DDR_MEM
}
kernel_addr = 0x00100000;
update_addr = 0x00700000;
dts_addr = 0x00000000;
_romsize = _end_data - _start_text;
_sramsize = _end_bss - _start_text;
END_STACK = _start_text;
_stack_top = ORIGIN(DDR_MEM) + LENGTH(DDR_MEM);
end = .;

View File

@ -61,26 +61,44 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset, const uint32_t* dts_offset)
void RAMFUNCTION do_boot(const uint32_t *app_offset)
#endif
{
/* Set application address via r4 */
asm volatile("mov r4, %0" : : "r"(app_offset));
/* Single asm block so the compiler cannot insert any code between
* the register set-up and the bx that would clobber r0..r4. Inputs
* are tied to the matching named operands; r0..r4 are listed in the
* clobber set so the compiler does not assume their values survive.
*
* When MMU=1 we always emit the ARM Linux boot ABI
* (r0=0, r1=~0, r2=DTB_phys, r3=0). Bare-metal payloads do not read
* r0..r3 so the same ABI works for them; Linux requires it. This
* removes the need for a separate LINUX_PAYLOAD switch per target.
*
* Without MMU there is no DTB to pass, so we fall back to a minimal
* handoff (all GPRs cleared) used by targets like sama5d3. */
#ifdef MMU
/* Move the dts pointer to r5 (as first argument) */
asm volatile("mov r5, %0" : : "r"(dts_offset));
register const uint32_t *dts_in = dts_offset;
asm volatile (
"mov r4, %[entry]\n"
"mov r2, %[dts]\n"
"mov r0, #0\n"
"mvn r1, #0\n"
"mov r3, #0\n"
"bx r4\n"
:
: [entry] "r" (app_offset), [dts] "r" (dts_in)
: "r0", "r1", "r2", "r3", "r4", "memory"
);
#else
asm volatile("mov r5, 0");
asm volatile (
"mov r4, %[entry]\n"
"mov r0, #0\n"
"mov r1, #0\n"
"mov r2, #0\n"
"mov r3, #0\n"
"bx r4\n"
:
: [entry] "r" (app_offset)
: "r0", "r1", "r2", "r3", "r4", "memory"
);
#endif
/* Zero registers r1, r2, r3 */
asm volatile("mov r3, 0");
asm volatile("mov r2, 0");
asm volatile("mov r1, 0");
/* Move the dts pointer to r0 (as first argument) */
asm volatile("mov r0, r5");
/* Unconditionally jump to app_entry at r4 */
asm volatile("bx r4");
}
#ifdef RAM_CODE

View File

@ -1,5 +1,25 @@
/**
* Arm32 (32bit Cortex-A) boot up
/* boot_arm32_start.S
*
* Generic ARMv7-A 32-bit (Cortex-A5/A7/A8/A9/A15/A17) startup for wolfBoot.
* Performs the minimum CPU setup that every standalone image needs before
* running C code:
*
* 1. mask IRQ + FIQ, force SVC mode
* 2. set VBAR to wolfBoot's vector table (so aborts route to us, not
* to whatever the bootloader/BootROM left vectors pointing at)
* 3. clear SCTLR V (high vectors), A (alignment fault), C (D-cache),
* I (I-cache) bits. Leave M (MMU) alone so we inherit a flat 1:1
* mapping from the bootloader if it set one up - disabling MMU on
* ARMv7-A would treat all memory as Strongly-Ordered and fault on
* unaligned accesses (e.g. C string ops on 2-byte aligned literals)
* 4. invalidate TLB, I-cache, branch predictor
* 5. set up per-mode stacks (SVC/IRQ/FIQ/ABT/UND) carved below _stack_top
* 6. copy .data, zero .bss, enable async aborts, jump to main
*
* Modeled after the Xilinx standalone BSP cortexa9/gcc/boot.S, generalized
* for any ARMv7-A target. Used by both SAMA5D3 (Cortex-A5) and Zynq-7000
* (Cortex-A9).
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
@ -18,81 +38,141 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
.section start
.text
/* startup entry point */
.globl reset_vector_entry
.align 4
.arm
.section start, "ax"
.globl reset_vector_entry
/* VBAR on ARMv7-A masks the low 5 bits (RES0), so the vector base
* must be 32-byte aligned or we end up pointing at the wrong place
* and exceptions silently miss the table. Use .balign 32 to make
* that requirement explicit regardless of the linker placement. */
.balign 32
reset_vector_entry:
/* Exception vectors (should be a branch to be detected as a valid code by the rom */
_exception_vectors:
b isr_reset /* reset */
b isr_empty /* Undefined Instruction */
b isr_swi /* Software Interrupt */
b isr_pabt /* Prefetch Abort */
b dabt_vector /* Data Abort */
.word _romsize /* Size of the binary for ROMCode loading */
b isr_irq /* IRQ : read the AIC */
b isr_fiq /* FIQ */
_vector_table:
b isr_reset /* 0x00 reset */
b isr_undef /* 0x04 undefined */
b isr_swi /* 0x08 swi */
b isr_pabt /* 0x0C prefetch abort */
b isr_dabt /* 0x10 data abort */
.word _romsize /* 0x14 (size word, kept for FSBL/BootROM parity) */
b isr_irq /* 0x18 IRQ */
b isr_fiq /* 0x1C FIQ */
isr_empty:
b isr_empty
isr_swi:
b isr_swi
isr_pabt:
b isr_pabt
dabt_vector:
subs pc, r14, #4 /* return */
nop
isr_rsvd:
b isr_rsvd
isr_irq:
b isr_irq
isr_fiq:
b isr_fiq
isr_undef: b isr_undef
isr_swi: b isr_swi
isr_pabt: b isr_pabt
isr_dabt: b isr_dabt
isr_irq: b isr_irq
isr_fiq: b isr_fiq
/* Reset handler procedure. Prepare the memory and call main() */
isr_reset:
/* Initialize the stack pointer */
ldr sp,=_stack_top
/* Save BootROM supplied boot source information to stack */
push {r4}
/* 1. Mask IRQ + FIQ, force SVC mode. */
cpsid if
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13 /* SVC mode */
msr cpsr_c, r0
/* Copy the data section */
ldr r2, =_lp_data
ldmia r2, {r1, r3, r4}
1:
cmp r3, r4
ldrcc r2, [r1], #4
strcc r2, [r3], #4
bcc 1b
/* 2. Set VBAR to our vector table (the load address). */
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0
/* Zero bss area */
adr r2, _lp_bss
ldmia r2, {r3, r4}
mov r2, #0
1:
cmp r3, r4
strcc r2, [r3], #4
bcc 1b
/* 3. Adjust SCTLR. Keep MMU bit (M) as the bootloader left it - if it
* set up flat 1:1 mapping (Xilinx FSBL on Zynq-7000 does), we want it
* on so unaligned LDR/STR from C code doesn't fault. Clear V (high
* vectors), A (alignment fault check), C (D-cache), I (I-cache). */
mrc p15, 0, r1, c1, c0, 0
bic r1, r1, #(1 << 13) /* V bit */
bic r1, r1, #(1 << 1) /* A bit */
bic r1, r1, #(1 << 2) /* C bit (D-cache) */
bic r1, r1, #(1 << 12) /* I bit (I-cache) */
mcr p15, 0, r1, c1, c0, 0
dsb
isb
/* Jump to main() */
ldr r4, = main
/* 4. Invalidate TLB, I-cache, branch predictor. Leave D-cache alone -
* if the bootloader has dirty lines we'd need clean+invalidate by
* set/way (CSSELR/CCSIDR walk), which is risky in startup. */
mov r0, #0
mcr p15, 0, r0, c8, c7, 0 /* TLBIALL */
mcr p15, 0, r0, c7, c5, 0 /* ICIALLU */
mcr p15, 0, r0, c7, c5, 6 /* BPIALL */
dsb
isb
/* 5. Set stack pointers for IRQ/FIQ/ABT/UND/SVC modes. Each gets a
* 1 KB slice carved below _stack_top:
* _stack_top <- top
* 0x000 SVC (sys/usr) - main wolfBoot stack (largest)
* 0x800 IRQ
* 0xC00 FIQ
* 0x1000 ABT
* 0x1400 UND
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r1, r0, #0x12 /* IRQ */
msr cpsr_c, r1
ldr sp, =(_stack_top - 0x800)
orr r1, r0, #0x11 /* FIQ */
msr cpsr_c, r1
ldr sp, =(_stack_top - 0xC00)
orr r1, r0, #0x17 /* ABT */
msr cpsr_c, r1
ldr sp, =(_stack_top - 0x1000)
orr r1, r0, #0x1b /* UND */
msr cpsr_c, r1
ldr sp, =(_stack_top - 0x1400)
orr r1, r0, #0x13 /* SVC (where main runs) */
msr cpsr_c, r1
ldr sp, =_stack_top
/* Save BootROM r4 (boot source info on some platforms; ignored by
* platforms that don't use it). */
push {r4}
/* 6. Copy .data section (LMA -> VMA). LMA == VMA in the standard
* wolfBoot linker scripts, so this is usually a no-op. */
ldr r2, =_lp_data
ldmia r2, {r1, r3, r4}
1: cmp r3, r4
ldrcc r2, [r1], #4
strcc r2, [r3], #4
bcc 1b
/* Zero .bss */
adr r2, _lp_bss
ldmia r2, {r3, r4}
mov r2, #0
1: cmp r3, r4
strcc r2, [r3], #4
bcc 1b
/* Enable async-abort delivery (clear A bit in CPSR) so we get an
* exception now if the bus throws one, rather than later when state
* is harder to recover. */
mrs r0, cpsr
bic r0, r0, #(1 << 8)
msr cpsr_xsf, r0
/* Jump to main(). */
ldr r4, =main
mov lr, pc
bx r4
/* main() should never return */
_panic:
b _panic
.align
.align
_lp_data:
.word _start_data
.word _end_data
.word _start_data
.word _end_data
_lp_bss:
.word _start_bss
.word _end_bss
.word _start_bss
.word _end_bss

View File

@ -31,6 +31,10 @@
#include "wolfboot/wolfboot.h"
#include <string.h>
#ifdef WOLFBOOT_UBOOT_LEGACY
#include "gpt.h" /* gpt_crc32_* helpers (reflected CRC-32, poly 0xEDB88320) */
#endif
#ifdef WOLFBOOT_TPM
#include "tpm.h"
#endif
@ -135,6 +139,81 @@ int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst)
}
#endif /* WOLFBOOT_USE_RAMBOOT */
#ifdef WOLFBOOT_UBOOT_LEGACY
/* Validate a 64-byte U-Boot legacy image header (image_header_t).
*
* Layout (all multi-byte fields stored big-endian on flash):
* 0x00 4 ih_magic 0x27051956
* 0x04 4 ih_hcrc CRC32 of header with hcrc treated as 0
* 0x08 4 ih_time timestamp
* 0x0C 4 ih_size payload size (excl. header)
* 0x10 4 ih_load load address
* 0x14 4 ih_ep entry point
* 0x18 4 ih_dcrc data CRC32 (validated by wolfBoot signature)
* 0x1C 1 ih_os
* 0x1D 1 ih_arch
* 0x1E 1 ih_type
* 0x1F 1 ih_comp
* 0x20 32 ih_name
*
* Magic alone is a ~1-in-2^32 collision for random data, so we also
* validate the header CRC32 (~2^-32) and the payload size, dropping the
* joint false-positive probability to roughly 2^-64. This matches
* U-Boot's own mkimage/bootm validation. */
static int uboot_legacy_header_valid(const uint8_t *hdr, uint32_t total)
{
struct gpt_crc32_ctx ctx;
uint8_t scratch[UBOOT_IMG_HDR_SZ];
uint32_t magic;
uint32_t hcrc;
uint32_t size;
uint32_t crc;
if (hdr == NULL)
return 0;
if (total < UBOOT_IMG_HDR_SZ)
return 0;
/* ih_magic is stored big-endian on flash; UBOOT_IMG_HDR_MAGIC is the
* host-order word that compares equal to that BE encoding on a
* little-endian host (which is the only ARM/x86 host wolfBoot
* supports for the targets enabling WOLFBOOT_UBOOT_LEGACY). */
memcpy(&magic, hdr + 0x00, sizeof(magic));
if (magic != UBOOT_IMG_HDR_MAGIC)
return 0;
/* ih_size: big-endian payload length. Reject zero and anything that
* would overrun the signed image. */
memcpy(&size, hdr + 0x0C, sizeof(size));
size = ((size & 0xFF000000U) >> 24) |
((size & 0x00FF0000U) >> 8) |
((size & 0x0000FF00U) << 8) |
((size & 0x000000FFU) << 24);
if (size == 0)
return 0;
if (size > (total - UBOOT_IMG_HDR_SZ))
return 0;
/* ih_hcrc: CRC32 of the header with the hcrc field treated as zero. */
memcpy(scratch, hdr, UBOOT_IMG_HDR_SZ);
memcpy(&hcrc, scratch + 0x04, sizeof(hcrc));
memset(scratch + 0x04, 0, sizeof(hcrc));
gpt_crc32_init(&ctx);
gpt_crc32_update(&ctx, scratch, UBOOT_IMG_HDR_SZ);
crc = gpt_crc32_final(&ctx);
/* hcrc is stored big-endian; byte-swap for comparison against the
* host-order CRC32 returned by gpt_crc32_final. */
hcrc = ((hcrc & 0xFF000000U) >> 24) |
((hcrc & 0x00FF0000U) >> 8) |
((hcrc & 0x0000FF00U) << 8) |
((hcrc & 0x000000FFU) << 24);
if (hcrc != crc)
return 0;
return 1;
}
#endif /* WOLFBOOT_UBOOT_LEGACY */
void RAMFUNCTION wolfBoot_start(void)
{
int active = -1, ret = 0;
@ -316,17 +395,17 @@ backup_on_failure:
#endif
#ifdef WOLFBOOT_UBOOT_LEGACY
/* Check for U-Boot Legacy format image header */
/* Check for U-Boot legacy format image header. Validate magic +
* header CRC32 + payload size before stripping the 64-byte header,
* so a non-uImage payload whose first 4 bytes happen to collide
* with UBOOT_IMG_HDR_MAGIC (~1 in 2^32) cannot be misinterpreted. */
image_ptr = wolfBoot_peek_image(&os_image, 0, NULL);
if (image_ptr) {
if (*((uint32_t*)image_ptr) == UBOOT_IMG_HDR_MAGIC) {
/* Note: Could parse header and get load address at 0x10 */
/* Skip 64 bytes (size of Legacy format image header) */
load_address += UBOOT_IMG_HDR_SZ;
os_image.fw_base += UBOOT_IMG_HDR_SZ;
os_image.fw_size -= UBOOT_IMG_HDR_SZ;
}
if (image_ptr != NULL &&
uboot_legacy_header_valid(image_ptr, os_image.fw_size)) {
/* Skip 64 bytes (size of legacy format image header). */
load_address += UBOOT_IMG_HDR_SZ;
os_image.fw_base += UBOOT_IMG_HDR_SZ;
os_image.fw_size -= UBOOT_IMG_HDR_SZ;
}
#endif

View File

@ -0,0 +1,64 @@
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
/* App is staged by wolfBoot to DDR at WOLFBOOT_LOAD_ADDRESS=0x10000000.
* Stack carved out from the half-MB region just above. */
MEMORY
{
DDR_MEM(rwx) : ORIGIN = 0x10000000, LENGTH = 0x00080000 /* 512 KB code/data/bss */
STACK_MEM(rw) : ORIGIN = 0x10080000, LENGTH = 0x00080000 /* 512 KB stack */
}
/* The test app has no separate startup file - wolfBoot's do_boot bx's
* directly to WOLFBOOT_LOAD_ADDRESS, which is the very first byte of the
* raw .bin image. To guarantee that byte is the first instruction of
* main() (regardless of toolchain ordering or LTO), we KEEP main's
* function section at the start of .text. The ENTRY directive only
* affects ELF metadata; the .bin payload starts wherever the linker
* places .text first. */
ENTRY(main)
SECTIONS
{
.text : AT (ORIGIN(DDR_MEM)) {
_start_text = .;
/* main() is annotated with __attribute__((section(".boot_entry")))
* in app_zynq7000.c so it lands here, at offset 0 of the raw .bin
* payload (= WOLFBOOT_LOAD_ADDRESS). KEEP() prevents --gc-sections
* from dropping it. */
KEEP(*(.boot_entry))
*(.text)
*(.text.*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
*(.glue_7)
. = ALIGN(4);
*(.eh_frame)
. = ALIGN(4);
_end_text = .;
}
. = ALIGN(4);
.dummy : {
_edummy = .;
}
.data : AT (LOADADDR(.dummy)) {
_start_data = .;
*(.vectors)
*(.data)
_end_data = .;
}
.bss (NOLOAD) : {
. = ALIGN(4);
_start_bss = .;
*(.bss)
_end_bss = .;
}
}
_romsize = _end_data - _start_text;
_sramsize = _end_bss - _start_text;
END_STACK = _start_text;
_stack_top = ORIGIN(STACK_MEM) + LENGTH(STACK_MEM);
end = .;

View File

@ -413,6 +413,11 @@ ifeq ($(TARGET),sama5d3)
LSCRIPT_TEMPLATE:=$(ARCH)-$(TARGET).ld
endif
ifeq ($(TARGET),zynq7000)
APP_OBJS+=./boot_arm32_start.o
LSCRIPT_TEMPLATE:=$(ARCH)-$(TARGET).ld
endif
ifeq ($(TARGET),stm32l4)
APP_OBJS+=$(STM32CUBE)/Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash.o
APP_OBJS+=$(STM32CUBE)/Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ex.o

View File

@ -0,0 +1,74 @@
/* app_zynq7000.c
*
* Bare-metal Cortex-A9 test app for the Zynq-7000 ZC702. Prints a banner
* on UART1 and a heartbeat character so the user can see do_boot() landed.
*
* Copyright (C) 2026 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 3 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-1335, USA
*/
#include <stdint.h>
#ifdef TARGET_zynq7000
#define UART1_FIFO (*(volatile uint32_t*)0xE0001030U)
#define UART1_SR (*(volatile uint32_t*)0xE000102CU)
#define UART_SR_TXFULL 0x10U
#define UART_SR_TXEMPTY 0x08U
static void uart_putc(char c)
{
while (UART1_SR & UART_SR_TXFULL)
;
UART1_FIFO = (uint32_t)(uint8_t)c;
}
static void uart_puts(const char *s)
{
while (*s) {
if (*s == '\n')
uart_putc('\r');
uart_putc(*s++);
}
}
static void delay(volatile uint32_t n)
{
while (n--) {
__asm__ volatile("nop");
}
}
/* Freestanding entry point - wolfBoot's do_boot bx's directly to
* WOLFBOOT_LOAD_ADDRESS, which is the first byte of the raw .bin payload.
* Pin main to a known input section (.boot_entry) and KEEP that section
* first in the linker script (test-app/ARM-zynq7000.ld) so this function
* is at offset 0 of the .bin regardless of compiler/LTO function
* ordering. noreturn lets the compiler skip emitting a return path. */
__attribute__((section(".boot_entry"), noreturn))
int main(void)
{
uart_puts("\n=== ZC702 test-app: BOOT OK ===\n");
uart_puts("wolfBoot verified + chain-loaded this image\n");
while (1) {
uart_putc('.');
delay(2000000);
}
}
#endif /* TARGET_zynq7000 */

View File

@ -0,0 +1,83 @@
# jtag_load.tcl - load wolfboot.elf onto a ZC702 via Xilinx Platform Cable II.
#
# Uses the prebuilt Zynq-7000 FSBL (zynq_fsbl.elf) to bring DDR / MIO /
# clocks / UART up, then loads wolfboot.elf over the top and starts it.
#
# Usage:
# source /opt/Xilinx/2025.2/Vitis/settings64.sh
# xsdb tools/scripts/zynq7000/jtag_load.tcl
#
# Set the JTAG boot mode straps on the ZC702 (SW16 = all OFF) before use.
# After this script runs the board may need a power-cycle to recover the
# CPU into a JTAG-loadable state again.
#
# Override paths via env:
# FSBL_ELF=... FSBL ELF path
# WOLFBOOT_ELF=... wolfboot ELF path
set fsbl_default "$::env(HOME)/GitHub/soc-prebuilt-firmware/zc702-zynq/zynq_fsbl.elf"
set wolfboot_default "[file dirname [info script]]/../../../wolfboot.elf"
if {[info exists ::env(FSBL_ELF)]} { set fsbl_elf $::env(FSBL_ELF) } \
else { set fsbl_elf $fsbl_default }
if {[info exists ::env(WOLFBOOT_ELF)]} { set wolfboot_elf $::env(WOLFBOOT_ELF) } \
else { set wolfboot_elf $wolfboot_default }
if {![file exists $fsbl_elf]} {
puts "ERROR: FSBL not found at $fsbl_elf"
puts "Clone wolfSSL/soc-prebuilt-firmware next to wolfboot or set FSBL_ELF."
exit 1
}
if {![file exists $wolfboot_elf]} {
puts "ERROR: wolfboot.elf not found at $wolfboot_elf"
exit 1
}
connect
# Sometimes the chain comes up empty if the previous run left the CPU in
# an off-chain state (e.g. WFI with clock gated). Retry the target lookup.
# We treat ANY catch failure (return code != 0) as a retry condition,
# whether it's "no targets", a JTAG server hiccup, or a connection error -
# the next iteration will re-attempt the targets command after a delay.
set selected 0
for {set i 0} {$i < 5} {incr i} {
set rc [catch {targets -set -filter {name =~ "ARM Cortex-A9 MPCore #0"}} err]
if {$rc == 0} {
set selected 1
break
}
puts "Cortex-A9 select failed (try $i): $err"
after 500
}
if {!$selected} {
puts "ERROR: could not select Cortex-A9 target after 5 retries."
puts "Power-cycle the ZC702 (SW10) and try again."
exit 1
}
# Full PS reset, then wait for BootROM to enter JTAG-mode poll loop.
rst -system
after 1500
targets -set -filter {name =~ "ARM Cortex-A9 MPCore #0"}
# Run FSBL to completion. It does ps7_init (DDR/MIO/clocks/UART), then
# parks itself since no bundled second-stage exists. 2-3s is plenty.
puts "Loading FSBL: $fsbl_elf"
dow $fsbl_elf
con
after 3000
# Stop where FSBL parked, but do NOT rst -processor here - that would drop
# us back into BootROM and lose FSBL's PS state.
stop
# Load wolfBoot at its DDR address. xsdb's `dow` does NOT consistently set
# PC after a second target dow, so set PC and CPSR explicitly.
puts "Loading wolfBoot: $wolfboot_elf"
dow $wolfboot_elf
rwr pc 0x04000000
rwr cpsr 0xD3 ;# SVC mode, IRQ+FIQ masked
puts "Resuming - watch UART1 (115200 8N1) for the wolfBoot banner."
con

View File

@ -0,0 +1,135 @@
#!/bin/bash
# Sign and stage a Zynq-7000 Linux kernel for wolfBoot (verified on ZC702).
#
# Two modes (chosen by the APPENDED env var):
#
# APPENDED=1 (default, recommended) - appends the DTB to the zImage and
# signs the concatenation as one image. The kernel finds the DTB at the
# end of itself via CONFIG_ARM_APPENDED_DTB. Required because the ARMv7
# zImage decompressor is observed to lose r2 (the DTB physical pointer
# wolfBoot passed in) before it reaches the decompressed kernel head.S
# on Zynq-7000 - the kernel ends up with __atags_pointer = 0 and never
# parses chosen.bootargs / chosen.stdout-path. Appending the DTB is
# independent of r2.
#
# APPENDED=0 - signs the zImage alone and stages the DTB raw at
# WOLFBOOT_DTS_BOOT_ADDRESS. wolfBoot reads it via PART_DTS_BOOT and
# relocates to WOLFBOOT_LOAD_DTS_ADDRESS, then passes that pointer in
# r2 per the ARM Linux boot ABI. Useful for kernels/decompressors
# that do preserve r2 correctly.
#
# Inputs (env):
# ZIMAGE - path to ARM zImage (default: ../linux-xlnx/arch/arm/boot/zImage)
# DTB - path to .dtb (default: ../linux-xlnx/arch/arm/boot/dts/zynq-zc702.dtb)
# VERSION - image version (default: 1)
# APPENDED - 0 or 1 (default: 1)
#
# Kernel must be built with:
# APPENDED=1 -> CONFIG_ARM_APPENDED_DTB=y, CONFIG_ARM_ATAG_DTB_COMPAT=y
# APPENDED=0 -> bootargs / stdout-path baked into the DTB ahead of time
set -e
# Parse .config (Makefile syntax: NAME ?= value or NAME = value) for the
# small set of variables we actually need. Whitelist parsing rather than
# eval'ing the file content - avoids shell-injection risk if the .config
# was copied from somewhere untrusted, and surfaces typos cleanly.
config_get() {
local key="$1"
awk -v k="$key" '
$0 ~ "^"k"[[:space:]]*\\??=" {
sub(/^[^=]+=/, "")
sub(/^[[:space:]]+/, "")
sub(/[[:space:]]+$/, "")
print
exit
}
' .config 2>/dev/null
}
WOLFBOOT_PARTITION_BOOT_ADDRESS=$(config_get WOLFBOOT_PARTITION_BOOT_ADDRESS)
WOLFBOOT_DTS_BOOT_ADDRESS=$(config_get WOLFBOOT_DTS_BOOT_ADDRESS)
WOLFBOOT_PARTITION_SIZE=$(config_get WOLFBOOT_PARTITION_SIZE)
# The sign tool reads IMAGE_HEADER_SIZE from the environment (sign.c line
# 2824). Without this export, sign defaults to 256 bytes -- which leaves
# wolfBoot reading the wrong fw_base offset on flash if the running config
# uses a larger header.
IMAGE_HEADER_SIZE=$(config_get IMAGE_HEADER_SIZE)
[ -n "$IMAGE_HEADER_SIZE" ] && export IMAGE_HEADER_SIZE
SIGN_TOOL="./tools/keytools/sign"
KEY="wolfboot_signing_private_key.der"
ZIMAGE="${ZIMAGE:-../linux-xlnx/arch/arm/boot/zImage}"
DTB="${DTB:-../linux-xlnx/arch/arm/boot/dts/zynq-zc702.dtb}"
VERSION="${VERSION:-1}"
APPENDED="${APPENDED:-1}"
[ -f "$ZIMAGE" ] || { echo "ERROR: kernel not found at $ZIMAGE" >&2; exit 1; }
[ -f "$DTB" ] || { echo "ERROR: dtb not found at $DTB" >&2; exit 1; }
[ -f "$KEY" ] || { echo "ERROR: signing key $KEY not found" >&2; exit 1; }
PSIZE=$((${WOLFBOOT_PARTITION_SIZE:-0x600000}))
if [ "$APPENDED" = "1" ]; then
# Concatenate zImage + DTB, sign as single image.
KDTB=$(mktemp /tmp/zImage_dtb.XXXXXX)
# Single-quoted trap so $KDTB is expanded at trap-fire time, with the
# path quoted internally - safe even if mktemp returns a path with
# whitespace/metacharacters.
trap 'rm -f "$KDTB"' EXIT
cat "$ZIMAGE" "$DTB" > "$KDTB"
SIZE=$(stat -c %s "$KDTB")
if [ "$SIZE" -gt "$PSIZE" ]; then
echo "ERROR: zImage+dtb ($SIZE bytes) exceeds WOLFBOOT_PARTITION_SIZE ($PSIZE)" >&2
exit 1
fi
echo "Mode : APPENDED (zImage + DTB concatenated, signed as one image)"
echo "zImage: $ZIMAGE"
echo "DTB : $DTB"
echo "Total : $SIZE bytes"
echo "Signing as PART_BOOT v$VERSION ..."
$SIGN_TOOL --ecc256 --sha256 "$KDTB" "$KEY" "$VERSION"
SIGNED_OUT="${KDTB%.*}_v${VERSION}_signed.bin"
[ -f "$SIGNED_OUT" ] || SIGNED_OUT="${KDTB}_v${VERSION}_signed.bin"
mv "$SIGNED_OUT" "image_v${VERSION}_signed.bin"
echo ""
echo "Outputs:"
ls -la "image_v${VERSION}_signed.bin"
echo ""
echo "Flash with (replace <FSBL> and <ID>):"
echo " program_flash -f image_v${VERSION}_signed.bin -offset ${WOLFBOOT_PARTITION_BOOT_ADDRESS} -flash_type qspi-x4-single -fsbl <FSBL> -target_id <ID>"
echo ""
echo "(No separate DTB programming needed - DTB is appended to zImage.)"
else
# Sign zImage alone, copy DTB raw.
KSIZE=$(stat -c %s "$ZIMAGE")
DSIZE=$(stat -c %s "$DTB")
if [ "$KSIZE" -gt "$PSIZE" ]; then
echo "ERROR: zImage ($KSIZE bytes) exceeds WOLFBOOT_PARTITION_SIZE ($PSIZE)" >&2
exit 1
fi
echo "Mode : RAW DTB (zImage signed alone, DTB staged separately at PART_DTS_BOOT)"
echo "zImage: $ZIMAGE ($KSIZE bytes)"
echo "DTB : $DTB ($DSIZE bytes)"
echo "Signing kernel as PART_BOOT v$VERSION ..."
$SIGN_TOOL --ecc256 --sha256 "$ZIMAGE" "$KEY" "$VERSION"
SIGNED_OUT="${ZIMAGE%.*}_v${VERSION}_signed.bin"
[ -f "$SIGNED_OUT" ] || SIGNED_OUT="${ZIMAGE}_v${VERSION}_signed.bin"
mv "$SIGNED_OUT" "image_v${VERSION}_signed.bin"
cp "$DTB" dtb.bin
echo ""
echo "Outputs:"
ls -la "image_v${VERSION}_signed.bin" dtb.bin
echo ""
echo "Flash with (replace <FSBL> and <ID>):"
echo " program_flash -f image_v${VERSION}_signed.bin -offset ${WOLFBOOT_PARTITION_BOOT_ADDRESS} -flash_type qspi-x4-single -fsbl <FSBL> -target_id <ID>"
echo " program_flash -f dtb.bin -offset ${WOLFBOOT_DTS_BOOT_ADDRESS} -flash_type qspi-x4-single -fsbl <FSBL> -target_id <ID>"
fi

View File

@ -0,0 +1,152 @@
#!/bin/bash
# Lay out a Zynq-7000 wolfBoot SD card (verified on ZC702 / Arasan v2.0).
#
# Pure MBR layout (no GPT). The Zynq-7000 BootROM (UG821 ch.6.3) requires
# an MBR with the first partition as FAT32, type 0x0C (FAT32-LBA), with the
# Active flag (0x80) set, and BOOT.BIN as a regular file in that FAT32
# root. wolfBoot's disk.c reads MBR partitions when no protective GPT entry
# is found (src/disk.c:disk_open_mbr).
#
# Layout:
# MBR p1 64 MB FAT32-LBA (0x0C) Active - holds BOOT.BIN for BootROM
# (>= 33 MB so mkfs.vfat creates a
# standard-cluster FAT32 the
# BootROM accepts)
# MBR p2 16 MB Linux raw (0x83) - signed boot image (BOOT_PART_A=1)
# MBR p3 16 MB Linux raw (0x83) - signed update image (BOOT_PART_B=2)
#
# wolfBoot indexes MBR partitions starting at 0, so partition p1=idx0,
# p2=idx1, p3=idx2 - matching BOOT_PART_A=1 and BOOT_PART_B=2 in the config.
#
# Usage:
# sudo ./tools/scripts/zynq7000/prepare_sdcard.sh /dev/sdX [signed_image]
#
# Bare-metal payload (default):
# sudo ./tools/scripts/zynq7000/prepare_sdcard.sh /dev/sdX
# -> writes test-app/image_v1_signed.bin to p2 and p3.
#
# Linux payload (signed appended-DTB zImage produced by prepare_linux.sh):
# sudo ./tools/scripts/zynq7000/prepare_sdcard.sh /dev/sdX \
# test-app/zImage_signed.bin
# -> writes the signed kernel image to p2 (BOOT_A) and p3 (BOOT_B).
# The MBR partition layout is identical for both -- the choice of payload
# is signing-side, not partitioning-side.
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
DEV="$1"
SIGNED="${2:-test-app/image_v1_signed.bin}"
BOOTBIN="${BOOT_BIN:-./BOOT.BIN}"
[ -n "$DEV" ] || { echo -e "${RED}usage:${NC} sudo $0 <device> [signed_image]" >&2; exit 1; }
[ "$EUID" = 0 ] || { echo -e "${RED}must run as root${NC}" >&2; exit 1; }
case "$DEV" in
/dev/sda|/dev/nvme*)
echo -e "${RED}refusing $DEV (looks like a system disk)${NC}" >&2
exit 1 ;;
/dev/mmcblk0|/dev/mmcblk0*)
# On many embedded Linux hosts mmcblk0 IS the OS disk. Refuse by
# default; allow override only if the user explicitly opts in.
if [ "${ALLOW_MMCBLK0:-0}" != "1" ]; then
echo -e "${RED}refusing $DEV (mmcblk0 is the primary OS disk on" >&2
echo -e "many systems). Re-run with ALLOW_MMCBLK0=1 if this is" >&2
echo -e "really the SD card you want to wipe.${NC}" >&2
exit 1
fi ;;
/dev/sd[b-z]|/dev/mmcblk[1-9]) ;;
*) echo -e "${RED}unsupported device: $DEV${NC}" >&2; exit 1 ;;
esac
[ -b "$DEV" ] || { echo -e "${RED}$DEV not a block device${NC}" >&2; exit 1; }
# Belt-and-suspenders: refuse non-removable devices unless caller opts in.
# Some USB SD readers expose RM=0; in that case the user can set
# ALLOW_NON_REMOVABLE=1 after eyeballing lsblk.
if [ "${ALLOW_NON_REMOVABLE:-0}" != "1" ]; then
rm_flag=$(lsblk -ndo RM "$DEV" 2>/dev/null | tr -d '[:space:]' || echo "")
if [ -n "$rm_flag" ] && [ "$rm_flag" != "1" ]; then
echo -e "${RED}refusing $DEV (RM=$rm_flag - not flagged as removable)." >&2
echo -e "Re-run with ALLOW_NON_REMOVABLE=1 if this really is the" >&2
echo -e "SD card you want to wipe.${NC}" >&2
exit 1
fi
fi
mount | grep -q "^${DEV}" && { echo -e "${RED}unmount $DEV partitions first${NC}" >&2; mount | grep "^${DEV}" >&2; exit 1; }
[ -f "$BOOTBIN" ] || { echo -e "${RED}BOOT.BIN not found at $BOOTBIN${NC}" >&2; exit 1; }
[ -f "$SIGNED" ] || { echo -e "${RED}signed image not found at $SIGNED${NC}" >&2; exit 1; }
case "$DEV" in
/dev/mmcblk*) P1="${DEV}p1"; P2="${DEV}p2"; P3="${DEV}p3" ;;
*) P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3" ;;
esac
echo -e "${YELLOW}Target:${NC}"
lsblk -o NAME,SIZE,MODEL,VENDOR,TRAN "$DEV" 2>/dev/null | head -5
echo -e "${YELLOW}BOOT.BIN:${NC} $BOOTBIN ($(stat -c %s "$BOOTBIN") bytes)"
echo -e "${YELLOW}signed:${NC} $SIGNED ($(stat -c %s "$SIGNED") bytes)"
echo
read -p "Type 'yes' to wipe $DEV: " CONFIRM
[ "$CONFIRM" = yes ] || { echo "Aborted."; exit 1; }
echo -e "${GREEN}1.${NC} Wiping head + tail to remove any existing GPT/MBR..."
wipefs --all --force "$DEV" >/dev/null 2>&1 || true
dd if=/dev/zero of="$DEV" bs=1M count=8 conv=fsync status=none
# Tail wipe (kills stale backup-GPT) - only if the device is large enough
# that seek = (size - 2048) is positive. Refuse to seek into a tiny or
# unreadable device.
DEV_SECTORS=$(blockdev --getsz "$DEV" 2>/dev/null || echo 0)
if [ "$DEV_SECTORS" -gt 4096 ]; then
dd if=/dev/zero of="$DEV" bs=512 \
seek=$((DEV_SECTORS - 2048)) count=2048 \
conv=fsync status=none 2>/dev/null || true
else
echo -e "${YELLOW} (skipping tail wipe: device only $DEV_SECTORS sectors)${NC}"
fi
sync
echo -e "${GREEN}2.${NC} Writing pure MBR (parted msdos label) with 3 primary partitions..."
parted "$DEV" --script -- \
mklabel msdos \
mkpart primary fat32 1MiB 65MiB \
mkpart primary 65MiB 81MiB \
mkpart primary 81MiB 97MiB \
set 1 boot on
sync; partprobe "$DEV"; sleep 1
echo -e "${GREEN}3.${NC} Patching MBR types: p1=0x0C (FAT32-LBA), p2/p3=0x83 (Linux)..."
# parted leaves p1 as 0x0C already when fat32 is requested; force in case.
# Each MBR partition entry is 16 bytes starting at 0x1BE.
# p1 entry: offset 0x1BE, type byte at 0x1BE+4 = 0x1C2
# p2 entry: offset 0x1CE, type byte at 0x1CE+4 = 0x1D2
# p3 entry: offset 0x1DE, type byte at 0x1DE+4 = 0x1E2
printf '\x0C' | dd of="$DEV" bs=1 seek=$((0x1C2)) count=1 conv=notrunc status=none
printf '\x83' | dd of="$DEV" bs=1 seek=$((0x1D2)) count=1 conv=notrunc status=none
printf '\x83' | dd of="$DEV" bs=1 seek=$((0x1E2)) count=1 conv=notrunc status=none
# Active flag on p1 (parted's `set 1 boot on` should have done this)
printf '\x80' | dd of="$DEV" bs=1 seek=$((0x1BE)) count=1 conv=notrunc status=none
sync; partprobe "$DEV"; sleep 1
echo -e "${GREEN}4.${NC} Formatting $P1 as FAT32 (label BOOT)..."
mkfs.vfat -F 32 -n BOOT "$P1" >/dev/null
echo -e "${GREEN}5.${NC} Copying BOOT.BIN to $P1..."
MNT=$(mktemp -d)
mount "$P1" "$MNT"
cp "$BOOTBIN" "$MNT/BOOT.BIN"
sync
umount "$MNT"
rmdir "$MNT"
echo -e "${GREEN}6.${NC} Writing signed image to $P2 (BOOT_A) and $P3 (BOOT_B)..."
dd if="$SIGNED" of="$P2" bs=512 conv=fsync status=none
dd if="$SIGNED" of="$P3" bs=512 conv=fsync status=none
sync
echo
echo -e "${GREEN}MBR partition entries (offset 0x1BE):${NC}"
dd if="$DEV" bs=1 skip=$((0x1BE)) count=64 status=none 2>/dev/null | xxd | head -4
echo
echo -e "${GREEN}Done.${NC} Insert into J64, set SW16-3 + SW16-4 ON for SD boot,"
echo -e "and power-cycle. Console on UART1 @ 115200."

View File

@ -0,0 +1,22 @@
// bootgen image descriptor for ZC702 QSPI boot.
//
// Pairs the prebuilt Zynq-7000 FSBL from
// ${PREBUILT_DIR}/zynq_fsbl.elf (default ../soc-prebuilt-firmware/zc702-zynq)
// with wolfboot.elf produced by `make TARGET=zynq7000`.
//
// Usage:
// PREBUILT_DIR=$HOME/GitHub/soc-prebuilt-firmware/zc702-zynq \
// cp ${PREBUILT_DIR}/zynq_fsbl.elf .
// bootgen -arch zynq -image tools/scripts/zynq7000/zynq7000_qspi.bif -w -o BOOT.BIN
//
// Then program BOOT.BIN to QSPI offset 0 with `program_flash` (Vitis) or
// Vivado Hardware Manager (program in JTAG strap mode: SW16 all OFF). Then set
// the QSPI boot strap by turning SW16-4 ON (MIO[5], MSB of the boot device
// field; per UG850 ch.1.2.4) and cold power-cycle so the BootROM re-samples
// the strap.
the_ROM_image:
{
[bootloader] zynq_fsbl.elf
wolfboot.elf
}