Skip to content

Commit bf87e1f

Browse files
committed
zynq7000: add Xilinx Zynq-7000 (ZC702) port
1 parent 8c7b864 commit bf87e1f

14 files changed

Lines changed: 1754 additions & 4 deletions

File tree

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ ifeq ($(TARGET),sama5d3)
285285
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
286286
endif
287287

288+
ifeq ($(TARGET),zynq7000)
289+
MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin
290+
endif
291+
288292
ifeq ($(TARGET),rp2350)
289293
MAIN_TARGET:=include/target.h keytools wolfboot_signing_private_key.der pico-sdk-info
290294
endif

arch.mk

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,19 @@ ifeq ($(ARCH),ARM)
303303
CFLAGS+=-DWOLFBOOT_USE_STDLIBC
304304
endif
305305

306+
ifeq ($(TARGET),zynq7000)
307+
# AMD/Xilinx Zynq-7000 (Cortex-A9, ARMv7-A) - ZC702 Evaluation Kit.
308+
# Loaded by Xilinx FSBL into DDR; see hal/zynq7000.{c,h,ld}.
309+
CORTEX_A9=1
310+
UPDATE_OBJS:=src/update_ram.o
311+
CFLAGS+=-DWOLFBOOT_DUALBOOT -fno-builtin -ffreestanding
312+
# Do NOT define WOLFBOOT_USE_STDLIBC: newlib's memcpy uses unaligned
313+
# LDRs which fault on Cortex-A9 when MMU is off (FSBL leaves MMU off
314+
# on Zynq-7000). Use wolfBoot's own aligned-safe memcpy from src/string.c.
315+
# U-Boot legacy header detection for Linux/U-Boot payloads (Milestone 5)
316+
CFLAGS+=-DWOLFBOOT_UBOOT_LEGACY
317+
endif
318+
306319
ifeq ($(TARGET),va416x0)
307320
CFLAGS+=-I$(WOLFBOOT_ROOT)/hal/vorago/ \
308321
-I$(VORAGO_SDK_DIR)/common/drivers/hdr/ \
@@ -344,6 +357,32 @@ ifeq ($(CORTEX_A5),1)
344357
-DWOLFSSL_ARM_ARCH=7 -DWOLFSSL_ARMASM_INLINE -DWOLFSSL_ARMASM_NO_NEON
345358
endif
346359
endif
360+
else
361+
ifeq ($(CORTEX_A9),1)
362+
# Cortex-A9 (ARMv7-A, 32-bit) - Zynq-7000.
363+
# Build in ARM state (-marm); reset vector lands in ARM mode after FSBL.
364+
# Note: do not filter out -mthumb from CFLAGS/LDFLAGS - that converts the
365+
# variables to simple-expansion flavor and breaks lazy $(LSCRIPT) expansion
366+
# in test-app/Makefile. -marm appended later wins over -mthumb anyway.
367+
FPU=-mfpu=vfp3-d16
368+
CFLAGS+=-mcpu=cortex-a9 -mtune=cortex-a9 -marm -static -z noexecstack \
369+
-mno-unaligned-access
370+
LDFLAGS+=-mcpu=cortex-a9 -mtune=cortex-a9 -marm -static -z noexecstack
371+
# Cortex-A9 uses boot_arm32.o (shared do_boot) + a Zynq-7000-specific
372+
# startup (VBAR, MMU/cache disable, all-mode stacks). The shared
373+
# boot_arm32_start.S used by SAMA5D3/Cortex-A5 lacks those.
374+
OBJS+=src/boot_arm32.o src/boot_zynq7000_start.o
375+
ifeq ($(NO_ASM),1)
376+
MATH_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_c32.o
377+
else
378+
MATH_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_arm32.o
379+
ifneq ($(NO_ARM_ASM),1)
380+
OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/arm/armv8-32-sha256-asm.o
381+
OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/arm/armv8-32-sha256-asm_c.o
382+
CFLAGS+=-DWOLFSSL_SP_ARM32_ASM -DWOLFSSL_ARMASM -DWOLFSSL_ARMASM_NO_HW_CRYPTO \
383+
-DWOLFSSL_ARM_ARCH=7 -DWOLFSSL_ARMASM_INLINE -DWOLFSSL_ARMASM_NO_NEON
384+
endif
385+
endif
347386
else
348387
# All others use boot_arm.o
349388
OBJS+=src/boot_arm.o
@@ -456,6 +495,7 @@ else
456495
endif
457496
endif
458497
endif
498+
endif
459499

460500

461501
## Renesas RX

config/examples/zynq7000.config

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
ARCH?=ARM
2+
TARGET?=zynq7000
3+
SIGN?=ECC256
4+
HASH?=SHA256
5+
6+
# Cortex-A9 (Zynq-7000) - selected automatically via TARGET=zynq7000 in arch.mk
7+
DEBUG?=0
8+
DEBUG_UART?=1
9+
V?=0
10+
SPMATH?=1
11+
12+
# wolfBoot itself is loaded by Xilinx FSBL to DDR at 0x04000000 (hal/zynq7000.ld).
13+
# WOLFBOOT_LOAD_ADDRESS is the *app* staging address: where wolfBoot copies
14+
# the verified signed image before do_boot. Must NOT overlap wolfBoot itself
15+
# AND src/update_ram.c expects dst > wolfBoot's _end - so place it above the
16+
# wolfBoot region (0x04000000-0x040FFFFF) at 16 MB.
17+
WOLFBOOT_LOAD_ADDRESS=0x10000000
18+
19+
# QSPI flash (16 MB N25Q128A on ZC702) via XQspiPs (hal/zynq7000.c).
20+
# Override EXT_FLASH=0 on the make command line for JTAG-only dev builds.
21+
EXT_FLASH?=1
22+
NO_XIP=1
23+
24+
# QSPI partition layout (16 MB total):
25+
# 0x000000 - 0x0FFFFF BOOT.BIN (FSBL + wolfboot)
26+
# 0x100000 - 0x6FFFFF BOOT_A (~6 MB primary)
27+
# 0x700000 - 0xCFFFFF UPDATE_B (~6 MB update)
28+
# 0xD00000 - 0xD0FFFF SWAP scratch (64 KB sector)
29+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000
30+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00700000
31+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x00D00000
32+
WOLFBOOT_PARTITION_SIZE=0x00600000
33+
WOLFBOOT_SECTOR_SIZE=0x10000
34+
35+
# DTS placeholders (used in Milestone 5 for Linux payload)
36+
WOLFBOOT_LOAD_DTS_ADDRESS=0x00100000
37+
WOLFBOOT_DTS_BOOT_ADDRESS=0x00080000
38+
WOLFBOOT_DTS_UPDATE_ADDRESS=0x00680000
39+
40+
IMAGE_HEADER_SIZE=1024
41+
42+
CROSS_COMPILE=arm-none-eabi-

docs/Targets.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ This README describes configuration of supported targets.
5353
* [TI Hercules TMS570LC435](#ti-hercules-tms570lc435)
5454
* [Vorago VA416x0](#vorago-va416x0)
5555
* [Xilinx Zynq UltraScale](#xilinx-zynq-ultrascale)
56+
* [Xilinx Zynq-7000 (ZC702)](#xilinx-zynq-7000-zc702)
5657
* [Versal Gen 1 VMK180](#versal-gen-1-vmk180)
5758

5859
## STM32F4
@@ -3391,6 +3392,220 @@ Entering idle loop...
33913392
```
33923393

33933394

3395+
## Xilinx Zynq-7000 (ZC702)
3396+
3397+
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).
3398+
3399+
wolfBoot is loaded by the Xilinx Zynq-7000 FSBL into DDR:
3400+
```
3401+
BootROM -> FSBL -> wolfBoot -> signed app (or U-Boot/Linux)
3402+
```
3403+
3404+
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.
3405+
3406+
This target supports:
3407+
- **QSPI boot** (primary): `config/examples/zynq7000.config`
3408+
- **SD card boot** (Milestone 6, planned): `config/examples/zc702_sdcard.config`
3409+
- **JTAG-loaded dev** via Platform Cable II + xsdb (no flash required)
3410+
3411+
### Prerequisites
3412+
3413+
1. **Toolchain**: `arm-none-eabi-gcc` (Arm bare-metal). Tested with 13.2.
3414+
2. **Xilinx Vitis** (provides `bootgen`, `xsdb`, and `program_flash`). Source the env once per shell:
3415+
```sh
3416+
source /opt/Xilinx/2025.2/Vitis/settings64.sh
3417+
```
3418+
Vivado's `settings64.sh` works equivalently if you don't have Vitis installed.
3419+
3. **Platform Cable II USB drivers** (one-time, requires root). Without these the
3420+
cable enumerates as `03fd:0013` with empty descriptors and `xsdb` reports no
3421+
JTAG targets:
3422+
```sh
3423+
sudo /opt/Xilinx/2025.2/Vitis/data/xicom/cable_drivers/lin64/install_script/install_drivers/install_drivers
3424+
```
3425+
Unplug/replug the cable afterward so udev can load the firmware.
3426+
4. **Pre-built ZC702 FSBL + DTB** (clone next to wolfboot-alt2):
3427+
```sh
3428+
git clone https://github.com/wolfSSL/soc-prebuilt-firmware.git
3429+
export PREBUILT_DIR=$(pwd)/../soc-prebuilt-firmware/zc702-zynq
3430+
ls $PREBUILT_DIR/zynq_fsbl.elf # required
3431+
```
3432+
5. **Hardware**: ZC702 with Platform Cable II (USB JTAG) connected to J22 and powered.
3433+
3434+
### Configuration Options
3435+
3436+
Key options in `config/examples/zynq7000.config`:
3437+
3438+
- `ARCH=ARM` - 32-bit ARM
3439+
- `TARGET=zynq7000` - selects `hal/zynq7000.{c,h,ld}` and the `CORTEX_A9` arch.mk block
3440+
- `SIGN=ECC256` / `HASH=SHA256` - smaller and faster than RSA on Cortex-A9
3441+
- `EXT_FLASH=1` - QSPI as external flash via `XQspiPs`
3442+
- `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`.
3443+
- `WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000` - 16 MB QSPI layout below
3444+
- `CROSS_COMPILE=arm-none-eabi-`
3445+
3446+
DDR layout:
3447+
3448+
| Region | Address range | Contents |
3449+
|---|---|---|
3450+
| App stage | `0x10000000`+ | Verified signed image, app text/data/bss/stack |
3451+
| Image header staging | `0x0FFFFC00`-`0x0FFFFFFF` | wolfBoot copies the 1 KB header here just before the load address |
3452+
| wolfBoot | `0x04000000`-`0x040FFFFF` | Loaded by FSBL, runs in place |
3453+
| FSBL/BootROM/OCM | `0x00000000`-`0x000FFFFF` | OCM low-mapped during boot |
3454+
3455+
QSPI partition layout (16 MB on-board flash):
3456+
3457+
| Offset | Size | Contents |
3458+
|-------------|---------|-----------------------------------|
3459+
| `0x000000` | ~512 KB | BOOT.BIN (FSBL + wolfboot) |
3460+
| `0x100000` | 6 MB | BOOT_A (signed primary image) |
3461+
| `0x700000` | 6 MB | UPDATE_B (signed update slot) |
3462+
| `0xD00000` | 64 KB | SWAP scratch sector |
3463+
| `0xD10000`+ | | reserved |
3464+
3465+
### Building wolfBoot
3466+
3467+
```sh
3468+
cp config/examples/zynq7000.config .config
3469+
make keysclean && make keytools
3470+
make TARGET=zynq7000 wolfboot.elf
3471+
```
3472+
3473+
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).
3474+
3475+
### Building BOOT.BIN (production QSPI boot)
3476+
3477+
```sh
3478+
cp ${PREBUILT_DIR}/zynq_fsbl.elf .
3479+
bootgen -arch zynq -image tools/scripts/zc702/zc702_qspi.bif -w -o BOOT.BIN
3480+
```
3481+
3482+
`bootgen` ships with Vitis. The `.bif` template at `tools/scripts/zc702/zc702_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).
3483+
3484+
### Programming QSPI
3485+
3486+
Set ZC702 boot mode straps to **JTAG** for programming, then either:
3487+
- Vitis: `program_flash -f BOOT.BIN -flash_type qspi_single -fsbl ${PREBUILT_DIR}/zynq_fsbl.elf`
3488+
- Vivado Hardware Manager: Tools -> Add Configuration Memory Device -> select N25Q128 -> program with BOOT.BIN at offset 0.
3489+
3490+
After programming, set boot mode to **QSPI** (SW16 - see UG850 ch.1.2.4) and power-cycle. Console comes up on UART1 (J17 USB-UART), 115200 8N1.
3491+
3492+
### JTAG-loaded development (no flash)
3493+
3494+
For driver bring-up or quick iteration, skip bootgen and load directly via Platform Cable II:
3495+
3496+
```sh
3497+
source /opt/Xilinx/2025.2/Vitis/settings64.sh # once per shell
3498+
xsdb tools/scripts/zc702/jtag_load.tcl
3499+
```
3500+
3501+
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.
3502+
3503+
With a signed image programmed at QSPI offset `0x100000` (see "Building and flashing the signed test app" below), expected UART output is:
3504+
3505+
```
3506+
wolfBoot Zynq-7000 (ZC702) hal_init
3507+
Versions: Boot 1, Update 0
3508+
Trying Boot partition at 0x100000
3509+
Loading header 1024 bytes from 0x100000 to 0xFFFFC00
3510+
Loading image 396 bytes from 0x100400 to 0x10000000...done
3511+
Boot partition: 0xFFFFC00 (sz 396, ver 0x1, type 0x201)
3512+
Checking integrity...done
3513+
Verifying signature...done
3514+
Successfully selected image in part: 0
3515+
Firmware Valid
3516+
Booting at 0x10000000
3517+
3518+
=== ZC702 test-app: BOOT OK ===
3519+
wolfBoot verified + chain-loaded this image
3520+
.....
3521+
```
3522+
3523+
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.
3524+
3525+
If `xsdb` reports `no targets found` or empty `jtag servers`, either:
3526+
- Cable USB drivers not installed - see step 3 of Prerequisites, OR
3527+
- 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.
3528+
3529+
A separate JTAG-only dev build (no QSPI driver) can be produced with `make EXT_FLASH=0`.
3530+
3531+
### Building and flashing the signed test app
3532+
3533+
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`:
3534+
3535+
```sh
3536+
cp config/examples/zynq7000.config .config
3537+
make keysclean && make # builds wolfboot.elf + test-app/image_v1_signed.bin
3538+
```
3539+
3540+
Program the signed image to QSPI offset `0x100000` (the BOOT_A partition):
3541+
3542+
```sh
3543+
program_flash -f test-app/image_v1_signed.bin \
3544+
-fsbl ${PREBUILT_DIR}/zynq_fsbl.elf \
3545+
-flash_type qspi_single -offset 0x100000
3546+
```
3547+
3548+
`program_flash` ships with Vitis. Then run wolfBoot via `xsdb tools/scripts/zc702/jtag_load.tcl` - it should verify and chain-load the test app, producing the heartbeat output above.
3549+
3550+
### QSPI driver self-test (`TEST_EXT_FLASH`)
3551+
3552+
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`:
3553+
3554+
```sh
3555+
make CFLAGS_EXTRA=-DTEST_EXT_FLASH wolfboot.elf
3556+
xsdb tools/scripts/zc702/jtag_load.tcl
3557+
```
3558+
3559+
Expected output:
3560+
3561+
```
3562+
qspi: --- TEST_EXT_FLASH start ---
3563+
qspi: JEDEC ID = 0x20bb18 rc=00 <- Micron N25Q128
3564+
qspi: read @0x100000 = 574f4c468c010000 <- "WOLF" magic from a programmed signed image
3565+
qspi: erase sector @ 0x00200000 ...
3566+
qspi: page program ...
3567+
qspi: post-program JEDEC = 0x20bb18
3568+
qspi: rdback[0..7] = 0001020304050607
3569+
qspi: --- TEST_EXT_FLASH PASS ---
3570+
```
3571+
3572+
### QSPI driver design
3573+
3574+
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):
3575+
3576+
| Operation | Path | Why |
3577+
|---|---|---|
3578+
| 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 |
3579+
| 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 |
3580+
3581+
`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`.
3582+
3583+
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).
3584+
3585+
### Boot flow notes
3586+
3587+
- **Cortex-A9 startup**: `src/boot_zynq7000_start.S` (Z7-specific) 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`.
3588+
- **MMU stays ON**, inheriting FSBL's flat 1:1 DDR mapping. Disabling the MMU on Cortex-A9 makes all memory Strongly-Ordered, which traps unaligned LDR/STR and breaks any ARMv7-A unrolled `memcpy`.
3589+
- **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.
3590+
- **`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.
3591+
- **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`.
3592+
- **Register handoff**: per ARM Linux boot ABI - `r0 = 0`, `r1 = 0`, `r2 = DTB ptr` (Linux/U-Boot, Milestone 5), entry in `r4`.
3593+
- **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.
3594+
3595+
### Differences from the ZynqMP port
3596+
3597+
| Aspect | ZynqMP (`hal/zynq.c`) | Zynq-7000 (`hal/zynq7000.c`) |
3598+
|------------------|-------------------------------|------------------------------|
3599+
| CPU | Cortex-A53 quad, AArch64 | Cortex-A9 dual, ARMv7-A |
3600+
| QSPI controller | GQSPI (`XQspiPsu`) | Linear/Static (`XQspiPs`) |
3601+
| UART IP | XUartPs @ `0xFF000000` | XUartPs @ `0xE0001000` |
3602+
| SDHCI | Arasan v3.0 + Cadence shim | Arasan v2.0 (planned) |
3603+
| Crypto HW | CSU (AES-GCM, SHA3, PUF) | none (DevC AES only) |
3604+
| Boot chain | FSBL + PMUFW + BL31 + wolfBoot| FSBL + wolfBoot |
3605+
| Linux EL | EL2 (hypervisor) | SVC (no exception levels) |
3606+
| `bootgen -arch` | `zynqmp` | `zynq` |
3607+
3608+
33943609
## Versal Gen 1 VMK180
33953610

33963611
AMD Versal Prime Series VMK180 Evaluation Kit - Versal Prime XCVM1802-2MSEVSVA2197 Adaptive SoC - Dual ARM Cortex-A72.

0 commit comments

Comments
 (0)