diff --git a/config/examples/versal_vmk180.config b/config/examples/versal_vmk180.config index 1f723d78..2d56e0f2 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -27,6 +27,10 @@ DEBUG?=1 DEBUG_SYMBOLS=1 DEBUG_UART=1 +# Boot Benchmarking (optional): +# Enables timing of boot operations (flash read, integrity, signature). +BOOT_BENCHMARK?=1 + VTOR?=1 CORTEX_M0?=0 NO_ASM?=0 diff --git a/docs/Targets.md b/docs/Targets.md index f4f47e88..d0f25b2d 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -1981,7 +1981,7 @@ qemu-system-aarch64 -machine xlnx-zcu102 -cpu cortex-a53 -serial stdio -display ## Versal Gen 1 VMK180 -AMD Versal Prime VM1802 ACAP - Dual ARM Cortex-A72 (VMK180 Evaluation Board) +AMD Versal Prime Series VMK180 Evaluation Kit - Versal Prime XCVM1802-2MSEVSVA2197 Adaptive SoC - Dual ARM Cortex-A72 wolfBoot replaces U-Boot in the Versal boot flow: ``` @@ -2080,6 +2080,29 @@ Flash `BOOT.BIN` to QSPI flash using one of the following methods: sf write ${loadaddr} 0 ${filesize} ``` +### QSPI Flash + +VMK180 uses dual parallel MT25QU01GBBB flash (128MB each, 256MB total). The QSPI driver supports: +- DMA mode (default) or IO polling mode (`GQSPI_MODE_IO`) +- Quad SPI (4-bit) for faster reads +- 4-byte addressing for full flash access +- Hardware striping for dual parallel operation +- 75MHz default clock (configurable via `GQSPI_CLK_DIV`) + +### Building and Signing Test Application + +```sh +# Build and sign the test application +make test-app/image.bin +make test-app/image_v1_signed.bin +``` + +The signed test application will be at `test-app/image_v1_signed.bin`. + +### Flashing Test Application + +After flashing `BOOT.BIN` to QSPI offset 0x0, flash the signed test app to the boot partition at offset `0x800000` using your preferred method. + ### Example Boot Output ``` @@ -2088,11 +2111,30 @@ wolfBoot Secure Boot - AMD Versal ======================================== Current EL: 2 Timer Freq: 99999904 Hz -ext_flash_read: STUB -ext_flash_read: STUB -Versions: Boot 0, Update 0 -No valid image found! -wolfBoot: PANIC! +QSPI: Lower ID: 20 BB 21 +QSPI: Upper ID: 20 BB 21 +QSPI: 75MHz, Quad mode, DMA +Versions: Boot 1, Update 0 +Trying Boot partition at 0x800000 +Loading header 512 bytes from 0x800000 to 0xFFFFE00 +Loading image 664 bytes from 0x800200 to 0x10000000...done +Boot partition: 0xFFFFE00 (sz 664, ver 0x1, type 0x601) +Checking integrity...done +Verifying signature...done +Successfully selected image in part: 0 +Firmware Valid +Loading elf at 0x10000000 +Invalid elf, falling back to raw binary +Loading DTB (size 24894) from 0x1000 to RAM at 0x1000 +Booting at 0x10000000 + +=========================================== + wolfBoot Test Application - AMD Versal +=========================================== + +Application running successfully! + +Entering idle loop... ``` diff --git a/hal/versal.c b/hal/versal.c index c647302c..ee5b03c6 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -272,6 +272,21 @@ void hal_delay_us(uint32_t us) ; } +/** + * Get current time in microseconds (for benchmarking) + */ +uint64_t hal_get_timer_us(void) +{ + uint64_t cntpct = timer_get_count(); + uint64_t cntfrq = timer_get_freq(); + + if (cntfrq == 0) + cntfrq = TIMER_CLK_FREQ; + + /* Convert to microseconds: (count * 1000000) / freq */ + return (cntpct * 1000000ULL) / cntfrq; +} + /* ============================================================================ * QSPI Flash Driver (GQSPI) @@ -473,7 +488,9 @@ static void flush_dcache_range(uintptr_t start, uintptr_t end) __asm__ volatile("dsb sy" : : : "memory"); } -/* Wait for DMA completion */ +/* Wait for DMA completion + * Returns: 0 on success, -1 on timeout + */ static int qspi_dma_wait(void) { uint32_t timeout = GQSPIDMA_TIMEOUT_TRIES; @@ -483,6 +500,8 @@ static int qspi_dma_wait(void) if (timeout == 0) { QSPI_DEBUG_PRINTF("QSPI: DMA timeout\n"); + /* Clear any pending interrupts */ + GQSPIDMA_ISR = GQSPIDMA_ISR_ALL_MASK; return -1; } @@ -782,10 +801,12 @@ static int qspi_transfer_qread_dma(QspiDev_t *dev, const uint8_t *cmd, uint32_t /* DMA RX Phase */ if (ret == 0 && rxLen > 0) { - uint32_t remaining = rxLen; + uint32_t remaining; uint32_t xferSz; - /* Check alignment - DMA requires cache-line aligned buffer */ + /* Check alignment - DMA requires cache-line aligned buffer. + * If unaligned or not a multiple of 4 bytes, use temp buffer. + * CRITICAL: GenFIFO transfer size must match DMA size! */ if (((uintptr_t)rxData & (GQSPI_DMA_ALIGN - 1)) || (rxLen & 3)) { /* Use temp buffer for unaligned data */ dmaPtr = dma_tmpbuf; @@ -799,6 +820,9 @@ static int qspi_transfer_qread_dma(QspiDev_t *dev, const uint8_t *cmd, uint32_t dmaLen = rxLen; } + /* GenFIFO must request the same number of bytes as DMA expects */ + remaining = dmaLen; + /* Setup DMA destination */ GQSPIDMA_DST = ((uintptr_t)dmaPtr & 0xFFFFFFFFUL); GQSPIDMA_DST_MSB = ((uintptr_t)dmaPtr >> 32); @@ -839,7 +863,7 @@ static int qspi_transfer_qread_dma(QspiDev_t *dev, const uint8_t *cmd, uint32_t /* Invalidate cache after DMA */ flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen); - /* Copy from temp buffer if needed */ + /* Copy from temp buffer if needed (only copy requested bytes) */ if (ret == 0 && useTemp) { memcpy(rxData, dmaPtr, rxLen); } @@ -1224,11 +1248,12 @@ static void qspi_init(void) GQSPI_ISR = GQSPI_IXR_ALL_MASK; dsb(); - /* Preserve PLM's CFG but switch to IO mode for our transfers + /* Preserve PLM's CFG but set IO mode for initial commands (ID read, etc.) * PLM: 0xA0080010 = DMA mode | manual start | WP_HOLD | CLK_POL - * Key: Keep manual start mode (bit 29) and clock settings */ + * Key: Keep manual start mode (bit 29) and clock settings + * Note: qspi_transfer_qread_dma() will switch to DMA mode for reads */ cfg = (cfg & ~GQSPI_CFG_MODE_EN_MASK); /* Clear mode bits */ - cfg |= GQSPI_CFG_MODE_EN_IO; /* Set IO mode */ + cfg |= GQSPI_CFG_MODE_EN_IO; /* Set IO mode for init */ GQSPI_CFG = cfg; dsb(); @@ -1237,6 +1262,18 @@ static void qspi_init(void) GQSPI_RX_THRESH = 1; GQSPI_GF_THRESH = 16; +#ifndef GQSPI_MODE_IO + /* Initialize DMA controller - this was missing compared to zynq.c! + * Without this, DMA transfers can hang or timeout because the DMA + * controller is in an undefined state after PLM handoff. + */ + GQSPIDMA_CTRL = GQSPIDMA_CTRL_DEF; + GQSPIDMA_CTRL2 = GQSPIDMA_CTRL2_DEF; + GQSPIDMA_ISR = GQSPIDMA_ISR_ALL_MASK; /* Clear all pending interrupts */ + GQSPIDMA_IER = GQSPIDMA_ISR_ALL_MASK; /* Enable all interrupts */ + dsb(); +#endif + QSPI_DEBUG_PRINTF("QSPI: After config - CFG=0x%08x\n", GQSPI_CFG); /* Configure device for single flash (lower) first */ @@ -1353,9 +1390,35 @@ void hal_prepare_boot(void) } #endif - /* Memory barriers before jumping to application */ - dsb(); - isb(); + /* Clean and invalidate caches for the loaded application. + * The application was written to RAM via D-cache, but the CPU will + * fetch instructions via I-cache from main memory. We must: + * 1. Clean D-cache (flush dirty data to memory) + * 2. Invalidate I-cache (ensure fresh instruction fetch) + */ + + /* Clean entire D-cache to Point of Coherency */ + __asm__ volatile("dsb sy"); + + /* Clean D-cache for application region (0x10000000, 1MB should be enough) */ + { + uintptr_t addr; + uintptr_t end = 0x10000000 + (1 * 1024 * 1024); + for (addr = 0x10000000; addr < end; addr += 64) { + /* DC CVAC - Clean data cache line by VA to PoC */ + __asm__ volatile("dc cvac, %0" : : "r"(addr)); + } + } + + /* Data synchronization barrier - ensure clean completes */ + __asm__ volatile("dsb sy"); + + /* Invalidate instruction cache to ensure fresh code is fetched */ + __asm__ volatile("ic iallu"); + + /* Ensure cache invalidation completes before jumping */ + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); } #ifdef MMU @@ -1504,9 +1567,14 @@ int ext_flash_read(uintptr_t address, uint8_t *data, int len) return -1; } + QSPI_DEBUG_PRINTF("ext_flash_read: addr=0x%lx len=%d\n", + (unsigned long)address, len); + if (qspiDev.stripe) { /* For dual parallel the address is divided by 2 */ addr /= 2; + QSPI_DEBUG_PRINTF(" stripe mode: flash_addr=0x%lx\n", + (unsigned long)addr); } /* Use Quad Read command (0x6C) with 4-byte address */ @@ -1523,7 +1591,22 @@ int ext_flash_read(uintptr_t address, uint8_t *data, int len) ret = qspi_transfer_qread_dma(&qspiDev, cmd, 5, data, len, GQSPI_DUMMY_READ); #endif - return ret; + /* On DMA timeout, fill buffer with 0xFF to simulate unwritten flash. + * This handles reads to partition trailer areas that haven't been written. + * wolfBoot will see 0xFF (not magic) and handle appropriately. */ + if (ret != 0) { + memset(data, 0xFF, len); + } + + QSPI_DEBUG_PRINTF("ext_flash_read: ret=%d data[0-7]=%02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, + len > 0 ? data[0] : 0, len > 1 ? data[1] : 0, + len > 2 ? data[2] : 0, len > 3 ? data[3] : 0, + len > 4 ? data[4] : 0, len > 5 ? data[5] : 0, + len > 6 ? data[6] : 0, len > 7 ? data[7] : 0); + + /* Return bytes read on success (like zynq.c) */ + return (ret == 0) ? len : ret; } int ext_flash_erase(uintptr_t address, int len) diff --git a/hal/versal.h b/hal/versal.h index 2fc68eda..a927dccf 100644 --- a/hal/versal.h +++ b/hal/versal.h @@ -61,6 +61,21 @@ #endif /* USE_BUILTIN_STARTUP */ +/* ============================================================================ + * Versal-specific Boot Defaults + * ============================================================================ + * SKIP_GIC_INIT: Versal uses GICv3 (not GICv2 like ZynqMP). + * BL31 handles GIC initialization, so skip gicv2_init_secure(). + * BOOT_EL1: wolfBoot runs at EL2, but applications (Linux, test-app) + * expect EL1. Transition from EL2 to EL1 before jumping to app. + */ +#ifndef SKIP_GIC_INIT +#define SKIP_GIC_INIT 1 +#endif +#ifndef BOOT_EL1 +#define BOOT_EL1 1 +#endif + /* ============================================================================ * Memory Map diff --git a/include/hal.h b/include/hal.h index 1804e054..92f04ca0 100644 --- a/include/hal.h +++ b/include/hal.h @@ -53,8 +53,8 @@ void hal_deinit(); void hal_init(void); -#ifdef WOLFBOOT_UPDATE_DISK -/* Timer functions (platform-specific) */ +/* Timer functions (platform-specific, used for benchmarking) */ +#if defined(WOLFBOOT_UPDATE_DISK) || defined(BOOT_BENCHMARK) uint64_t hal_get_timer_us(void); #endif diff --git a/options.mk b/options.mk index a5dc7c46..c97c4d05 100644 --- a/options.mk +++ b/options.mk @@ -604,6 +604,15 @@ endif ifeq ($(NO_QNX),1) CFLAGS+=-D"NO_QNX" endif +ifeq ($(SKIP_GIC_INIT),1) + CFLAGS+=-D"SKIP_GIC_INIT" +endif +ifeq ($(BOOT_EL1),1) + CFLAGS+=-D"BOOT_EL1" +endif +ifeq ($(BOOT_BENCHMARK),1) + CFLAGS+=-D"BOOT_BENCHMARK" +endif ifeq ($(ALLOW_DOWNGRADE),1) CFLAGS+= -D"ALLOW_DOWNGRADE" diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index 9487f84a..fce0081b 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -37,6 +37,16 @@ extern unsigned int _end_data; extern void main(void); extern void gicv2_init_secure(void); +/* SKIP_GIC_INIT - Skip GIC initialization before booting app + * This is needed for: + * - Versal: Uses GICv3, not GICv2. BL31 handles GIC setup. + * - Systems where another bootloader stage handles GIC init + * NO_QNX also implies SKIP_GIC_INIT for backwards compatibility + */ +#if defined(NO_QNX) && !defined(SKIP_GIC_INIT) +#define SKIP_GIC_INIT +#endif + unsigned int current_el(void) { unsigned long el; @@ -44,6 +54,97 @@ unsigned int current_el(void) return (unsigned int)((el >> 2) & 0x3U); } +#if defined(BOOT_EL1) && defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 +/** + * @brief Transition from EL2 to EL1 and jump to application + * + * This function configures the necessary system registers for EL1 operation + * and performs an exception return (ERET) to drop from EL2 to EL1. + * + * Based on ARM Architecture Reference Manual and U-Boot implementation. + * + * @param entry_point Address to jump to in EL1 + * @param dts_addr Device tree address (passed in x0 to application) + */ +static void RAMFUNCTION el2_to_el1_boot(uintptr_t entry_point, uintptr_t dts_addr) +{ + /* 1. Configure timer access for EL1 */ + asm volatile( + "mrs x0, cnthctl_el2\n\t" + "orr x0, x0, #3\n\t" /* EL1PCEN | EL1PCTEN - enable EL1 timer access */ + "msr cnthctl_el2, x0\n\t" + "msr cntvoff_el2, xzr" /* Clear virtual timer offset */ + ::: "x0" + ); + + /* 2. Configure virtual processor ID */ + asm volatile( + "mrs x0, midr_el1\n\t" + "msr vpidr_el2, x0\n\t" + "mrs x0, mpidr_el1\n\t" + "msr vmpidr_el2, x0" + ::: "x0" + ); + + /* 3. Disable coprocessor traps to EL2 */ + asm volatile( + "mov x0, #0x33ff\n\t" /* CPTR_EL2: RES1 bits, no traps */ + "msr cptr_el2, x0\n\t" + "msr hstr_el2, xzr\n\t" /* No traps to EL2 on system registers */ + "mov x0, #(3 << 20)\n\t" /* CPACR_EL1: Full FP/SIMD access */ + "msr cpacr_el1, x0" + ::: "x0" + ); + + /* 4. Initialize SCTLR_EL1 with safe defaults (RES1 bits, MMU/cache off) */ + asm volatile( + "ldr x0, =0x30d00800\n\t" /* RES1 bits: 29,28,23,22,20,11 */ + "msr sctlr_el1, x0" + ::: "x0" + ); + + /* 5. Migrate stack pointer and vector base to EL1 */ + asm volatile( + "mov x0, sp\n\t" + "msr sp_el1, x0\n\t" + "mrs x0, vbar_el2\n\t" + "msr vbar_el1, x0" + ::: "x0" + ); + + /* 6. Configure HCR_EL2 - EL1 is AArch64, no hypervisor calls */ + asm volatile( + "mov x0, #(1 << 31)\n\t" /* RW: EL1 is AArch64 */ + "orr x0, x0, #(1 << 29)\n\t" /* HCD: Disable HVC instruction */ + "msr hcr_el2, x0" + ::: "x0" + ); + + /* 7. Set up SPSR_EL2 for return to EL1h with all interrupts masked */ + asm volatile( + "mov x0, #0x3c4\n\t" /* DAIF masked (0xF<<6) + M[4:0]=0b00100 (EL1h) */ + "msr spsr_el2, x0" + ::: "x0" + ); + + /* 8. Set exception return address and DTB pointer, then ERET */ + asm volatile( + "msr elr_el2, %0\n\t" /* Entry point in ELR_EL2 */ + "mov x0, %1\n\t" /* DTB address in x0 (first arg) */ + "mov x1, xzr\n\t" /* Zero remaining argument registers */ + "mov x2, xzr\n\t" + "mov x3, xzr\n\t" + "eret" + : + : "r"(entry_point), "r"(dts_addr) + : "x0", "x1", "x2", "x3" + ); + + /* Should never reach here */ + __builtin_unreachable(); +} +#endif /* BOOT_EL1 && EL2_HYPERVISOR */ + void boot_entry_C(void) { register unsigned int *dst, *src; @@ -101,6 +202,32 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) hal_dts_fixup((uint32_t*)dts_offset); #endif +#ifndef SKIP_GIC_INIT + /* Initialize GICv2 for Kernel (ZynqMP and similar platforms) + * Skip this for: + * - Versal (uses GICv3, handled by BL31) + * - Platforms where BL31 or another stage handles GIC + */ + gicv2_init_secure(); +#endif + +#if defined(BOOT_EL1) && defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 + /* Transition from EL2 to EL1 before jumping to application. + * This is needed when: + * - Application expects to run at EL1 (e.g., Linux kernel) + * - wolfBoot runs at EL2 (hypervisor mode) + */ + { + #ifdef MMU + uintptr_t dts = (uintptr_t)dts_offset; + #else + uintptr_t dts = 0; + #endif + el2_to_el1_boot((uintptr_t)app_offset, dts); + } +#else + /* Stay at current EL (EL2 or EL3) and jump directly to application */ + /* Set application address via x4 */ asm volatile("mov x4, %0" : : "r"(app_offset)); @@ -111,11 +238,6 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) asm volatile("mov x5, xzr"); #endif -#ifndef NO_QNX - /* Initialize GICv2 for Kernel */ - gicv2_init_secure(); -#endif - /* Zero registers x1, x2, x3 */ asm volatile("mov x3, xzr"); asm volatile("mov x2, xzr"); @@ -126,6 +248,7 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset) /* Unconditionally jump to app_entry at x4 */ asm volatile("br x4"); +#endif /* BOOT_EL1 */ } #ifdef RAM_CODE diff --git a/src/update_ram.c b/src/update_ram.c index ece070fd..8f8f00a9 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -29,6 +29,7 @@ #include "printf.h" #include "wolfboot/wolfboot.h" #include + #ifdef WOLFBOOT_TPM #include "tpm.h" #endif @@ -42,6 +43,19 @@ extern int wolfBoot_get_dts_size(void *dts_addr); extern uint32_t kernel_load_addr; extern uint32_t dts_load_addr; +#ifdef BOOT_BENCHMARK + /* Timing variable for benchmarking - placed at function scope */ + static uint64_t _boot_bench_start; + #define BENCHMARK_START() _boot_bench_start = hal_get_timer_us() + #define BENCHMARK_END(msg) do { \ + uint64_t _elapsed_ms = (hal_get_timer_us() - _boot_bench_start) / 1000;\ + wolfBoot_printf(msg " (%lu ms)\n", (unsigned long)_elapsed_ms); \ + } while(0) +#else + #define BENCHMARK_START() do {} while(0) + #define BENCHMARK_END(msg) wolfBoot_printf(msg "\n") +#endif + #if ((defined(EXT_FLASH) && defined(NO_XIP)) || \ (defined(EXT_ENCRYPTED) && defined(MMU))) && \ !defined(WOLFBOOT_NO_RAMBOOT) @@ -82,8 +96,9 @@ int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst) img_size = wolfBoot_image_size((uint8_t*)dst); /* Read the entire image into RAM */ - wolfBoot_printf("Loading image %d bytes from %p to %p\n", + wolfBoot_printf("Loading image %d bytes from %p to %p...", img_size, src + IMAGE_HEADER_SIZE, dst + IMAGE_HEADER_SIZE); + BENCHMARK_START(); #if defined(EXT_FLASH) && defined(NO_XIP) ret = ext_flash_read((uintptr_t)src + IMAGE_HEADER_SIZE, dst + IMAGE_HEADER_SIZE, img_size); @@ -94,6 +109,7 @@ int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst) #else memcpy(dst + IMAGE_HEADER_SIZE, src + IMAGE_HEADER_SIZE, img_size); #endif + BENCHMARK_END("done"); /* mark image as no longer external */ img->not_ext = 1; @@ -168,12 +184,31 @@ void RAMFUNCTION wolfBoot_start(void) #else ret = wolfBoot_open_image(&os_image, active); #endif - if ( (ret < 0) || - ((ret = wolfBoot_verify_integrity(&os_image) < 0)) || - ((ret = wolfBoot_verify_authenticity(&os_image)) < 0)) { + if (ret < 0) { goto backup_on_failure; + } - } else { + /* Verify image integrity (hash check) */ + wolfBoot_printf("Checking integrity..."); + BENCHMARK_START(); + ret = wolfBoot_verify_integrity(&os_image); + if (ret < 0) { + wolfBoot_printf("FAILED\n"); + goto backup_on_failure; + } + BENCHMARK_END("done"); + + /* Verify image authenticity (signature check) */ + wolfBoot_printf("Verifying signature..."); + BENCHMARK_START(); + ret = wolfBoot_verify_authenticity(&os_image); + if (ret < 0) { + wolfBoot_printf("FAILED\n"); + goto backup_on_failure; + } + BENCHMARK_END("done"); + + { /* Success - integrity and signature valid */ #if !defined(WOLFBOOT_NO_LOAD_ADDRESS) && defined(WOLFBOOT_LOAD_ADDRESS) load_address = (uint32_t*)WOLFBOOT_LOAD_ADDRESS; diff --git a/test-app/AARCH64.ld b/test-app/AARCH64.ld index 18f423fd..0d08f193 100644 --- a/test-app/AARCH64.ld +++ b/test-app/AARCH64.ld @@ -1,42 +1,42 @@ +/* Memory region - address comes from WOLFBOOT_LOAD_ADDRESS, size from WOLFBOOT_TEST_APP_SIZE */ MEMORY { - DDR_MEM(rx) : ORIGIN = 0xa0000, LENGTH = 0x80000000 - 0xa0000 + FLASH (rx) : ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ } -ENTRY(main); + +/* Use _start if boot_arm64_start.S is linked, otherwise fall back to main */ +ENTRY(_start); SECTIONS { .text : { _start_text = .; - KEEP(*(.boot*)) + KEEP(*(.text.startup)) /* Startup code (_start) if boot_arm64_start.S is used */ + KEEP(*(.boot*)) /* Legacy boot section for compatibility */ *(.text*) *(.rodata*) *(.note.*) . = ALIGN(4); _end_text = .; - } > DDR_MEM - .edidx : - { - . = ALIGN(4); - *(.ARM.exidx*) - } > DDR_MEM + } > FLASH .edidx : { . = ALIGN(4); *(.ARM.exidx*) - } > DDR_MEM + } > FLASH .data : { + PROVIDE(_stored_data = .); /* For boot_arm64_start.S data copying */ _start_data = .; KEEP(*(.data*)) . = ALIGN(4); KEEP(*(.ramcode)) . = ALIGN(4); _end_data = .; - } > DDR_MEM + } > FLASH .bss (NOLOAD) : { @@ -48,9 +48,19 @@ SECTIONS _end_bss = .; __bss_end__ = .; _end = .; - } > DDR_MEM + } > FLASH + + .stack (NOLOAD) : + { + . = ALIGN(16); + . = . + 0x4000; /* 16KB stack */ + _stack = .; + } > FLASH + . = ALIGN(4); } -END_STACK = _start_text; +/* Stack pointer - use proper stack section if available, otherwise fall back to text start */ +PROVIDE(__stack = _stack); +PROVIDE(END_STACK = _start_text); /* Legacy compatibility for platforms that don't use stack section */ diff --git a/test-app/Makefile b/test-app/Makefile index d2e39d21..47acba02 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -133,6 +133,10 @@ ifeq ($(ARCH),RISCV) APP_OBJS+=startup_riscv.o vector_riscv.o endif +ifeq ($(ARCH),AARCH64) + APP_OBJS:=boot_arm64_start.o $(APP_OBJS) +endif + ifeq ($(ARCH),RISCV64) APP_OBJS+=startup_riscv.o vector_riscv.o endif @@ -507,6 +511,15 @@ ifeq ($(TARGET),nxp_ls1028a) LSCRIPT_TEMPLATE:=AARCH64-ls1028a.ld endif +ifeq ($(TARGET),versal) + LSCRIPT_TEMPLATE:=AARCH64.ld + LDFLAGS+=-nostdlib +endif + +ifeq ($(TARGET),zynq) + LSCRIPT_TEMPLATE:=AARCH64.ld +endif + ifeq ($(TARGET),pic32ck) APP_OBJS+=../hal/pic32c.o endif diff --git a/test-app/app_versal.c b/test-app/app_versal.c index 7952eacf..2bac3b75 100644 --- a/test-app/app_versal.c +++ b/test-app/app_versal.c @@ -24,21 +24,35 @@ #include #include "wolfboot/wolfboot.h" -/* UART registers (same as in hal/versal.h) */ +/* UART registers for PL011 UART (Versal uses PL011, NOT Cadence UART) + * Register layout from hal/versal.h: + * - Data Register (DR): offset 0x00 + * - Flag Register (FR): offset 0x18 + * - Control Register (CR): offset 0x30 + */ #define VERSAL_UART0_BASE 0xFF000000UL -#define UART_SR_OFFSET 0x2C -#define UART_FIFO_OFFSET 0x30 -#define UART_SR_TXFULL (1UL << 4) -#define UART_SR_TXEMPTY (1UL << 3) +#define UART_DR_OFFSET 0x00 /* Data Register (TX/RX) */ +#define UART_FR_OFFSET 0x18 /* Flag Register */ +#define UART_FR_TXFF (1UL << 5) /* TX FIFO full */ +#define UART_FR_TXFE (1UL << 7) /* TX FIFO empty */ -#define UART_SR (*((volatile uint32_t*)(VERSAL_UART0_BASE + UART_SR_OFFSET))) -#define UART_FIFO (*((volatile uint32_t*)(VERSAL_UART0_BASE + UART_FIFO_OFFSET))) +#define UART_DR (*((volatile uint32_t*)(VERSAL_UART0_BASE + UART_DR_OFFSET))) +#define UART_FR (*((volatile uint32_t*)(VERSAL_UART0_BASE + UART_FR_OFFSET))) + +/* Get current exception level */ +static uint32_t get_current_el(void) +{ + uint64_t current_el; + __asm__ volatile("mrs %0, CurrentEL" : "=r" (current_el)); + return (uint32_t)((current_el >> 2) & 0x3); +} static void uart_tx(uint8_t c) { - while (UART_SR & UART_SR_TXFULL) + /* Wait while TX FIFO is full */ + while (UART_FR & UART_FR_TXFF) ; - UART_FIFO = c; + UART_DR = c; } static void uart_print(const char *s) @@ -52,17 +66,24 @@ static void uart_print(const char *s) void main(void) { + uint32_t el = get_current_el(); + uart_print("\n\n"); uart_print("===========================================\n"); uart_print(" wolfBoot Test Application - AMD Versal\n"); uart_print("===========================================\n\n"); + /* Print current exception level */ + uart_print("Current EL: "); + uart_tx('0' + el); + uart_print("\n"); + uart_print("Application running successfully!\n"); uart_print("\nEntering idle loop...\n"); - /* Wait for transmit to complete */ - while (!(UART_SR & UART_SR_TXEMPTY)) + /* Wait for transmit to complete (TX FIFO empty) */ + while (!(UART_FR & UART_FR_TXFE)) ; /* Idle loop */ diff --git a/test-app/boot_arm64_start.S b/test-app/boot_arm64_start.S new file mode 100644 index 00000000..5f71f25c --- /dev/null +++ b/test-app/boot_arm64_start.S @@ -0,0 +1,67 @@ +/* boot_arm64_start.S + * + * AArch64 (64-bit ARM) boot startup code for test applications + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 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 + */ + +.section .text.startup, "ax" +.global _start +.type _start, @function + +_start: + /* Set up stack pointer */ + ldr x0, =__stack + mov sp, x0 + + /* Copy data section from flash to RAM if needed */ + ldr x0, =_stored_data + ldr x1, =_start_data + ldr x2, =_end_data + cmp x1, x2 + b.ge 2f + cmp x0, x1 + b.eq 2f /* Skip if data already in place */ +1: + ldr x3, [x0], #8 + str x3, [x1], #8 + cmp x1, x2 + b.lt 1b +2: + + /* Clear BSS */ + ldr x0, =__bss_start__ + ldr x1, =__bss_end__ + cmp x0, x1 + b.ge 4f +3: + str xzr, [x0], #8 + cmp x0, x1 + b.lt 3b +4: + + /* Jump to main - never returns */ + bl main + + /* If main returns, loop forever */ +5: + wfi + b 5b + +.size _start, . - _start diff --git a/test-app/versal.ld b/test-app/versal.ld deleted file mode 100644 index 8b41e95a..00000000 --- a/test-app/versal.ld +++ /dev/null @@ -1,57 +0,0 @@ -/* versal.ld - Test application linker script for AMD Versal - * - * Copyright (C) 2025 wolfSSL Inc. - * - * This application is loaded by wolfBoot to WOLFBOOT_LOAD_ADDRESS - */ - -OUTPUT_FORMAT("elf64-littleaarch64") -OUTPUT_ARCH(aarch64) -ENTRY(_start) - -/* Memory regions - application loaded by wolfBoot */ -MEMORY -{ - /* Application loaded at WOLFBOOT_LOAD_ADDRESS */ - RAM (rwx) : ORIGIN = 0x10000000, LENGTH = 64M -} - -SECTIONS -{ - .text : { - _start_text = .; - KEEP(*(.text._start)) - *(.text*) - *(.rodata*) - . = ALIGN(8); - _end_text = .; - } > RAM - - .data : { - . = ALIGN(8); - _start_data = .; - *(.data*) - . = ALIGN(8); - _end_data = .; - } > RAM - - .bss (NOLOAD) : { - . = ALIGN(8); - __bss_start__ = .; - *(.bss*) - *(COMMON) - . = ALIGN(8); - __bss_end__ = .; - } > RAM - - .stack (NOLOAD) : { - . = ALIGN(16); - . = . + 0x4000; - _stack = .; - } > RAM - - _end = .; -} - -PROVIDE(__stack = _stack); - diff --git a/tools/scripts/vmk180/build_flash_qspi.sh b/tools/scripts/vmk180/build_flash_qspi.sh index 6f26a093..2e947118 100755 --- a/tools/scripts/vmk180/build_flash_qspi.sh +++ b/tools/scripts/vmk180/build_flash_qspi.sh @@ -2,7 +2,8 @@ # Build, flash QSPI, and boot VMK180 - all in one script # # Usage: -# ./build_flash_qspi.sh # Full build, flash, and boot +# ./build_flash_qspi.sh # Full build, flash, and boot wolfBoot +# ./build_flash_qspi.sh --test-app # Full build + flash test app to boot partition # ./build_flash_qspi.sh --boot-sdcard # Test SD card boot mode only # ./build_flash_qspi.sh --boot-qspi # Test QSPI boot mode only # @@ -234,9 +235,11 @@ test_boot() { } # Check for test modes +FLASH_TEST_APP=false case "${1:-}" in test-boot|--boot-sdcard) test_boot boot_sdcard "boot-sdcard" ;; --boot-qspi) test_boot boot_qspi "boot-qspi" ;; + --test-app) FLASH_TEST_APP=true ;; esac # Build wolfBoot @@ -244,6 +247,20 @@ log_info "Building wolfBoot..." cp config/examples/versal_vmk180.config .config make clean && make +# Build test app if requested +if [ "$FLASH_TEST_APP" = "true" ]; then + log_info "Building and signing test application..." + make test-app/image.bin + make test-app/image_v1_signed.bin + + testapp_size=$(stat -c%s "test-app/image_v1_signed.bin") + log_info "Test app size: $testapp_size bytes" + + # Copy test app to TFTP directory + cp test-app/image_v1_signed.bin "${TFTP_DIR}/" + log_ok "Test app copied to TFTP: ${TFTP_DIR}/image_v1_signed.bin" +fi + # Generate BOOT.BIN log_info "Generating BOOT.BIN..." @@ -275,6 +292,14 @@ filesize=$(stat -c%s "${TFTP_DIR}/BOOT.BIN") filesize_hex=$(printf "0x%x" $filesize) log_info "BOOT.BIN size: $filesize bytes" +# Get test app size if flashing it +testapp_size_hex="0x0" +if [ "$FLASH_TEST_APP" = "true" ]; then + testapp_size=$(stat -c%s "${TFTP_DIR}/image_v1_signed.bin") + testapp_size_hex=$(printf "0x%x" $testapp_size) + log_info "Test app size: $testapp_size bytes" +fi + # Flash QSPI via U-Boot TFTP log_info "Flashing QSPI..." boot_sdcard @@ -283,6 +308,8 @@ expect <" + + puts "Erasing boot partition at 0x800000 (128KB sector)..." + send "sf erase 0x800000 0x20000\r" + expect { + "Versal>" { puts "Erase complete" } + timeout { puts "Erase timeout"; exit 1 } + } + + puts "Writing test app to 0x800000..." + send "sf write 0x10000000 0x800000 \$testapp_size_hex\r" + expect { + "Versal>" { puts "Write complete" } + timeout { puts "Write timeout"; exit 1 } + } + + puts "Test app flashed to boot partition!" +} + +puts "" +puts "All flash operations complete!" close EXPECT_EOF -log_ok "Flash and verification complete!" +log_ok "Flash operations complete!" # Restart continuous UART logging after expect exits log_info "Restarting continuous UART logging..."