diff --git a/config/examples/zynqmp_sdcard.config b/config/examples/zynqmp_sdcard.config
index 00f62c4013..f2401021f3 100644
--- a/config/examples/zynqmp_sdcard.config
+++ b/config/examples/zynqmp_sdcard.config
@@ -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
@@ -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
@@ -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)
diff --git a/docs/Targets.md b/docs/Targets.md
index 6c915ee22d..0f193afe7d 100644
--- a/docs/Targets.md
+++ b/docs/Targets.md
@@ -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 |
|-----------|--------|-----------|-------------------------------|-------------------------------------------|
@@ -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:
@@ -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 |
|-----------|------|------|------|----------|
diff --git a/hal/versal.c b/hal/versal.c
index 5423e7c720..46d64d46fc 100644
--- a/hal/versal.c
+++ b/hal/versal.c
@@ -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
@@ -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);
diff --git a/hal/zynq.c b/hal/zynq.c
index 37ff2ee181..2105d7fa5a 100644
--- a/hal/zynq.c
+++ b/hal/zynq.c
@@ -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 */
@@ -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);
@@ -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);
@@ -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)
{
@@ -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));
@@ -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) {
@@ -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
@@ -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);
+}
+
void* hal_get_dts_address(void)
{
#ifdef WOLFBOOT_DTS_BOOT_ADDRESS
@@ -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));
+
+ /* 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);
+
+ /* 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
diff --git a/hal/zynq.ld b/hal/zynq.ld
index 230b76455c..f4b4b2fcc1 100644
--- a/hal/zynq.ld
+++ b/hal/zynq.ld
@@ -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
diff --git a/include/sdhci.h b/include/sdhci.h
index 65fccf48ef..35415026cc 100644
--- a/include/sdhci.h
+++ b/include/sdhci.h
@@ -40,9 +40,16 @@
#define SDHCI_BLOCK_SIZE 512
#endif
-/* DMA threshold - minimum transfer size to use DMA mode (default: 512KB) */
+/* DMA threshold - minimum transfer size (bytes) to use SDMA instead of PIO.
+ * Default 4KB: forces SDMA for virtually all multi-block CMD18 reads.
+ * The PIO multi-block read path has a known race condition on Arasan SDHCI
+ * controllers (ZynqMP, Versal) where BRR (Buffer Read Ready) is re-checked
+ * too quickly between blocks under compiler optimization (-Os/-O2), causing
+ * stale data reads and firmware integrity failures. Using SDMA avoids the
+ * BRR polling loop entirely.
+ * Override in target .config if a larger PIO window is acceptable. */
#ifndef SDHCI_DMA_THRESHOLD
-#define SDHCI_DMA_THRESHOLD (512U * 1024U)
+#define SDHCI_DMA_THRESHOLD (4U * 1024U)
#endif
/* Disk test block address (platform should override) */
@@ -50,7 +57,13 @@
#define DISK_TEST_BLOCK_ADDR 149504 /* ~76MB offset */
#endif
-/* Auto-select DMA buffer boundary based on threshold */
+/* DMA buffer boundary: how often the SDMA engine pauses to refresh its
+ * address pointer (handled by sdhci_irq_handler() via SDHCI_SRS12_DMAINT).
+ * This is a throughput knob and is independent of SDHCI_DMA_THRESHOLD
+ * (which controls when to switch from PIO to SDMA). Override in target
+ * .config to match the largest expected single transfer for fewer
+ * boundary IRQs; otherwise auto-select based on the threshold. */
+#ifndef SDHCI_DMA_BUFF_BOUNDARY
#if (SDHCI_DMA_THRESHOLD > (256U * 1024U))
#define SDHCI_DMA_BUFF_BOUNDARY SDHCI_SRS01_DMA_BUFF_512KB
#if (SDHCI_DMA_THRESHOLD != (512U * 1024U))
@@ -92,6 +105,7 @@
#warning "SDHCI_DMA_THRESHOLD rounded up to 4KB (minimum)"
#endif
#endif
+#endif /* !SDHCI_DMA_BUFF_BOUNDARY */
/* Timeouts */
#ifndef SDHCI_INIT_TIMEOUT_US
diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c
index 7b8fccbadc..cf8d074796 100644
--- a/src/boot_aarch64.c
+++ b/src/boot_aarch64.c
@@ -26,9 +26,16 @@
#include "printf.h"
#include "wolfboot/wolfboot.h"
-/* Include platform-specific header for EL configuration defines */
-#ifdef TARGET_versal
+/* Include platform-specific header for EL configuration defines
+ * (EL2_HYPERVISOR, etc.). Must be visible here so the BOOT_EL1 /
+ * EL2_HYPERVISOR guards around the EL2->EL1 ERET transition below
+ * compile in for the active target. */
+#if defined(TARGET_versal)
#include "hal/versal.h"
+#elif defined(TARGET_zynq)
+#include "hal/zynq.h"
+#elif defined(TARGET_nxp_ls1028a)
+#include "hal/nxp_ls1028a.h"
#endif
/* Linker exported variables */
@@ -43,6 +50,17 @@ extern unsigned int _end_data;
extern void main(void);
extern void gicv2_init_secure(void);
+/* Asm helper in boot_aarch64_start.S: cleans the entire D-cache to PoC,
+ * invalidates the I-cache to PoU, and disables MMU + I-cache + D-cache
+ * via SCTLR_EL2, then returns. Required before handoff to any payload
+ * that sets up its own translation (Linux kernel, hypervisor, bare-metal
+ * RTOS, later bootloader stage), and mandatory for the ARM64 Linux boot
+ * protocol. Only built when EL2_HYPERVISOR == 1 is visible to
+ * boot_aarch64_start.S (e.g. via hal/zynq.h on ZynqMP). */
+#if defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1
+extern void el2_flush_and_disable_mmu(void);
+#endif
+
/* SKIP_GIC_INIT - Skip GIC initialization before booting app
* This is needed for:
* - Versal: Uses GICv3, not GICv2. BL31 handles GIC setup.
@@ -163,7 +181,22 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset)
el2_to_el1_boot((uintptr_t)app_offset, dts);
}
#else
- /* Stay at current EL (EL2 or EL3) and jump directly to application */
+ /* Stay at current EL (EL2 or EL3) and jump directly to application.
+ *
+ * Before the jump, tear down wolfBoot's EL2 MMU/caches so the next
+ * stage enters with a clean state. Mandatory for the ARM64 Linux
+ * boot protocol (Linux's arm64_panic_block_init() panics with
+ * "Non-EFI boot detected with MMU and caches enabled" otherwise),
+ * and correct for any payload that sets up its own translation
+ * (hypervisor, RTOS, later bootloader stage). */
+#if defined(MMU) && defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1
+ if (current_el() == 2) {
+ wolfBoot_printf("do_boot: flushing caches, disabling MMU\n");
+ el2_flush_and_disable_mmu();
+ }
+#endif
+
+ /* Non-Linux EL2 and EL3 path: legacy direct br x4 */
/* Set application address via x4 */
asm volatile("mov x4, %0" : : "r"(app_offset));
diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S
index 03b6359e71..544b5e8de9 100644
--- a/src/boot_aarch64_start.S
+++ b/src/boot_aarch64_start.S
@@ -1334,4 +1334,118 @@ el2_to_el1_boot:
b .
#endif /* BOOT_EL1 && EL2_HYPERVISOR */
+
+/*
+ * Clean entire D-cache to the Point of Coherency (PoC), invalidate the
+ * I-cache to the Point of Unification (PoU), and disable MMU + I/D-cache
+ * at EL2. Returns normally to the caller.
+ *
+ * Terminology (ARM ARM B2.8):
+ * PoC - Point of Coherency: the point at which all observers (CPUs,
+ * DMA masters, etc.) see the same memory. Cleaning to PoC
+ * guarantees the image bytes we memcpy'd are visible to the
+ * next stage's first uncached instruction fetches.
+ * PoU - Point of Unification: the point at which instruction and data
+ * caches converge. Invalidating I-cache to PoU ensures stale
+ * fetches are discarded before we hand off.
+ *
+ * wolfBoot's startup (line ~347 above) enables MMU+I+D cache at EL2 for
+ * its own use. Any payload we hand off to (Linux kernel, hypervisor,
+ * bare-metal RTOS, a later bootloader stage) expects to enter without
+ * inheriting wolfBoot's translation tables, and the ARM64 Linux boot
+ * protocol (Documentation/arch/arm64/booting.rst) explicitly REQUIRES
+ * MMU off, D-cache off, and the loaded image cleaned to PoC. This
+ * helper performs that teardown and returns; the caller then performs
+ * the actual jump with whatever ABI the payload expects.
+ *
+ * Safe to return because wolfBoot's .text is identity-mapped (VA=PA)
+ * at EL2, so instruction fetch keeps working after SCTLR_EL2.M is
+ * cleared.
+ *
+ * AAPCS64: clobbers x0-x7, x9-x11; x30 (LR) is preserved because the
+ * set/way loop body does not touch it.
+ */
+#if defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1
+.global el2_flush_and_disable_mmu
+el2_flush_and_disable_mmu:
+ /* ---- 1. Clean & invalidate entire data cache to PoC by set/way ----
+ * Standard ARMv8 routine, adapted from arm-trusted-firmware /
+ * U-Boot / Linux. Iterates every (level, set, way) triple and
+ * issues `dc cisw` on it. Terminates at the Level of Coherency
+ * (LoC) read from CLIDR_EL1. */
+ mrs x0, clidr_el1
+ and x3, x0, #0x07000000 /* x3 = LoC (level of coherency) */
+ lsr x3, x3, #23 /* x3 = LoC * 2 */
+ cbz x3, .Ldcache_done
+ mov x10, #0 /* x10 = current cache level << 1 */
+
+.Ldcache_level_loop:
+ add x2, x10, x10, lsr #1 /* x2 = level * 3 */
+ lsr x1, x0, x2 /* x1 = ctype field for this level */
+ and x1, x1, #7
+ cmp x1, #2
+ b.lt .Ldcache_skip_level /* No data cache at this level */
+ msr csselr_el1, x10 /* Select cache level (instruction = 0) */
+ isb
+ mrs x1, ccsidr_el1
+ and x2, x1, #7 /* x2 = log2(line length) - 4 */
+ add x2, x2, #4 /* x2 = log2(line length) */
+ mov x4, #0x3ff
+ and x4, x4, x1, lsr #3 /* x4 = max way number */
+ clz w5, w4 /* x5 = bit position of way size */
+ mov x7, #0x7fff
+ and x7, x7, x1, lsr #13 /* x7 = max set number */
+
+.Ldcache_set_loop:
+ mov x9, x4 /* x9 = current way */
+.Ldcache_way_loop:
+ lsl x6, x9, x5
+ orr x11, x10, x6 /* level | way */
+ lsl x6, x7, x2
+ orr x11, x11, x6 /* level | way | set */
+ dc cisw, x11 /* clean & invalidate by set/way */
+ subs x9, x9, #1
+ b.ge .Ldcache_way_loop
+ subs x7, x7, #1
+ b.ge .Ldcache_set_loop
+
+.Ldcache_skip_level:
+ add x10, x10, #2
+ cmp x3, x10
+ b.gt .Ldcache_level_loop
+
+.Ldcache_done:
+ mov x10, #0
+ msr csselr_el1, x10
+ dsb sy
+ isb
+
+ /* ---- 2. Invalidate entire I-cache to PoU ----
+ * `ic iallu` invalidates all instruction cache to the Point of
+ * Unification for the local PE. */
+ ic iallu
+ dsb ish
+ isb
+
+ /* ---- 3. Disable MMU + I-cache + D-cache at EL2 ----
+ * SCTLR_EL2.M (bit 0) = MMU enable
+ * SCTLR_EL2.C (bit 2) = D-cache enable
+ * SCTLR_EL2.I (bit 12) = I-cache enable
+ *
+ * ARM ARM (B2.7.2) requires `dsb sy` before `isb` when modifying
+ * SCTLR_ELx.M so the system register write is observable before the
+ * pipeline is re-synchronized. Matches the MMU-enable sequence used
+ * earlier in this file.
+ */
+ mrs x0, SCTLR_EL2
+ bic x0, x0, #(1 << 0) /* M */
+ bic x0, x0, #(1 << 2) /* C */
+ bic x0, x0, #(1 << 12) /* I */
+ msr SCTLR_EL2, x0
+ dsb sy
+ isb
+
+ ret
+#endif /* EL2_HYPERVISOR */
+
.end
diff --git a/src/sdhci.c b/src/sdhci.c
index ba82947228..f54161d027 100644
--- a/src/sdhci.c
+++ b/src/sdhci.c
@@ -581,6 +581,7 @@ static uint32_t sdhci_get_response_bits(int from, int count)
/* voltage: 0=off or SDHCI_SRS10_BVS_[X_X]V */
static int sdcard_power_init_seq(uint32_t voltage)
{
+ int retries;
/* Set power to specified voltage */
int status = sdhci_set_power(voltage);
#ifdef DEBUG_SDHCI
@@ -590,9 +591,24 @@ static int sdcard_power_init_seq(uint32_t voltage)
SDHCI_REG(SDHCI_SRS09), SDHCI_REG(SDHCI_SRS10),
SDHCI_REG(SDHCI_SRS11), SDHCI_REG(SDHCI_SRS12));
#endif
- if (status == 0) {
- /* send CMD0 (go idle) to reset card */
+ if (status != 0)
+ return status;
+ /* SD spec requires >= 1ms after power stabilizes before CMD0. */
+ udelay(1000);
+ /* Some cards and the ZynqMP Arasan controller need more settling
+ * time after the slot-type change + soft reset in sdhci_platform_init().
+ * Use a retry loop: if CMD0 fails, wait and retry (self-calibrating). */
+ for (retries = 0; retries < 10; retries++) {
status = sdhci_cmd(MMC_CMD0_GO_IDLE, 0, SDHCI_RESP_NONE);
+ if (status == 0)
+ break;
+ udelay(10000); /* 10ms between retries */
+ }
+ if (status != 0) {
+ wolfBoot_printf("SD: CMD0 failed after %d retries\n", retries);
+ }
+ else if (retries > 0) {
+ wolfBoot_printf("SD: CMD0 succeeded after %d retries\n", retries);
}
if (status == 0) {
/* send the operating conditions command */
@@ -1273,7 +1289,22 @@ static int sdhci_transfer(int dir, uint32_t cmd_index, uint32_t block_addr,
#endif /* !SDHCI_SDMA_DISABLED */
}
else {
- /* Blocking mode - buffer ready flag differs for read vs write */
+ /* PIO (Programmed I/O) mode — reads/writes data word-by-word via
+ * the SRS08 data port register.
+ *
+ * CAUTION: On Arasan SDHCI v3.0 (ZynqMP, Versal), multi-block PIO
+ * reads (CMD18) have a known race condition under compiler
+ * optimization (-Os/-O2). After reading one block, the BRR (Buffer
+ * Read Ready) flag in SRS12 may still be set from the previous
+ * block when the outer loop re-checks it. The optimized code
+ * re-polls so quickly that BRR has not yet auto-cleared, causing
+ * the next 512-byte read from SRS08 to return stale/partial data.
+ * This corrupts the loaded firmware image.
+ *
+ * Workaround: Set SDHCI_DMA_THRESHOLD low (default 4KB) so that
+ * multi-block reads use SDMA instead of this PIO path. The eMMC
+ * path manually clears BRR between blocks (W1C write below),
+ * which also avoids the race. */
uint32_t buf_ready_flag = (dir == SDHCI_DIR_READ) ?
SDHCI_SRS12_BRR : SDHCI_SRS12_BWR;
@@ -1387,6 +1418,11 @@ int sdhci_init(void)
/* Call platform-specific initialization (clocks, resets, pin mux) */
sdhci_platform_init();
+ /* Allow controller to settle after platform init (slot type change,
+ * soft reset, clock configuration). Without this, the controller may
+ * not be ready to accept register writes on some platforms. */
+ udelay(1000); /* 1ms */
+
/* Reset the host controller */
sdhci_reg_or(SDHCI_HRS00, SDHCI_HRS00_SWR);
/* Bit will clear when reset is done */
@@ -1482,6 +1518,9 @@ int sdhci_init(void)
/* Setup 400khz starting clock */
sdhci_set_clock(SDHCI_CLK_400KHZ);
+ /* Allow clock to stabilize before issuing first command */
+ udelay(1000); /* 1ms */
+
#ifdef DISK_EMMC
/* Run full eMMC card initialization */
status = emmc_card_full_init();
@@ -1507,15 +1546,21 @@ int sdhci_init(void)
}
#ifdef DEBUG_SDHCI
- {
- const char *card_type;
-#ifdef DISK_EMMC
- card_type = "eMMC";
-#else
- card_type = "SD";
-#endif
- wolfBoot_printf("sdhci_init: %s status: %d\n", card_type, status);
+ if (status == 0) {
+ wolfBoot_printf("SDHCI: DMA (threshold: %dKB, buf boundary: %dKB)\n",
+ SDHCI_DMA_THRESHOLD / 1024,
+ (4 << ((SDHCI_DMA_BUFF_BOUNDARY >> 12) & 0x7))
+ );
}
+
+ wolfBoot_printf("SDHCI: %s init, status %d\n",
+ #ifdef DISK_EMMC
+ "eMMC"
+ #else
+ "SD"
+ #endif
+ , status
+ );
#endif
return status;
diff --git a/src/update_disk.c b/src/update_disk.c
index 088dd328e3..4254b17118 100644
--- a/src/update_disk.c
+++ b/src/update_disk.c
@@ -353,6 +353,7 @@ void RAMFUNCTION wolfBoot_start(void)
pB_ver_u = (uint32_t)pB_ver;
wolfBoot_printf("Versions, A:%u B:%u\r\n", pA_ver_u, pB_ver_u);
+ wolfBoot_printf("Load block size: %dKB\r\n", DISK_BLOCK_SIZE / 1024);
max_ver = (pB_ver_u > pA_ver_u) ? pB_ver_u : pA_ver_u;
/* Choose partition with higher version */
diff --git a/tools/scripts/nxp_t1040/t1040_debug.cmm b/tools/scripts/nxp_t1040/t1040_debug.cmm
index 4ed918006c..161025835e 100644
--- a/tools/scripts/nxp_t1040/t1040_debug.cmm
+++ b/tools/scripts/nxp_t1040/t1040_debug.cmm
@@ -18,8 +18,10 @@
; 3. wolfBoot runs from DDR (0x7FF00000)
; ------------------------------------------------------------------------------
-; Base directory for wolfBoot build output (adjust to match your build path)
-&basedir="/home/davidgarske/GitHub/wolfboot-alt"
+; Base directory for wolfBoot build output. "." means the TRACE32 current
+; working directory - set this to your wolfBoot checkout path if running
+; TRACE32 from elsewhere (e.g. "C:/src/wolfBoot" or "/home/user/wolfBoot").
+&basedir="."
PRINT "========================================"
PRINT "T1040 wolfBoot Debug Session"
diff --git a/tools/scripts/nxp_t1040/t1040_flash.cmm b/tools/scripts/nxp_t1040/t1040_flash.cmm
index e7163fb345..cac80d7e3f 100644
--- a/tools/scripts/nxp_t1040/t1040_flash.cmm
+++ b/tools/scripts/nxp_t1040/t1040_flash.cmm
@@ -25,11 +25,15 @@
; 0xEFFFC000: Stage 1 loader (16 KB, includes reset vector)
; ------------------------------------------------------------------------------
-; Base directory for wolfBoot build output (adjust to match your build path)
-&basedir="/home/davidgarske/GitHub/wolfboot-alt"
-
-; Persistent backup directory (survives make clean)
-&backupdir="/home/davidgarske/Projects/NXP/t1040rdb"
+; Base directory for wolfBoot build output. "." means the TRACE32 current
+; working directory - set this to your wolfBoot checkout path if running
+; TRACE32 from elsewhere (e.g. "C:/src/wolfBoot" or "/home/user/wolfBoot").
+&basedir="."
+
+; Persistent backup directory (survives make clean). Leave as "." to keep
+; artifacts alongside the build tree, or point elsewhere to preserve
+; signed/backup images across source cleans.
+&backupdir="."
; FLASH Number of banks
; The JS28F00AM29EWHA is a single 128MB NOR chip. CPLD virtual banking
diff --git a/tools/scripts/zcu102/zcu102-ca53-qspi.cmm b/tools/scripts/zcu102/zcu102-ca53-qspi.cmm
old mode 100755
new mode 100644
index cb19d2fc5c..e6049d7f73
--- a/tools/scripts/zcu102/zcu102-ca53-qspi.cmm
+++ b/tools/scripts/zcu102/zcu102-ca53-qspi.cmm
@@ -1,225 +1,241 @@
-; Zynq UltraScale+ ZCU102 Quad SPI FLASH Programming Script
-;
-; S(D)RAM : 0xFFFC1000
-; Generic Quad-SPI Controller base: 0xFF0F0000
-; FLASH: MT25QU512 (Micron)
-;
-; Based on Lauterbach TRACE32 ZCU102 QSPI demo scripts.
-
-LOCAL &arg1
-ENTRY &arg1
-&arg1=STRing.UPpeR("&arg1") // for example "PREPAREONLY"
-&dualqspi=1 ; dual(1) or single(0)
-
-; Adjust to your TRACE32 installation path
-LOCAL &pdd
-&pdd="C:/T32/demo/arm"
-
-SYStem.RESet
-SYStem.CPU ZYNQ-ULTRASCALE+-APU
-SYStem.MemAccess DAP
-CORE.ASSIGN 1.
-ETM.OFF
-Trace.DISable
-
-;
-; this sequence forces the SoC to use BOOTMODE=JTAG
-; a SRST is issued using the debug logic
-; we use the first A53 for flash programming
-SYStem.Option ResBreak OFF
-SYStem.Option EnReset OFF
-SYStem.Option TRST OFF
-DO &pdd/hardware/zynq_ultrascale/scripts/zynq-ultrascale_reset.cmm OVERRIDE_BOOTMODE=0x0
-SYStem.Mode Prepare
-DO &pdd/hardware/zynq_ultrascale/scripts/zynq-ultrascale_kick_bootcore.cmm "A53_X64"
-SYStem.Mode.Attach
-Break.direct
-;
-
-SYStem.JtagClock CTCK 24MHz
-
-; --------------------------------------------------------------------------------
-; peripheral initializations
-
-//Pin muxing MIO configuration MIO[0:12]
-
-Data.Set A:0xFF5E0068 %LE %Long 0x01010c00 ;QSPI_REF_CTRL
-
-&addr=0xFF180000
-RePeaT 13.
-(
- Data.Set A:&addr %LE %Long 0x2
- &addr=&addr+0x4
-)
-
-Data.Set A:0xFF180204 %LE %Long 0x2240000
-
-Data.Set A:0xFF5E0238 %Long 0x17FFFE
-
-IF &dualqspi==1
- GOSUB READ_ID_TEST_DUAL
-ELSE
- GOSUB READ_ID_TEST
-
-Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
-Data.Set A:0xFF0F010C %Long 0x0FBE ; Interrupt disable register
-Data.Set A:0xFF0F0128 %Long 0x01 ; thres hold
-Data.Set A:0xFF0F012C %Long 0x01 ; thres hold
-Data.Set A:0xFF0F0104 %Long 0x0FBE ; Interrupt status register
-
-Data.Set A:0xFF0F014C %Long 0x7 ; reset tx fifo and gen_fifo..
-WAIT 100.ms
-Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
-
-;The flash dualport works only under the non-secure mode for the ZYNQ-ULTRASCALE+
-SYStem.MemAccess DAP
-Register.Set NS 1.
-Register.Set M 0x5 ;EL1h
-
-; --------------------------------------------------------------------------------
-; Flash Declaration
-
-Break.RESet
-
-FLASHFILE.RESet
-FLASHFILE.CONFIG 0xFF0F0000
-
-IF &dualqspi==1
- FLASHFILE.TARGET 0xFFFC1000++0x2FFF E:0xFFFC4000++0x27FF &pdd/flash/word/spiw4b64_zynqultra.bin /KEEP /STACKSIZE 0x400 /DUALPORT
-ELSE
- FLASHFILE.TARGET 0xFFFC1000++0x2FFF E:0xFFFC4000++0x27FF &pdd/flash/byte/spi4b64fs_zynqultra.bin /KEEP /STACKSIZE 0x400 /DUALPORT
-
-FLASHFILE.GETID
-
-//End of the test prepareonly
-IF "&arg1"=="PREPAREONLY"
- ENDDO
-
-; Save Whole Flash
-;FLASHFILE.SAVE "flash_dump.bin" 0x0++0x1FFFFFFF
-
-FLASHFILE.Create 0x0--0x1FFFFFFF 0x20000 Byte
-
-; Flash BOOT.BIN (wolfBoot) at offset 0x0
-DIALOG.YESNO "Flash wolfBoot BOOT.BIN now?"
-ENTRY &programnow
-if &programnow
-(
- FLASHFILE.ReProgram ALL
- FLASHFILE.Load "BOOT.BIN" 0x0
- FLASHFILE.ReProgram off
-)
-
-; Flash signed application image at partition offset
-DIALOG.YESNO "Flash signed application image now?"
-ENTRY &programnow
-if &programnow
-(
- FLASHFILE.ReProgram ALL
- FLASHFILE.Load "test-app/image_v1_signed.bin" 0x7000000
- FLASHFILE.ReProgram off
-)
-
-ENDDO
-
-; --------------------------------------------------------------------------------
-; Subroutines
-
-READ_ID_TEST:
-(
- Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
- Data.Set A:0xFF0F010C %Long 0x0FBE ;Interrupt disable register
- Data.Set A:0xFF0F0128 %Long 0x01 ;thres hold
- Data.Set A:0xFF0F012C %Long 0x01 ;thres hold
- Data.Set A:0xFF0F0104 %Long 0x0FBE ;Interrupt status register
-
- ;read A:0xFF0F0100 -> 0x0
-
- Data.Set A:0xFF0f014c %Long 0x7 ; reset tx fifo and gen_fifo..
- WAIT 100.ms
-
- Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
-
- &dat_xfer=0x1<<8.
- &spimode=0x1<<10.
- &cs_lower=0x1<<12.
- &cs_upper=0x1<<13.
- &bus_lower=0x1<<14.
- &bus_upper=0x2<<14.
- &bus_both=0x3<<14.
- &transmit=0x1<<16.
- &receive=0x1<<17.
- &stripe=0x1<<18.
-
- Data.Set A:0xFF0F0114 %Long 0x1 ;spi enable register , cs low
-
- Data.Set A:0xFF0F011C %Long 0x9f ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
- PRINT "read 0x" Data.Long(A:0xff0f0120) " (dummy)"
-
- Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
- PRINT "read 0x" Data.Long(A:0xff0f0120) " (manufacture id)"
-
- Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
- PRINT "read 0x" Data.Long(A:0xff0f0120) " (device id)"
-
- Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
- PRINT "read 0x" Data.Long(A:0xff0f0120)
-
- Data.Set A:0xFF0F0114 %Long 0x0 ;spi disable, cs high
-
- RETURN
-)
-
-
-READ_ID_TEST_DUAL:
-(
- Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
- Data.Set A:0xFF0F010C %Long 0x0FBE ;Interrupt disable register
- Data.Set A:0xFF0F0128 %Long 0x01 ;thres hold
- Data.Set A:0xFF0F012C %Long 0x01 ;thres hold
- Data.Set A:0xFF0F0104 %Long 0x0FBE ;Interrupt status register
-
- ;read A:0xFF0F0100 -> 0x0
-
- Data.Set A:0xFF0f014c %Long 0x7 ; reset tx fifo and gen_fifo..
- WAIT 100.ms
-
- Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
-
- &dat_xfer=0x1<<8.
- &spimode=0x1<<10.
- &cs_lower=0x1<<12.
- &cs_upper=0x1<<13.
- &bus_lower=0x1<<14.
- &bus_upper=0x2<<14.
- &bus_both=0x3<<14.
- &transmit=0x1<<16.
- &receive=0x1<<17.
- &stripe=0x1<<18.
-
- Data.Set A:0xFF0F0114 %Long 0x1 ;spi enable register , cs low
-
- Data.Set A:0xFF0F011C %Long 0x00009f9f ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
- PRINT "read 0x" Data.Long(A:0xff0f0120)
-
- Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
- PRINT "read 0x" Data.Long(A:0xff0f0120)
-
- Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
- PRINT "read 0x" Data.Long(A:0xff0f0120)
-
- Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
- Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
- PRINT "read 0x" Data.Long(A:0xff0f0120)
-
- Data.Set A:0xFF0F0114 %Long 0x0 ;spi disable, cs high
-
- RETURN
-)
+; Zynq UltraScale+ ZCU102 Quad SPI FLASH Programming Script
+;
+; S(D)RAM : 0xFFFC1000
+; Generic Quad-SPI Controller base: 0xFF0F0000
+; FLASH: MT25QU512 (Micron)
+;
+; Based on Lauterbach TRACE32 ZCU102 QSPI demo scripts.
+
+LOCAL &arg1
+ENTRY &arg1
+&arg1=STRing.UPpeR("&arg1") // for example "PREPAREONLY"
+&dualqspi=1 ; dual(1) or single(0)
+
+; TRACE32 demo scripts directory.
+; "~~" is the TRACE32 installation path - expands to the correct location
+; on Windows (e.g. C:/T32), Linux (e.g. /opt/t32), and macOS automatically,
+; so no per-platform edit is needed. Override only if the demo scripts live
+; somewhere non-standard:
+; &pdd="C:/T32/demo/arm" ; Windows default install
+; &pdd="/opt/t32/demo/arm" ; Linux default install
+LOCAL &pdd
+&pdd="~~/demo/arm"
+
+SYStem.RESet
+SYStem.CPU ZYNQ-ULTRASCALE+-APU
+SYStem.MemAccess DAP
+CORE.ASSIGN 1.
+ETM.OFF
+Trace.DISable
+
+;
+; this sequence forces the SoC to use BOOTMODE=JTAG
+; a SRST is issued using the debug logic
+; we use the first A53 for flash programming
+SYStem.Option ResBreak OFF
+SYStem.Option EnReset OFF
+SYStem.Option TRST OFF
+DO &pdd/hardware/zynq_ultrascale/scripts/zynq-ultrascale_reset.cmm OVERRIDE_BOOTMODE=0x0
+SYStem.Mode Prepare
+DO &pdd/hardware/zynq_ultrascale/scripts/zynq-ultrascale_kick_bootcore.cmm "A53_X64"
+SYStem.Mode.Attach
+Break.direct
+;
+
+SYStem.JtagClock CTCK 24MHz
+
+; --------------------------------------------------------------------------------
+; peripheral initializations
+
+//Pin muxing MIO configuration MIO[0:12]
+
+Data.Set A:0xFF5E0068 %LE %Long 0x01010c00 ;QSPI_REF_CTRL
+
+&addr=0xFF180000
+RePeaT 13.
+(
+ Data.Set A:&addr %LE %Long 0x2
+ &addr=&addr+0x4
+)
+
+Data.Set A:0xFF180204 %LE %Long 0x2240000
+
+Data.Set A:0xFF5E0238 %Long 0x17FFFE
+
+IF &dualqspi==1
+ GOSUB READ_ID_TEST_DUAL
+ELSE
+ GOSUB READ_ID_TEST
+
+Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
+Data.Set A:0xFF0F010C %Long 0x0FBE ; Interrupt disable register
+Data.Set A:0xFF0F0128 %Long 0x01 ; thres hold
+Data.Set A:0xFF0F012C %Long 0x01 ; thres hold
+Data.Set A:0xFF0F0104 %Long 0x0FBE ; Interrupt status register
+
+Data.Set A:0xFF0F014C %Long 0x7 ; reset tx fifo and gen_fifo..
+WAIT 100.ms
+Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
+
+;The flash dualport works only under the non-secure mode for the ZYNQ-ULTRASCALE+
+SYStem.MemAccess DAP
+Register.Set NS 1.
+Register.Set M 0x5 ;EL1h
+
+; --------------------------------------------------------------------------------
+; Flash Declaration
+
+Break.RESet
+
+FLASHFILE.RESet
+FLASHFILE.CONFIG 0xFF0F0000
+
+IF &dualqspi==1
+ FLASHFILE.TARGET 0xFFFC1000++0x2FFF E:0xFFFC4000++0x27FF &pdd/flash/word/spiw4b64_zynqultra.bin /KEEP /STACKSIZE 0x400 /DUALPORT
+ELSE
+ FLASHFILE.TARGET 0xFFFC1000++0x2FFF E:0xFFFC4000++0x27FF &pdd/flash/byte/spi4b64fs_zynqultra.bin /KEEP /STACKSIZE 0x400 /DUALPORT
+
+FLASHFILE.GETID
+
+//End of the test prepareonly
+IF "&arg1"=="PREPAREONLY"
+ ENDDO
+
+; Save Whole Flash
+;FLASHFILE.SAVE "flash_dump.bin" 0x0++0x1FFFFFFF
+
+FLASHFILE.Create 0x0--0x1FFFFFFF 0x20000 Byte
+
+; NOTE ON LARGE IMAGES (>~100 MB):
+; FLASHFILE.Load buffers the full source file into TRACE32 temporary memory
+; before any programming. Many installations cap that pool at ~128 MB, so a
+; single Load of a file larger than ~100 MB will fail with
+; "FATAL ERROR: out of temporary memory"
+; Workaround: split the source externally (e.g. `dd bs=1M count=N` and
+; `dd bs=1M skip=N`) and issue each Load in its own
+; FLASHFILE.ReProgram ALL / off bracket (the temp pool is released between
+; brackets - a single bracket spanning multiple large Loads still exhausts
+; memory on the second Load).
+
+; Flash BOOT.BIN (wolfBoot) at offset 0x0
+DIALOG.YESNO "Flash wolfBoot BOOT.BIN now?"
+ENTRY &programnow
+if &programnow
+(
+ FLASHFILE.ReProgram ALL
+ FLASHFILE.Load "BOOT.BIN" 0x0
+ FLASHFILE.ReProgram off
+)
+
+; Flash signed application image at partition offset
+DIALOG.YESNO "Flash signed application image now?"
+ENTRY &programnow
+if &programnow
+(
+ FLASHFILE.ReProgram ALL
+ FLASHFILE.Load "test-app/image_v1_signed.bin" 0x7000000
+ FLASHFILE.ReProgram off
+)
+
+ENDDO
+
+; --------------------------------------------------------------------------------
+; Subroutines
+
+READ_ID_TEST:
+(
+ Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
+ Data.Set A:0xFF0F010C %Long 0x0FBE ;Interrupt disable register
+ Data.Set A:0xFF0F0128 %Long 0x01 ;thres hold
+ Data.Set A:0xFF0F012C %Long 0x01 ;thres hold
+ Data.Set A:0xFF0F0104 %Long 0x0FBE ;Interrupt status register
+
+ ;read A:0xFF0F0100 -> 0x0
+
+ Data.Set A:0xFF0f014c %Long 0x7 ; reset tx fifo and gen_fifo..
+ WAIT 100.ms
+
+ Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
+
+ &dat_xfer=0x1<<8.
+ &spimode=0x1<<10.
+ &cs_lower=0x1<<12.
+ &cs_upper=0x1<<13.
+ &bus_lower=0x1<<14.
+ &bus_upper=0x2<<14.
+ &bus_both=0x3<<14.
+ &transmit=0x1<<16.
+ &receive=0x1<<17.
+ &stripe=0x1<<18.
+
+ Data.Set A:0xFF0F0114 %Long 0x1 ;spi enable register , cs low
+
+ Data.Set A:0xFF0F011C %Long 0x9f ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
+ PRINT "read 0x" Data.Long(A:0xff0f0120) " (dummy)"
+
+ Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
+ PRINT "read 0x" Data.Long(A:0xff0f0120) " (manufacture id)"
+
+ Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
+ PRINT "read 0x" Data.Long(A:0xff0f0120) " (device id)"
+
+ Data.Set A:0xFF0F011C %Long 0x00 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &transmit|&receive|&bus_lower|&cs_lower|&spimode|&dat_xfer|0x1
+ PRINT "read 0x" Data.Long(A:0xff0f0120)
+
+ Data.Set A:0xFF0F0114 %Long 0x0 ;spi disable, cs high
+
+ RETURN
+)
+
+READ_ID_TEST_DUAL:
+(
+ Data.Set A:0xFF0F0144 %LE %Long 1 ;enable GQSPI_SEL
+ Data.Set A:0xFF0F010C %Long 0x0FBE ;Interrupt disable register
+ Data.Set A:0xFF0F0128 %Long 0x01 ;thres hold
+ Data.Set A:0xFF0F012C %Long 0x01 ;thres hold
+ Data.Set A:0xFF0F0104 %Long 0x0FBE ;Interrupt status register
+
+ ;read A:0xFF0F0100 -> 0x0
+
+ Data.Set A:0xFF0f014c %Long 0x7 ; reset tx fifo and gen_fifo..
+ WAIT 100.ms
+
+ Data.Set A:0xFF0F0100 %Long 0x00080010 ;Config register, clk speed control[5:3] , mode==00 [31:30] I/O mode
+
+ &dat_xfer=0x1<<8.
+ &spimode=0x1<<10.
+ &cs_lower=0x1<<12.
+ &cs_upper=0x1<<13.
+ &bus_lower=0x1<<14.
+ &bus_upper=0x2<<14.
+ &bus_both=0x3<<14.
+ &transmit=0x1<<16.
+ &receive=0x1<<17.
+ &stripe=0x1<<18.
+
+ Data.Set A:0xFF0F0114 %Long 0x1 ;spi enable register , cs low
+
+ Data.Set A:0xFF0F011C %Long 0x00009f9f ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
+ PRINT "read 0x" Data.Long(A:0xff0f0120)
+
+ Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
+ PRINT "read 0x" Data.Long(A:0xff0f0120)
+
+ Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
+ PRINT "read 0x" Data.Long(A:0xff0f0120)
+
+ Data.Set A:0xFF0F011C %Long 0x00000000 ; tx, data transfer
+ Data.Set A:0xFF0F0140 %Long &stripe|&transmit|&receive|&bus_both|&cs_lower|&cs_upper|&spimode|&dat_xfer|0x4 ; tx,4 byte data transfer
+ PRINT "read 0x" Data.Long(A:0xff0f0120)
+
+ Data.Set A:0xFF0F0114 %Long 0x0 ;spi disable, cs high
+
+ RETURN
+)
diff --git a/tools/scripts/zcu102/zcu102-debug-wolfboot.cmm b/tools/scripts/zcu102/zcu102-debug-wolfboot.cmm
index c0bc0cbabc..2e0edd5951 100644
--- a/tools/scripts/zcu102/zcu102-debug-wolfboot.cmm
+++ b/tools/scripts/zcu102/zcu102-debug-wolfboot.cmm
@@ -22,7 +22,8 @@ Trace.DISable
SYStem.Mode Prepare
;GOSUB DisableWatchdog
-;DO "C:/T32/demo/arm/hardware/zynq_ultrascale/scripts/zynq-ultrascale_kick_bootcore.cmm" A53_X64
+; "~~" expands to the TRACE32 install dir on any host OS (Windows/Linux/macOS):
+;DO "~~/demo/arm/hardware/zynq_ultrascale/scripts/zynq-ultrascale_kick_bootcore.cmm" A53_X64
SYStem.Mode.Attach