Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions config/examples/zynqmp_sdcard.config
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ HASH?=SHA3
IMAGE_HEADER_SIZE?=1024

# Debug options
DEBUG?=1
DEBUG?=0
DEBUG_SYMBOLS=1
DEBUG_UART=1
CFLAGS_EXTRA+=-DDEBUG_ZYNQ=1

# Display boot timing data
#BOOT_BENCHMARK?=1

# SD card support - use SDHCI driver
DISK_SDCARD?=1
DISK_EMMC?=0
Expand All @@ -38,9 +41,14 @@ NO_XIP=1

# ELF loading support
ELF?=1
#DEBUG_ELF?=1

# Boot Exception Level: transition from EL2 -> EL1 before jumping to app
BOOT_EL1?=1
# Boot Exception Level: leave wolfBoot at EL2 for handoff to Linux (matches
# the standard PetaLinux U-Boot flow and preserves KVM/hypervisor use of
# EL2). The EL2 Linux-cleanup path in do_boot() will clean dcache/disable
# MMU before jumping to the kernel. To drop to EL1 via ERET instead, set
# BOOT_EL1?=1 (requires EL2_HYPERVISOR=1, which is the hal/zynq.h default).
#BOOT_EL1?=1

# General options
VTOR?=1
Expand Down Expand Up @@ -75,16 +83,29 @@ WOLFBOOT_NO_PARTITIONS=1
CFLAGS_EXTRA+=-DBOOT_PART_A=1
CFLAGS_EXTRA+=-DBOOT_PART_B=2

# Disk read chunk size (512KB)
# Disk read chunk size for firmware loading (update_disk.c). 512KB gives the
# best throughput (~1.4s for 32MB). The SDMA engine handles SDMA buffer
# boundary crossings within each 512KB chunk; this boundary is 4KB by default
# (auto-derived from SDHCI_DMA_THRESHOLD). To reduce boundary IRQs, override
# SDHCI_DMA_BUFF_BOUNDARY independently using the raw register value so the
# override is safe to use in preprocessor #if expressions, e.g.:
# CFLAGS_EXTRA+=-DSDHCI_DMA_BUFF_BOUNDARY=0x7000 # 512KB (SDHCI_SRS01_DMA_BUFF_512KB)
CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000

# Linux rootfs is on partition 4 (SD1 = mmcblk1)
CFLAGS_EXTRA+=-DLINUX_BOOTARGS_ROOT=\"/dev/mmcblk1p4\"
# Linux rootfs is on partition 4. Device naming depends on whether both
# ZynqMP SDHCI controllers are enabled in the XSA / device tree:
# * both sdhci0 + sdhci1 enabled -> SD1 = /dev/mmcblk1
# * only sdhci1 enabled (ZCU102 default -> only external SD populated)
# -> SD1 = /dev/mmcblk0
# Check `ls /sys/class/mmc_host/` on your running target to confirm.
CFLAGS_EXTRA+=-DLINUX_BOOTARGS_ROOT=\"/dev/mmcblk0p4\"

# ============================================================================
# Boot Memory Layout
# ============================================================================
# wolfBoot runs from DDR at 0x8000000 (same as U-Boot, loaded via BL31)
# wolfBoot runs from DDR at 0x8000000 (128MB, same as U-Boot, loaded via BL31).
# Must match the ORIGIN in hal/zynq.ld. WOLFBOOT_LOAD_ADDRESS must be above
# this region (wolfBoot is ~2MB) to avoid self-overwrite during firmware load.
WOLFBOOT_ORIGIN=0x8000000

# Load Partition to RAM Address (Linux kernel loads here)
Expand Down
26 changes: 26 additions & 0 deletions docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -2599,6 +2599,8 @@ qemu-system-aarch64 -machine xlnx-zcu102 -cpu cortex-a53 -serial stdio -display

Use `config/examples/zynqmp_sdcard.config`. This uses the Arasan SDHCI controller (SD1 - external SD card slot on ZCU102) and an **MBR** partitioned SD card.

On the direct-jump handoff path, wolfBoot flushes the EL2 D-cache/I-cache and disables the EL2 MMU via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S` when `BOOT_EL1` is not enabled and the current exception level is EL2. The ERET-to-EL1 handoff path is different, so this cleanup is not unconditional.

**Partition layout**
| Partition | Name | Size | Type | Contents |
|-----------|--------|-----------|-------------------------------|-------------------------------------------|
Expand Down Expand Up @@ -2687,6 +2689,28 @@ Set the ZCU102 boot mode switches (SW6) for SD card boot:
| QSPI32 | 0 0 1 0 | on, on, off, on |
| SD1 | 1 1 1 0 | off, off, off, on |

**SDHCI Notes (Arasan controller)**

The ZynqMP uses an Arasan SDHCI v3.0 controller. Key considerations:

- **SDMA vs PIO**: The PIO (Programmed I/O) multi-block read path has a race condition on
this controller under compiler optimization (`-Os`/`-O2`). The BRR (Buffer Read Ready)
flag is re-polled too quickly between blocks, causing stale data reads that corrupt
firmware images. The default `SDHCI_DMA_THRESHOLD=4096` forces all multi-block reads
through the SDMA path, which avoids this issue entirely.
- **HV4E redirect**: The Arasan controller does not support Host Version 4 Enable (HV4E).
The platform HAL in `hal/zynq.c` transparently redirects SRS22/SRS23 writes to the
legacy SRS00 register for 32-bit SDMA addressing.
- **Card detect**: The Arasan controller does not support CDSS/CDTL card detect test
level. `SDHCI_FORCE_CARD_DETECT` is set in the config since FSBL already booted from
the same SD card.
- **`DISK_BLOCK_SIZE`**: Controls the firmware read chunk size in `update_disk.c` (default
64KB). This determines the per-read size passed to the SDHCI driver. It does not need
to be smaller than `SDHCI_DMA_BUFF_BOUNDARY`; if a read crosses one or more SDMA buffer
boundaries, the SDHCI driver handles that via the normal SDMA boundary interrupt path.
In practice, this setting is a tradeoff: larger reads may trigger boundary IRQs more
often, while smaller reads reduce crossings but increase request overhead.

**Debug**

Enable SDHCI debug output by uncommenting in the config:
Expand Down Expand Up @@ -3005,6 +3029,8 @@ Typical boot timing with ECC384/SHA384 signing:

Use `config/examples/versal_vmk180_sdcard.config`. This uses the Arasan SDHCI controller and an **MBR** partitioned SD card.

Versal defaults to `BOOT_EL1` — the handoff goes through `el2_to_el1_boot` (ERET to EL1). Custom `BOOT_EL2` Versal configs get the same EL2 cache/MMU teardown as ZynqMP via `el2_flush_and_disable_mmu` in `src/boot_aarch64_start.S`, so no extra config flag is needed to boot Linux directly at EL2.

**Partition layout**
| Partition | Name | Size | Type | Contents |
|-----------|------|------|------|----------|
Expand Down
12 changes: 8 additions & 4 deletions hal/versal.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
/* Linux kernel command line arguments */
#ifndef LINUX_BOOTARGS
#ifndef LINUX_BOOTARGS_ROOT
/* Default Versal SD layout: rootfs on partition 2. Configurations that use
* the 4-partition MBR layout with OFP_A/OFP_B slots
* (boot / OFP_A / OFP_B / rootfs, e.g. config/examples/zynqmp_sdcard.config)
* should override LINUX_BOOTARGS_ROOT to "/dev/mmcblk0p4". */
#define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p2"
#endif

Expand Down Expand Up @@ -1275,16 +1279,16 @@ int hal_dts_fixup(void* dts_addr)
/* Expand total size to allow adding/modifying properties */
fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512);

/* Find /chosen node */
/* Find /chosen node; create it only if genuinely missing. Any other
* negative return (malformed FDT, etc.) is surfaced directly rather
* than masked by a follow-on fdt_add_subnode() failure. */
off = fdt_find_node_offset(fdt, -1, "chosen");
if (off < 0) {
/* Create /chosen node if it doesn't exist */
if (off == -FDT_ERR_NOTFOUND) {
off = fdt_add_subnode(fdt, 0, "chosen");
}

if (off >= 0) {
/* Set bootargs property */
wolfBoot_printf("FDT: Setting bootargs: %s\n", LINUX_BOOTARGS);
fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS);
} else {
wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off);
Expand Down
156 changes: 149 additions & 7 deletions hal/zynq.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@
/* QSPI bare-metal */
#endif

/* DTB fixup for kernel command line. Override LINUX_BOOTARGS or
* LINUX_BOOTARGS_ROOT in your config to customize.
*
* Note: console=ttyPS0 is ZynqMP-specific (PS UART0). Versal's default
* (hal/versal.c) omits the console= token because Versal relies on
* earlycon alone plus a DT-declared stdout-path. */
#ifndef LINUX_BOOTARGS
#ifndef LINUX_BOOTARGS_ROOT
#define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p4"
#endif
#define LINUX_BOOTARGS \
"earlycon console=ttyPS0,115200 root=" LINUX_BOOTARGS_ROOT " rootwait"
#endif

/* QSPI Slave Device Information */
typedef struct QspiDev {
uint32_t mode; /* GQSPI_GEN_FIFO_MODE_SPI, GQSPI_GEN_FIFO_MODE_DSPI or GQSPI_GEN_FIFO_MODE_QSPI */
Expand Down Expand Up @@ -490,7 +504,6 @@ static int csu_dma_config(int ch, int doSwap)
int csu_aes(int enc, const uint8_t* iv, const uint8_t* in, uint8_t* out, uint32_t sz)
{
int ret;
uint32_t reg;

/* Flush data cache for variables used */
flush_dcache_range((unsigned long)iv, (unsigned long)iv + AES_GCM_TAG_SZ);
Expand Down Expand Up @@ -587,7 +600,6 @@ int csu_init(void)
#endif
uint32_t reg1 = pmu_mmio_read(CSU_IDCODE);
uint32_t reg2 = pmu_mmio_read(CSU_VERSION);
uint64_t ms;

wolfBoot_printf("CSU ID 0x%08x, Ver 0x%08x\n",
reg1, reg2 & CSU_VERSION_MASK);
Expand Down Expand Up @@ -1347,6 +1359,39 @@ static int qspi_exit_4byte_addr(QspiDev_t* dev)
}
#endif

/* Soft-reset the flash to a known idle state.
* FSBL / BootROM may leave the flash in an unexpected mode (XIP enabled,
* 4-byte addr set, auto-boot probing, etc.). Issue RESET_ENABLE (0x66) +
* RESET_MEMORY (0x99) to bring it back to defaults before first transaction.
* Per Micron MT25Q datasheet: t_SHSL2 ~ 40 us max after RESET_MEMORY. */
static int qspi_flash_reset(QspiDev_t* dev)
{
int ret;
uint8_t cmd[4]; /* size multiple of uint32_t */

memset(cmd, 0, sizeof(cmd));
cmd[0] = RESET_ENABLE_CMD;
/* Reset commands are always issued in single-SPI mode regardless of
* dev->mode: the flash's current bus mode is unknown at reset time, and
* the single-SPI opcode is the universal-compatible form. */
ret = qspi_transfer(dev, cmd, 1, NULL, 0, NULL, 0, 0,
GQSPI_GEN_FIFO_MODE_SPI);
#if defined(DEBUG_ZYNQ) && DEBUG_ZYNQ >= 2
wolfBoot_printf("Flash Reset Enable: Ret %d\n", ret);
#endif
if (ret == GQSPI_CODE_SUCCESS) {
cmd[0] = RESET_MEMORY_CMD;
ret = qspi_transfer(dev, cmd, 1, NULL, 0, NULL, 0, 0,
GQSPI_GEN_FIFO_MODE_SPI);
#if defined(DEBUG_ZYNQ) && DEBUG_ZYNQ >= 2
wolfBoot_printf("Flash Reset Memory: Ret %d\n", ret);
#endif
}
/* Allow flash time to complete the reset and become ready. */
hal_delay_ms(1);
return ret;
}

/* QSPI functions */
void qspi_init(void)
{
Expand Down Expand Up @@ -1430,13 +1475,29 @@ void qspi_init(void)
#if (GQSPI_CLK_REF / (2 << GQSPI_CLK_DIV)) <= 40000000 /* 40MHz */
/* At <40 MHz, the Quad-SPI controller should be in non-loopback mode with
* the clock and data tap delays bypassed. */
IOU_TAPDLY_BYPASS |= IOU_TAPDLY_BYPASS_LQSPI_RX;
/* IOU_TAPDLY_BYPASS is not writable from EL2/EL1 without going through PMU. */
if (current_el() <= 2) {
pmu_request(PM_MMIO_WRITE, IOU_TAPDLY_BYPASS_ADDR,
IOU_TAPDLY_BYPASS_LQSPI_RX, IOU_TAPDLY_BYPASS_LQSPI_RX,
0, NULL);
}
else {
IOU_TAPDLY_BYPASS |= IOU_TAPDLY_BYPASS_LQSPI_RX;
}
GQSPI_LPBK_DLY_ADJ = 0;
GQSPI_DATA_DLY_ADJ = 0;
#elif (GQSPI_CLK_REF / (2 << GQSPI_CLK_DIV)) <= 100000000 /* 100MHz */
/* At <100 MHz, the Quad-SPI controller should be in clock loopback mode
* with the clock tap delay bypassed, but the data tap delay enabled. */
IOU_TAPDLY_BYPASS |= IOU_TAPDLY_BYPASS_LQSPI_RX;
/* IOU_TAPDLY_BYPASS is not writable from EL2/EL1 without going through PMU. */
if (current_el() <= 2) {
pmu_request(PM_MMIO_WRITE, IOU_TAPDLY_BYPASS_ADDR,
IOU_TAPDLY_BYPASS_LQSPI_RX, IOU_TAPDLY_BYPASS_LQSPI_RX,
0, NULL);
}
else {
IOU_TAPDLY_BYPASS |= IOU_TAPDLY_BYPASS_LQSPI_RX;
}
GQSPI_LPBK_DLY_ADJ = GQSPI_LPBK_DLY_ADJ_USE_LPBK;
GQSPI_DATA_DLY_ADJ = (GQSPI_DATA_DLY_ADJ_USE_DATA_DLY |
GQSPI_DATA_DLY_ADJ_DATA_DLY_ADJ(2));
Expand Down Expand Up @@ -1471,6 +1532,19 @@ void qspi_init(void)
(void)reg_cfg;
(void)reg_isr;

/* Issue flash soft reset so we start from a known state regardless of
* whatever mode FSBL/BootROM left the device in. Send to each chip in
* dual-parallel configurations by targeting both chip selects. */
mDev.mode = GQSPI_GEN_FIFO_MODE_SPI;
mDev.bus = GQSPI_GEN_FIFO_BUS_LOW;
mDev.cs = GQSPI_GEN_FIFO_CS_LOWER;
(void)qspi_flash_reset(&mDev);
#if GQPI_USE_DUAL_PARALLEL == 1
mDev.bus = GQSPI_GEN_FIFO_BUS_UP;
mDev.cs = GQSPI_GEN_FIFO_CS_UPPER;
(void)qspi_flash_reset(&mDev);
#endif

/* ------ Flash Read ID (retry) ------ */
timeout = 0;
while (++timeout < QSPI_FLASH_READY_TRIES) {
Expand Down Expand Up @@ -1563,6 +1637,10 @@ void hal_init(void)
wolfBoot_printf(bootMsg);
wolfBoot_printf("Current EL: %d\n", current_el());

#ifndef WOLFBOOT_REPRODUCIBLE_BUILD
wolfBoot_printf("Build: %s %s\n", __DATE__, __TIME__);
#endif

#if defined(EXT_FLASH) && (EXT_FLASH == 1)
qspi_init();
#endif
Expand Down Expand Up @@ -1795,7 +1873,33 @@ void RAMFUNCTION ext_flash_unlock(void)

}

#ifdef MMU
/* The following helpers (hal_get_timer_us, hal_get_dts_address, hal_dts_fixup)
* are only compiled into the wolfBoot binary. The test-app build also links
* hal/zynq.o but must not pull in FDT/MMU-specific code, so __WOLFBOOT gates
* these symbols out of that build. */
#if defined(MMU) && defined(__WOLFBOOT)
/* Fallback timer frequency if CNTFRQ_EL0 is not configured (e.g. boot path
* that did not run ATF/BL31). ZynqMP system counter is 100 MHz. */
#ifndef ZYNQMP_TIMER_CLK_FREQ
#define ZYNQMP_TIMER_CLK_FREQ 100000000ULL
#endif

/* Get current time in microseconds using ARMv8 generic timer */
uint64_t hal_get_timer_us(void)
{
uint64_t count, freq;
__asm__ volatile("mrs %0, CNTPCT_EL0" : "=r"(count));
__asm__ volatile("mrs %0, CNTFRQ_EL0" : "=r"(freq));
/* Fall back to a known frequency rather than returning 0, so udelay()
* callers that spin on hal_get_timer_us() advancing remain monotonic
* (matches hal/versal.c). */
if (freq == 0)
freq = ZYNQMP_TIMER_CLK_FREQ;
/* Use __uint128_t to avoid overflow of (count * 1e6) at long uptimes
* (would overflow uint64_t after ~51h at 100MHz). */
return (uint64_t)(((__uint128_t)count * 1000000ULL) / freq);
}
Comment thread
dgarske marked this conversation as resolved.

void* hal_get_dts_address(void)
{
Comment thread
dgarske marked this conversation as resolved.
#ifdef WOLFBOOT_DTS_BOOT_ADDRESS
Expand All @@ -1809,8 +1913,46 @@ void* hal_get_dts_address(void)

int hal_dts_fixup(void* dts_addr)
{
/* place FDT fixup specific to ZynqMP here */
//fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt));
int off, ret;
struct fdt_header *fdt = (struct fdt_header *)dts_addr;

/* Verify FDT header */
ret = fdt_check_header(dts_addr);
if (ret != 0) {
wolfBoot_printf("FDT: Invalid header! %d\n", ret);
return ret;
}

wolfBoot_printf("FDT: Version %d, Size %d\n",
fdt_version(fdt), fdt_totalsize(fdt));
Comment thread
dgarske marked this conversation as resolved.

/* Expand totalsize so fdt_setprop() has in-blob free space to place
* a new/larger bootargs property. Physical headroom is already
* guaranteed by the load-address layout (DTB at WOLFBOOT_LOAD_DTS_ADDRESS,
* kernel loaded much higher), so growing the header is safe. Matches
* the pattern used in hal/versal.c:hal_dts_fixup. */
fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512);
Comment thread
dgarske marked this conversation as resolved.

/* Find /chosen node; create it only if genuinely missing. Any other
* negative return (malformed FDT, etc.) is surfaced directly rather
* than masked by a follow-on fdt_add_subnode() failure. */
off = fdt_find_node_offset(fdt, -1, "chosen");
if (off == -FDT_ERR_NOTFOUND) {
off = fdt_add_subnode(fdt, 0, "chosen");
}
if (off < 0) {
wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off);
return off;
}

/* Set bootargs property - overrides PetaLinux default root= with
* the wolfBoot partition layout. */
ret = fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS);
if (ret < 0) {
wolfBoot_printf("FDT: Failed to set bootargs (%d)\n", ret);
return ret;
}

return 0;
}
#endif
Expand Down
6 changes: 4 additions & 2 deletions hal/zynq.ld
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ MEMORY
{
/* psu_ddr_0_MEM_0 : ORIGIN = 0x0, LENGTH = 0x80000000 */
/* wolfBoot DDR location (2MB reserved):
* Loaded by FSBL/BL31 to DDR at 0x8000000 (128MB)
* Same address used for both QSPI and SD card boot
* Loaded by FSBL/BL31 to DDR at 0x8000000 (128MB, same as U-Boot).
* WOLFBOOT_LOAD_ADDRESS (signed-image staging area) must be above
* this region to avoid overwriting wolfBoot during firmware load.
* Must match WOLFBOOT_ORIGIN in the target .config.
*/
psu_ddr_0_MEM_0 : ORIGIN = 0x8000000, LENGTH = 0x200000
psu_ddr_1_MEM_0 : ORIGIN = 0x800000000, LENGTH = 0x80000000
Expand Down
Loading
Loading