Versal port cleanups

pull/679/head
David Garske 2026-01-16 11:03:03 -08:00 committed by Daniele Lacamera
parent dad2888450
commit ee3c313a66
14 changed files with 564 additions and 713 deletions

View File

@ -572,6 +572,12 @@ jobs:
arch: aarch64
config-file: ./config/examples/zynqmp.config
versal_vmk180_test:
uses: ./.github/workflows/test-build-aarch64.yml
with:
arch: aarch64
config-file: ./config/examples/versal_vmk180.config
sim_wolfhsm_test:
uses: ./.github/workflows/test-build.yml
with:

View File

@ -2041,7 +2041,7 @@ make
If you don't already have prebuilt firmware, clone the Xilinx prebuilt firmware repository:
```sh
git clone --branch xlnx_rel_v2024.1 https://github.com/Xilinx/soc-prebuilt-firmware.git
git clone --branch xlnx_rel_v2024.2 https://github.com/Xilinx/soc-prebuilt-firmware.git
export PREBUILT_DIR=$(pwd)/../soc-prebuilt-firmware/vmk180-versal
```
@ -2066,7 +2066,7 @@ The BIF file (`boot_wolfboot.bif`) references files using relative paths in the
### Flashing QSPI
Flash `BOOT.BIN` to QSPI flash using one of the following methods:
Flash `BOOT.BIN` to QSPI flash using your preferred method. For example:
- **Vitis**: Use the Hardware Manager to program the QSPI flash via JTAG. Load `BOOT.BIN` and program to QSPI32 flash memory.
@ -2093,15 +2093,33 @@ VMK180 uses dual parallel MT25QU01GBBB flash (128MB each, 256MB total). The QSPI
```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
**Test Application Details:**
- Uses generic `boot_arm64_start.S` startup code (shared with other AArch64 platforms)
- Uses generic `AARCH64.ld` linker script with `@WOLFBOOT_LOAD_ADDRESS@` placeholder
- Displays current exception level (EL) and firmware version
- Entry point: `_start` (in `boot_arm64_start.S`) which sets up stack, clears BSS, and calls `main()`
After flashing `BOOT.BIN` to QSPI offset 0x0, flash the signed test app to the boot partition at offset `0x800000` using your preferred method.
### Firmware Update Testing
wolfBoot supports firmware updates using the UPDATE partition. The bootloader automatically selects the image with the higher version number from either the BOOT or UPDATE partition.
**Partition Layout:**
- BOOT partition: `0x800000`
- UPDATE partition: `0x3400000`
- For RAM-based boot (Versal), images are loaded to `WOLFBOOT_LOAD_ADDRESS` (`0x10000000`)
**Update Behavior:**
- wolfBoot checks both BOOT and UPDATE partitions on boot
- Selects the partition with the higher version number
- Falls back to the other partition if verification fails
- The test application displays the firmware version it was signed with
To test firmware updates, build and sign the test application with different version numbers, then flash them to the appropriate partitions using your preferred method.
### Example Boot Output
@ -2131,7 +2149,8 @@ Booting at 0x10000000
===========================================
wolfBoot Test Application - AMD Versal
===========================================
Current EL: 1
Firmware Version: 2 (0x00000002)
Application running successfully!
Entering idle loop...

View File

@ -338,7 +338,8 @@ static int qspi_initialized = 0;
/* Forward declarations */
static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen,
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks);
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks,
const uint8_t *writeData, uint32_t writeLen);
static int qspi_wait_ready(QspiDev_t *dev);
/* Wait for GenFIFO empty (all entries processed) with timeout */
@ -573,13 +574,25 @@ static int qspi_fifo_rx(uint8_t *data, uint32_t len)
/* Core QSPI transfer function using GenFIFO */
static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen,
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks)
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks,
const uint8_t *writeData, uint32_t writeLen)
{
int ret = 0;
uint32_t entry;
uint32_t i;
uint32_t chunkLen;
uint32_t txEntry, chunkEntry;
const uint8_t *writePtr;
uint32_t remaining, offset, xferSz;
uint32_t rxEntry;
/* Enable GQSPI controller */
/* Set DMA mode only for Quad reads (when dummyClocks > 0) and not in IO mode */
if (dummyClocks > 0 && rxLen > 0) {
#ifndef GQSPI_MODE_IO
GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_DMA;
#endif
}
GQSPI_EN = 1;
dsb();
@ -605,45 +618,173 @@ static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen,
ret = qspi_gen_fifo_start_and_wait();
}
/* Dummy clocks phase (for fast read commands) */
/* Dummy clocks phase (for fast read commands)
* Use QSPI mode if dummy clocks are present (indicates Quad Read) */
if (ret == 0 && dummyClocks > 0) {
ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(dummyClocks));
uint32_t dummyEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_DATA_XFER |
GQSPI_GEN_FIFO_IMM(dummyClocks);
ret = qspi_gen_fifo_push(dummyEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
}
/* === RX Phase === */
if (dev->stripe) {
/* Striped mode: IMM(1) reads 1 byte from each flash = 2 bytes total */
for (i = 0; i < rxLen && ret == 0; i += 2) {
uint32_t rxEntry = entry | GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
GQSPI_GEN_FIFO_STRIPE |
GQSPI_GEN_FIFO_IMM(1);
/* === TX Write Data Phase === */
if (ret == 0 && writeLen > 0 && writeData != NULL) {
txEntry = entry | GQSPI_GEN_FIFO_TX | GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE);
writePtr = writeData;
chunkLen = writeLen;
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
if (ret == 0) {
/* Read 2 bytes (one from each flash, interleaved) */
ret = qspi_fifo_rx(&rxData[i], 2);
}
while (chunkLen > 0 && ret == 0) {
uint32_t chunk = (chunkLen > 255) ? 255 : chunkLen;
chunkEntry = txEntry | GQSPI_GEN_FIFO_IMM(chunk);
ret = qspi_gen_fifo_push(chunkEntry);
if (ret != 0) break;
/* Start GenFIFO processing so it drains TX FIFO as we fill it */
GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO;
dsb();
/* Push data to TX FIFO */
ret = qspi_fifo_tx(writePtr, chunk);
if (ret != 0) break;
/* Wait for GenFIFO to complete */
ret = qspi_wait_genfifo_empty();
writePtr += chunk;
chunkLen -= chunk;
}
} else {
/* Single flash: read 1 byte at a time */
for (i = 0; i < rxLen && ret == 0; i++) {
uint32_t rxEntry = entry | GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
GQSPI_GEN_FIFO_IMM(1);
}
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
/* === RX Phase === */
if (ret == 0 && rxLen > 0) {
/* Use QSPI mode for RX if dummy clocks were used (Quad Read) */
if (dummyClocks > 0) {
rxEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE);
#ifndef GQSPI_MODE_IO
/* DMA mode: Use DMA for RX phase */
if ((GQSPI_CFG & GQSPI_CFG_MODE_EN_MASK) == GQSPI_CFG_MODE_EN_DMA) {
uint8_t *dmaPtr;
uint32_t dmaLen;
int useTemp = 0;
/* 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;
dmaLen = (rxLen + GQSPI_DMA_ALIGN - 1) & ~(GQSPI_DMA_ALIGN - 1);
if (dmaLen > sizeof(dma_tmpbuf)) {
dmaLen = sizeof(dma_tmpbuf);
}
useTemp = 1;
} else {
dmaPtr = rxData;
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);
GQSPIDMA_SIZE = dmaLen;
/* Enable DMA done interrupt */
GQSPIDMA_IER = GQSPIDMA_ISR_DONE;
/* Flush dcache for DMA coherency */
flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen);
/* Push all GenFIFO entries first (use EXP mode for large transfers) */
while (ret == 0 && remaining > 0) {
xferSz = qspi_calc_exp(remaining, &rxEntry);
ret = qspi_gen_fifo_push(rxEntry);
remaining -= xferSz;
}
/* Trigger GenFIFO */
if (ret == 0) {
GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO;
dsb();
}
/* Wait for DMA completion */
if (ret == 0) {
ret = qspi_dma_wait();
}
/* Invalidate cache after DMA */
flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen);
/* Copy from temp buffer if needed (only copy requested bytes) */
if (ret == 0 && useTemp) {
memcpy(rxData, dmaPtr, rxLen);
}
} else {
/* IO mode: Use FIFO polling (fallback when DMA mode not enabled) */
remaining = rxLen;
offset = 0;
while (ret == 0 && remaining > 0) {
xferSz = qspi_calc_exp(remaining, &rxEntry);
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
if (ret == 0) {
ret = qspi_fifo_rx(&rxData[offset], xferSz);
}
offset += xferSz;
remaining -= xferSz;
}
}
if (ret == 0) {
ret = qspi_fifo_rx(&rxData[i], 1);
#else /* GQSPI_MODE_IO */
/* IO mode: Use FIFO polling */
remaining = rxLen;
offset = 0;
while (ret == 0 && remaining > 0) {
xferSz = qspi_calc_exp(remaining, &rxEntry);
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
if (ret == 0) {
ret = qspi_fifo_rx(&rxData[offset], xferSz);
}
offset += xferSz;
remaining -= xferSz;
}
#endif /* !GQSPI_MODE_IO */
} else {
/* SPI mode for simple reads */
rxEntry = entry | GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE) |
GQSPI_GEN_FIFO_IMM(1);
uint32_t readSz = dev->stripe ? 2 : 1;
for (i = 0; i < rxLen && ret == 0; i += readSz) {
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
if (ret == 0) {
ret = qspi_fifo_rx(&rxData[i], readSz);
}
}
}
}
@ -654,301 +795,12 @@ static int qspi_transfer(QspiDev_t *dev, const uint8_t *txData, uint32_t txLen,
qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
qspi_gen_fifo_start_and_wait();
/* Disable controller */
GQSPI_EN = 0;
dsb();
return ret;
}
/* QSPI Read transfer - uses Quad mode (4-bit) for data phase
* Command and address are sent in SPI mode, data received in QSPI mode */
static int qspi_transfer_qread(QspiDev_t *dev, const uint8_t *cmd, uint32_t cmdLen,
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks)
{
int ret = 0;
uint32_t entry, rxEntry;
uint32_t i;
/* Enable GQSPI controller */
GQSPI_EN = 1;
dsb();
/* Base entry for command phase: bus + CS + SPI mode */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_GEN_FIFO_MODE_SPI;
/* CS assertion */
ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
/* TX Phase - send command + address bytes in SPI mode */
for (i = 0; i < cmdLen && ret == 0; i++) {
uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX |
GQSPI_GEN_FIFO_IMM(cmd[i]);
ret = qspi_gen_fifo_push(txEntry);
}
/* Trigger and wait for TX to complete */
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
/* Dummy clocks phase (required for Fast/Quad Read)
* Send dummy clocks: DATA_XFER with no TX or RX, IMM = clock count */
if (ret == 0 && dummyClocks > 0) {
uint32_t dummyEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_DATA_XFER |
GQSPI_GEN_FIFO_IMM(dummyClocks);
ret = qspi_gen_fifo_push(dummyEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
}
/* RX Phase - receive data in QSPI mode (4-bit)
* Use EXP mode for large transfers (pattern from zynq.c) */
rxEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE);
{
uint32_t remaining = rxLen;
uint32_t offset = 0;
uint32_t xferSz;
while (ret == 0 && remaining > 0) {
xferSz = qspi_calc_exp(remaining, &rxEntry);
ret = qspi_gen_fifo_push(rxEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
if (ret == 0) {
ret = qspi_fifo_rx(&rxData[offset], xferSz);
}
offset += xferSz;
remaining -= xferSz;
}
}
/* CS Deassert */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI;
qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
qspi_gen_fifo_start_and_wait();
/* Disable controller */
GQSPI_EN = 0;
dsb();
return ret;
}
/* Switch back to IO mode if DMA was used and disable controller */
#ifndef GQSPI_MODE_IO
/* DMA-enabled QSPI Read transfer
* Uses DMA for RX phase for better performance on large reads */
static int qspi_transfer_qread_dma(QspiDev_t *dev, const uint8_t *cmd, uint32_t cmdLen,
uint8_t *rxData, uint32_t rxLen, uint32_t dummyClocks)
{
int ret = 0;
uint32_t entry, rxEntry;
uint32_t i;
uint8_t *dmaPtr;
uint32_t dmaLen;
int useTemp = 0;
/* Enable GQSPI controller in DMA mode */
GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_DMA;
GQSPI_EN = 1;
dsb();
/* Base entry for command phase: bus + CS + SPI mode */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_GEN_FIFO_MODE_SPI;
/* CS assertion */
ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
/* TX Phase - send command + address bytes in SPI mode */
for (i = 0; i < cmdLen && ret == 0; i++) {
uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX |
GQSPI_GEN_FIFO_IMM(cmd[i]);
ret = qspi_gen_fifo_push(txEntry);
if ((GQSPI_CFG & GQSPI_CFG_MODE_EN_MASK) == GQSPI_CFG_MODE_EN_DMA) {
GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_IO;
}
/* Trigger and wait for TX to complete */
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
/* Dummy clocks phase */
if (ret == 0 && dummyClocks > 0) {
uint32_t dummyEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_DATA_XFER |
GQSPI_GEN_FIFO_IMM(dummyClocks);
ret = qspi_gen_fifo_push(dummyEntry);
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
}
/* DMA RX Phase */
if (ret == 0 && rxLen > 0) {
uint32_t remaining;
uint32_t xferSz;
/* 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;
dmaLen = (rxLen + GQSPI_DMA_ALIGN - 1) & ~(GQSPI_DMA_ALIGN - 1);
if (dmaLen > sizeof(dma_tmpbuf)) {
dmaLen = sizeof(dma_tmpbuf);
}
useTemp = 1;
} else {
dmaPtr = rxData;
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);
GQSPIDMA_SIZE = dmaLen;
/* Enable DMA done interrupt */
GQSPIDMA_IER = GQSPIDMA_ISR_DONE;
/* Flush dcache for DMA coherency */
flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen);
/* Setup GenFIFO for RX with DMA - use EXP mode for large transfers */
rxEntry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_QSPI_MODE |
GQSPI_GEN_FIFO_RX |
GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE);
/* Use qspi_calc_exp for large transfers (pattern from zynq.c) */
while (ret == 0 && remaining > 0) {
xferSz = qspi_calc_exp(remaining, &rxEntry);
ret = qspi_gen_fifo_push(rxEntry);
remaining -= xferSz;
}
/* Trigger GenFIFO */
if (ret == 0) {
GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO;
dsb();
}
/* Wait for DMA completion */
if (ret == 0) {
ret = qspi_dma_wait();
}
/* Invalidate cache after DMA */
flush_dcache_range((uintptr_t)dmaPtr, (uintptr_t)dmaPtr + dmaLen);
/* Copy from temp buffer if needed (only copy requested bytes) */
if (ret == 0 && useTemp) {
memcpy(rxData, dmaPtr, rxLen);
}
}
/* CS Deassert */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI;
qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
qspi_gen_fifo_start_and_wait();
/* Switch back to IO mode and disable controller */
GQSPI_CFG = (GQSPI_CFG & ~GQSPI_CFG_MODE_EN_MASK) | GQSPI_CFG_MODE_EN_IO;
GQSPI_EN = 0;
dsb();
return ret;
}
#endif /* !GQSPI_MODE_IO */
/* Write page data to flash (for page programming) */
static int qspi_write_page(QspiDev_t *dev, const uint8_t *cmd, uint32_t cmdLen,
const uint8_t *data, uint32_t dataLen)
{
int ret = 0;
uint32_t entry;
uint32_t i;
/* Enable GQSPI controller */
GQSPI_EN = 1;
dsb();
/* Base entry: bus + CS + SPI mode (page program uses SPI mode) */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) |
(dev->cs & GQSPI_GEN_FIFO_CS_MASK) |
GQSPI_GEN_FIFO_MODE_SPI;
/* CS assertion */
ret = qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
/* TX Phase - send command bytes (includes address) via immediate mode */
for (i = 0; i < cmdLen && ret == 0; i++) {
uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX |
GQSPI_GEN_FIFO_IMM(cmd[i]);
ret = qspi_gen_fifo_push(txEntry);
}
/* Trigger and wait for command to complete */
if (ret == 0) {
ret = qspi_gen_fifo_start_and_wait();
}
/* TX Phase - send data via TX FIFO (not immediate mode) */
if (ret == 0 && dataLen > 0) {
uint32_t txEntry = entry | GQSPI_GEN_FIFO_TX | GQSPI_GEN_FIFO_DATA_XFER |
(dev->stripe & GQSPI_GEN_FIFO_STRIPE);
while (dataLen > 0 && ret == 0) {
uint32_t chunkLen = (dataLen > 255) ? 255 : dataLen;
uint32_t chunkEntry = txEntry | GQSPI_GEN_FIFO_IMM(chunkLen);
ret = qspi_gen_fifo_push(chunkEntry);
if (ret != 0) break;
/* Start GenFIFO processing so it drains TX FIFO as we fill it */
GQSPI_CFG |= GQSPI_CFG_START_GEN_FIFO;
dsb();
/* Push data to TX FIFO */
ret = qspi_fifo_tx(data, chunkLen);
if (ret != 0) break;
/* Wait for GenFIFO to complete */
ret = qspi_wait_genfifo_empty();
data += chunkLen;
dataLen -= chunkLen;
}
}
/* CS Deassert */
entry = (dev->bus & GQSPI_GEN_FIFO_BUS_MASK) | GQSPI_GEN_FIFO_MODE_SPI;
qspi_gen_fifo_push(entry | GQSPI_GEN_FIFO_IMM(1));
qspi_gen_fifo_start_and_wait();
/* Disable controller */
#endif
GQSPI_EN = 0;
dsb();
@ -962,7 +814,7 @@ static int qspi_read_id(QspiDev_t *dev, uint8_t *id, uint32_t len)
int ret;
cmd[0] = FLASH_CMD_READ_ID;
ret = qspi_transfer(dev, cmd, 1, id, len, 0);
ret = qspi_transfer(dev, cmd, 1, id, len, 0, NULL, 0);
return ret;
}
@ -983,13 +835,13 @@ static int qspi_read_status(QspiDev_t *dev, uint8_t *status)
tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER;
tmpDev.stripe = 0;
cmd[0] = FLASH_CMD_READ_STATUS;
ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0);
ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0, NULL, 0);
if (ret != 0) return ret;
/* Read from upper chip */
tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP;
tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER;
ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0);
ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0, NULL, 0);
if (ret != 0) return ret;
/* AND the status from both chips */
@ -998,7 +850,7 @@ static int qspi_read_status(QspiDev_t *dev, uint8_t *status)
}
cmd[0] = FLASH_CMD_READ_STATUS;
ret = qspi_transfer(dev, cmd, 1, data, 1, 0);
ret = qspi_transfer(dev, cmd, 1, data, 1, 0, NULL, 0);
if (ret == 0) {
*status = data[0];
}
@ -1021,13 +873,13 @@ static int qspi_read_flag_status(QspiDev_t *dev, uint8_t *status)
tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER;
tmpDev.stripe = 0;
cmd[0] = FLASH_CMD_READ_FLAG_STATUS;
ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0);
ret = qspi_transfer(&tmpDev, cmd, 1, &data[0], 1, 0, NULL, 0);
if (ret != 0) return ret;
/* Read from upper chip */
tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP;
tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER;
ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0);
ret = qspi_transfer(&tmpDev, cmd, 1, &data[1], 1, 0, NULL, 0);
if (ret != 0) return ret;
/* AND the status from both chips */
@ -1036,7 +888,7 @@ static int qspi_read_flag_status(QspiDev_t *dev, uint8_t *status)
}
cmd[0] = FLASH_CMD_READ_FLAG_STATUS;
ret = qspi_transfer(dev, cmd, 1, data, 1, 0);
ret = qspi_transfer(dev, cmd, 1, data, 1, 0, NULL, 0);
if (ret == 0) {
*status = data[0];
}
@ -1078,16 +930,16 @@ static int qspi_write_enable(QspiDev_t *dev)
tmpDev.bus = GQSPI_GEN_FIFO_BUS_LOW;
tmpDev.cs = GQSPI_GEN_FIFO_CS_LOWER;
tmpDev.stripe = 0;
ret = qspi_transfer(&tmpDev, cmd, 1, NULL, 0, 0);
ret = qspi_transfer(&tmpDev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
if (ret != 0) return ret;
/* Send to upper chip */
tmpDev.bus = GQSPI_GEN_FIFO_BUS_UP;
tmpDev.cs = GQSPI_GEN_FIFO_CS_UPPER;
ret = qspi_transfer(&tmpDev, cmd, 1, NULL, 0, 0);
ret = qspi_transfer(&tmpDev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
if (ret != 0) return ret;
} else {
ret = qspi_transfer(dev, cmd, 1, NULL, 0, 0);
ret = qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
if (ret != 0) return ret;
}
@ -1108,7 +960,7 @@ static int qspi_write_disable(QspiDev_t *dev)
uint8_t cmd[1];
cmd[0] = FLASH_CMD_WRITE_DISABLE;
return qspi_transfer(dev, cmd, 1, NULL, 0, 0);
return qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
}
#if GQPI_USE_4BYTE_ADDR == 1
@ -1123,7 +975,7 @@ static int qspi_enter_4byte_addr(QspiDev_t *dev)
if (ret != 0) return ret;
cmd[0] = FLASH_CMD_ENTER_4B_MODE;
ret = qspi_transfer(dev, cmd, 1, NULL, 0, 0);
ret = qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
QSPI_DEBUG_PRINTF("QSPI: Enter 4-byte mode: ret=%d\n", ret);
if (ret == 0) {
@ -1143,7 +995,7 @@ static int qspi_exit_4byte_addr(QspiDev_t *dev)
if (ret != 0) return ret;
cmd[0] = FLASH_CMD_EXIT_4B_MODE;
ret = qspi_transfer(dev, cmd, 1, NULL, 0, 0);
ret = qspi_transfer(dev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
QSPI_DEBUG_PRINTF("QSPI: Exit 4-byte mode: ret=%d\n", ret);
if (ret == 0) {
@ -1158,19 +1010,22 @@ static int qspi_exit_4byte_addr(QspiDev_t *dev)
#ifndef TEST_EXT_ADDRESS
#define TEST_EXT_ADDRESS 0x2800000 /* 40MB */
#endif
#ifndef TEST_EXT_SIZE
#define TEST_EXT_SIZE (FLASH_PAGE_SIZE * 4)
#endif
static int test_ext_flash(QspiDev_t* dev)
{
int ret;
uint32_t i;
uint8_t pageData[FLASH_PAGE_SIZE * 4];
uint8_t pageData[TEST_EXT_SIZE];
(void)dev;
wolfBoot_printf("Testing ext flash at 0x%x...\n", TEST_EXT_ADDRESS);
#ifndef TEST_FLASH_READONLY
/* Erase sector */
ret = ext_flash_erase(TEST_EXT_ADDRESS, FLASH_SECTOR_SIZE);
ret = ext_flash_erase(TEST_EXT_ADDRESS, WOLFBOOT_SECTOR_SIZE);
wolfBoot_printf("Erase Sector: Ret %d\n", ret);
/* Write Pages */
@ -1185,7 +1040,7 @@ static int test_ext_flash(QspiDev_t* dev)
memset(pageData, 0, sizeof(pageData));
ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData));
wolfBoot_printf("Read Page: Ret %d\n", ret);
if (ret != 0) {
if (ret < 0) {
wolfBoot_printf("Flash read failed!\n");
return ret;
}
@ -1228,8 +1083,6 @@ static void qspi_init(void)
/* Read initial state left by PLM */
cfg = GQSPI_CFG;
QSPI_DEBUG_PRINTF("QSPI: PLM state - CFG=0x%08x ISR=0x%08x\n",
cfg, GQSPI_ISR);
/* Disable controller during reconfiguration */
GQSPI_EN = 0;
@ -1251,7 +1104,7 @@ static void qspi_init(void)
/* 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
* Note: qspi_transfer_qread_dma() will switch to DMA mode for reads */
* Note: ext_flash_read() will switch to DMA mode for reads if not in IO mode */
cfg = (cfg & ~GQSPI_CFG_MODE_EN_MASK); /* Clear mode bits */
cfg |= GQSPI_CFG_MODE_EN_IO; /* Set IO mode for init */
GQSPI_CFG = cfg;
@ -1274,8 +1127,6 @@ static void qspi_init(void)
dsb();
#endif
QSPI_DEBUG_PRINTF("QSPI: After config - CFG=0x%08x\n", GQSPI_CFG);
/* Configure device for single flash (lower) first */
qspiDev.mode = GQSPI_GEN_FIFO_MODE_SPI;
qspiDev.bus = GQSPI_GEN_FIFO_BUS_LOW;
@ -1343,9 +1194,6 @@ static void qspi_init(void)
#endif
}
#endif /* EXT_FLASH */
/* ============================================================================
* HAL Public Interface
* ============================================================================
@ -1353,24 +1201,29 @@ static void qspi_init(void)
void hal_init(void)
{
#if defined(__WOLFBOOT) && defined(DEBUG_UART)
const char *banner = "\n"
"========================================\n"
"wolfBoot Secure Boot - AMD Versal\n"
"========================================\n";
#endif
#ifdef DEBUG_UART
uart_init();
#endif
#ifdef __WOLFBOOT
wolfBoot_printf("%s", banner);
#endif
wolfBoot_printf("Current EL: %d\n", current_el());
wolfBoot_printf("Timer Freq: %lu Hz\n", (unsigned long)timer_get_freq());
#endif /* DEBUG_UART */
#ifdef EXT_FLASH
qspi_init();
#endif
}
#endif /* EXT_FLASH */
void hal_prepare_boot(void)
{
#if defined(EXT_FLASH) && GQPI_USE_4BYTE_ADDR == 1
@ -1448,14 +1301,30 @@ void* hal_get_dts_update_address(void)
}
#endif /* MMU */
#ifdef WOLFBOOT_DUALBOOT
/**
* Get the primary (boot) partition address in flash
* Returns the flash address where the boot partition starts
*/
void* hal_get_primary_address(void)
{
return (void*)WOLFBOOT_PARTITION_BOOT_ADDRESS;
}
/**
* Get the update partition address in flash
* Returns the flash address where the update partition starts
*/
void* hal_get_update_address(void)
{
return (void*)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
}
#endif /* WOLFBOOT_DUALBOOT */
/* ============================================================================
* Flash Functions (STUBS)
* ============================================================================
* These are placeholder implementations.
* Real implementation will depend on boot media:
* - OSPI flash (VERSAL_OSPI_BASE)
* - SD/eMMC via SDHCI (VERSAL_SD0_BASE / VERSAL_SD1_BASE)
* There is no "internal flash" on the Versal, so these are stubs.
*/
void RAMFUNCTION hal_flash_unlock(void)
@ -1473,10 +1342,6 @@ int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len)
(void)address;
(void)data;
(void)len;
/* Stub - flash write not implemented */
wolfBoot_printf("hal_flash_write: STUB (addr=0x%lx, len=%d)\n",
(unsigned long)address, len);
return -1;
}
@ -1484,10 +1349,6 @@ int RAMFUNCTION hal_flash_erase(uintptr_t address, int len)
{
(void)address;
(void)len;
/* Stub - flash erase not implemented */
wolfBoot_printf("hal_flash_erase: STUB (addr=0x%lx, len=%d)\n",
(unsigned long)address, len);
return -1;
}
@ -1515,11 +1376,15 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
uint8_t cmd[5];
uint32_t xferSz, page, pages;
uintptr_t addr;
const uint8_t *pageData;
if (!qspi_initialized) {
return -1;
}
QSPI_DEBUG_PRINTF("ext_flash_write: addr=0x%lx, len=%d\n",
(unsigned long)address, len);
/* Write by page */
pages = ((len + (FLASH_PAGE_SIZE - 1)) / FLASH_PAGE_SIZE);
for (page = 0; page < pages && ret == 0; page++) {
@ -1543,9 +1408,9 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
cmd[3] = (addr >> 8) & 0xFF;
cmd[4] = addr & 0xFF;
/* Send command + data - hardware handles striping */
ret = qspi_write_page(&qspiDev, cmd, 5,
data + (page * FLASH_PAGE_SIZE), xferSz);
pageData = data + (page * FLASH_PAGE_SIZE);
ret = qspi_transfer(&qspiDev, cmd, sizeof(cmd), NULL, 0, 0, pageData, xferSz);
QSPI_DEBUG_PRINTF("Flash Page %d Write: Ret %d\n", page, ret);
if (ret != 0) break;
@ -1560,7 +1425,7 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len)
int ext_flash_read(uintptr_t address, uint8_t *data, int len)
{
uint8_t cmd[5];
int ret;
int ret = 0;
uintptr_t addr = address;
if (!qspi_initialized) {
@ -1573,8 +1438,6 @@ int ext_flash_read(uintptr_t address, uint8_t *data, int 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 */
@ -1584,28 +1447,14 @@ int ext_flash_read(uintptr_t address, uint8_t *data, int len)
cmd[3] = (addr >> 8) & 0xFF;
cmd[4] = addr & 0xFF;
/* Hardware handles striping via GQSPI_GEN_FIFO_STRIPE flag */
#ifdef GQSPI_MODE_IO
ret = qspi_transfer_qread(&qspiDev, cmd, 5, data, len, GQSPI_DUMMY_READ);
#else
ret = qspi_transfer_qread_dma(&qspiDev, cmd, 5, data, len, GQSPI_DUMMY_READ);
#endif
ret = qspi_transfer(&qspiDev, cmd, sizeof(cmd), data, len, GQSPI_DUMMY_READ, NULL, 0);
/* 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. */
/* On error, fill buffer with 0xFF to simulate unwritten flash */
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) */
QSPI_DEBUG_PRINTF("ext_flash_read: ret=%d\n", ret);
return (ret == 0) ? len : ret;
}
@ -1619,6 +1468,9 @@ int ext_flash_erase(uintptr_t address, int len)
return -1;
}
QSPI_DEBUG_PRINTF("ext_flash_erase: addr=0x%lx, len=%d\n",
(unsigned long)address, len);
while (len > 0 && ret == 0) {
addr = address;
if (qspiDev.stripe) {
@ -1635,9 +1487,10 @@ int ext_flash_erase(uintptr_t address, int len)
cmd[2] = (addr >> 16) & 0xFF;
cmd[3] = (addr >> 8) & 0xFF;
cmd[4] = addr & 0xFF;
ret = qspi_transfer(&qspiDev, cmd, sizeof(cmd), NULL, 0, 0, NULL, 0);
ret = qspi_transfer(&qspiDev, cmd, 5, NULL, 0, 0);
QSPI_DEBUG_PRINTF("ext_flash_erase: addr=0x%lx\n", (unsigned long)address);
QSPI_DEBUG_PRINTF(" Flash Erase: Ret %d, Address 0x%x\n",
ret, address);
if (ret == 0) {
ret = qspi_wait_ready(&qspiDev);

View File

@ -58,6 +58,23 @@ void hal_init(void);
uint64_t hal_get_timer_us(void);
#endif
/* Boot benchmarking macros
* Usage: Declare BENCHMARK_DECLARE() at function scope,
* then use BENCHMARK_START() and BENCHMARK_END(msg) to measure time.
*/
#ifdef BOOT_BENCHMARK
#define BENCHMARK_DECLARE() 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)\r\n", (unsigned long)_elapsed_ms); \
} while(0)
#else
#define BENCHMARK_DECLARE() do {} while(0)
#define BENCHMARK_START() do {} while(0)
#define BENCHMARK_END(msg) wolfBoot_printf(msg "\r\n")
#endif
#ifdef ARCH_64BIT
typedef uintptr_t haladdr_t; /* 64-bit platforms */
int hal_flash_write(uintptr_t address, const uint8_t *data, int len);

View File

@ -1291,11 +1291,12 @@ int wolfBoot_dualboot_candidate(void)
}
#else
static int wolfBoot_current_firmware_version()
static int wolfBoot_current_firmware_version(void)
{
return wolfBoot_get_blob_version(hal_get_primary_address());
}
static int wolfBoot_update_firmware_version() {
static int wolfBoot_update_firmware_version(void)
{
return wolfBoot_get_blob_version(hal_get_update_address());
}

View File

@ -266,7 +266,7 @@ void RAMFUNCTION wolfBoot_start(void)
uint32_t dts_size = 0;
#endif
char part_name[4] = {'P', ':', 'X', '\0'};
uint64_t start_us, elapsed_ms;
BENCHMARK_DECLARE();
#ifdef DISK_ENCRYPT
/* Initialize encryption - this sets up the cipher with key from storage */
@ -400,7 +400,7 @@ void RAMFUNCTION wolfBoot_start(void)
/* Read the image into RAM */
wolfBoot_printf("Loading image from disk...");
start_us = hal_get_timer_us();
BENCHMARK_START();
load_off = 0;
do {
ret = disk_part_read(BOOT_DISK, cur_part, load_off,
@ -416,13 +416,12 @@ void RAMFUNCTION wolfBoot_start(void)
selected ^= 1;
continue;
}
elapsed_ms = (hal_get_timer_us() - start_us) / 1000;
wolfBoot_printf("done. (%lu ms)\r\n", (unsigned long)elapsed_ms);
BENCHMARK_END("done");
#ifdef DISK_ENCRYPT
/* Decrypt the image in RAM */
wolfBoot_printf("Decrypting image...");
start_us = hal_get_timer_us();
BENCHMARK_START();
ret = decrypt_image((uint8_t*)load_address,
os_image.fw_size + IMAGE_HEADER_SIZE);
if (ret != 0) {
@ -430,8 +429,7 @@ void RAMFUNCTION wolfBoot_start(void)
selected ^= 1;
continue;
}
elapsed_ms = (hal_get_timer_us() - start_us) / 1000;
wolfBoot_printf("done. (%lu ms)\r\n", (unsigned long)elapsed_ms);
BENCHMARK_END("done");
#endif
memset(&os_image, 0, sizeof(os_image));
@ -443,25 +441,23 @@ void RAMFUNCTION wolfBoot_start(void)
}
wolfBoot_printf("Checking image integrity...");
start_us = hal_get_timer_us();
BENCHMARK_START();
if (wolfBoot_verify_integrity(&os_image) != 0) {
wolfBoot_printf("Error validating integrity for %s\r\n", part_name);
selected ^= 1;
continue;
}
elapsed_ms = (hal_get_timer_us() - start_us) / 1000;
wolfBoot_printf("done. (%lu ms)\r\n", (unsigned long)elapsed_ms);
BENCHMARK_END("done");
wolfBoot_printf("Verifying image signature...");
start_us = hal_get_timer_us();
BENCHMARK_START();
if (wolfBoot_verify_authenticity(&os_image) != 0) {
wolfBoot_printf("Error validating authenticity for %s\r\n",
part_name);
selected ^= 1;
continue;
} else {
elapsed_ms = (hal_get_timer_us() - start_us) / 1000;
wolfBoot_printf("done. (%lu ms)\r\n", (unsigned long)elapsed_ms);
BENCHMARK_END("done");
failures = 0;
break; /* Success case */
}

View File

@ -43,19 +43,6 @@ 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)
@ -71,6 +58,7 @@ int wolfBoot_ramboot(struct wolfBoot_image *img, uint8_t *src, uint8_t *dst)
{
int ret;
uint32_t img_size;
BENCHMARK_DECLARE();
/* read header into RAM */
wolfBoot_printf("Loading header %d bytes from %p to %p\n",
@ -122,6 +110,7 @@ void RAMFUNCTION wolfBoot_start(void)
{
int active = -1, ret = 0;
struct wolfBoot_image os_image;
BENCHMARK_DECLARE();
#ifdef WOLFBOOT_UBOOT_LEGACY
uint8_t *image_ptr;
#endif

View File

@ -1,55 +0,0 @@
MEMORY
{
FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = 256K
DRAM (rwx) : ORIGIN = 0x80001000 , LENGTH = 0xBFFFFFFF
OCRAM (rwx) : ORIGIN = 0x18020100, LENGTH = 128K
}
ENTRY(main);
SECTIONS
{
.text :
{
_start_text = .;
KEEP(*(.boot*))
*(.text*)
*(.rodata*)
*(.note.*)
. = ALIGN(4);
_end_text = .;
} > OCRAM
.edidx :
{
. = ALIGN(4);
*(.ARM.exidx*)
} > OCRAM
PROVIDE(_stored_data = .);
.data :
{
_start_data = .;
KEEP(*(.data*))
. = ALIGN(4);
KEEP(*(.ramcode))
. = ALIGN(4);
_end_data = .;
} > OCRAM
.bss (NOLOAD) :
{
_start_bss = .;
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_end_bss = .;
__bss_end__ = .;
_end = .;
} > OCRAM
. = ALIGN(4);
}
END_STACK = _start_text;

View File

@ -135,6 +135,9 @@ endif
ifeq ($(ARCH),AARCH64)
APP_OBJS:=boot_arm64_start.o $(APP_OBJS)
# Prevent inclusion of standard C runtime startup files that conflict with boot_arm64_start.S
# Use -Wl, prefix to pass flags directly to linker when GCC is used as linker driver
LDFLAGS+=-nostartfiles -nostdlib -nodefaultlibs -Wl,--entry=_start
endif
ifeq ($(ARCH),RISCV64)
@ -288,6 +291,9 @@ endif
ifeq ($(EXT_FLASH),1)
CFLAGS+=-D"EXT_FLASH=1" -D"PART_UPDATE_EXT=1"
ifeq ($(NO_XIP),1)
CFLAGS+=-D"PART_BOOT_EXT=1"
endif
endif
ifeq ($(SPI_FLASH),1)
@ -507,13 +513,12 @@ ifeq ($(TARGET),x86_fsp_qemu)
LDFLAGS=
endif
ifeq ($(TARGET),nxp_ls1028a)
LSCRIPT_TEMPLATE:=AARCH64-ls1028a.ld
endif
ifeq ($(TARGET),versal)
LSCRIPT_TEMPLATE:=AARCH64.ld
LDFLAGS+=-nostdlib
# Enable DEBUG_UART for test-app to use wolfBoot_printf and hal functions
DEBUG_UART:=1
CFLAGS+=-DDEBUG_UART
endif
ifeq ($(TARGET),zynq)

View File

@ -22,74 +22,37 @@
*/
#include <stdint.h>
#include "hal.h"
#include "hal/versal.h"
#include "wolfboot/wolfboot.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_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_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)
{
/* Wait while TX FIFO is full */
while (UART_FR & UART_FR_TXFF)
;
UART_DR = c;
}
static void uart_print(const char *s)
{
while (*s) {
if (*s == '\n')
uart_tx('\r');
uart_tx((uint8_t)*s++);
}
}
#include "printf.h"
void main(void)
{
uint32_t el = get_current_el();
uint32_t boot_version, update_version;
uart_print("\n\n");
uart_print("===========================================\n");
uart_print(" wolfBoot Test Application - AMD Versal\n");
uart_print("===========================================\n\n");
/* Initialize HAL (UART, etc.) */
hal_init();
/* Print current exception level */
uart_print("Current EL: ");
uart_tx('0' + el);
uart_print("\n");
/* Get versions from both partitions */
boot_version = wolfBoot_get_image_version(PART_BOOT);
update_version = wolfBoot_get_image_version(PART_UPDATE);
uart_print("Application running successfully!\n");
wolfBoot_printf("\n\n");
wolfBoot_printf("===========================================\n");
wolfBoot_printf(" wolfBoot Test Application - AMD Versal\n");
wolfBoot_printf("===========================================\n\n");
uart_print("\nEntering idle loop...\n");
/* Print firmware versions */
wolfBoot_printf("Boot Partition Version: %d (0x%08x)\n", boot_version, boot_version);
wolfBoot_printf("Update Partition Version: %d (0x%08x)\n", update_version, update_version);
/* Wait for transmit to complete (TX FIFO empty) */
while (!(UART_FR & UART_FR_TXFE))
;
wolfBoot_printf("Application running successfully!\n");
wolfBoot_printf("\nEntering idle loop...\n");
/* Idle loop */
while (1) {
__asm__ volatile("wfi");
}
}

View File

@ -65,3 +65,16 @@ _start:
b 5b
.size _start, . - _start
/* Provide _exit stub for bare-metal builds (required by some standard library code) */
.section .text, "ax"
.global _exit
.type _exit, @function
_exit:
/* Loop forever - bare-metal applications don't exit */
6:
wfi
b 6b
.size _exit, . - _exit

View File

@ -2,15 +2,16 @@
# Build, flash QSPI, and boot VMK180 - all in one script
#
# Usage:
# ./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
# ./versal_test.sh # Full build, flash, and boot wolfBoot
# ./versal_test.sh --test-app # Full build + flash test app to boot partition
# ./versal_test.sh --test-update # Full build + flash test app v2 to update partition
# ./versal_test.sh --boot-sdcard # Test SD card boot mode only
# ./versal_test.sh --boot-qspi # Test QSPI boot mode only
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WOLFBOOT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
WOLFBOOT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "$WOLFBOOT_ROOT"
# Config
@ -19,7 +20,7 @@ UART_BAUD="${UART_BAUD:-115200}"
SERVER_IP="${SERVER_IP:-10.0.4.24}"
BOARD_IP="${BOARD_IP:-10.0.4.90}"
TFTP_DIR="${TFTP_DIR:-/srv/tftp}"
VITIS_PATH="${VITIS_PATH:-/opt/Xilinx/Vitis/2024.1}"
VITIS_PATH="${VITIS_PATH:-/opt/Xilinx/Vitis/2024.2}"
RELAY_PORT="${RELAY_PORT:-/dev/ttyACM2}"
UART_LOG="${UART_LOG:-${WOLFBOOT_ROOT}/uart_log.txt}"
@ -37,6 +38,89 @@ for cmd in expect socat; do
command -v "$cmd" &>/dev/null || { log_error "$cmd not found - install with: sudo apt install $cmd"; exit 1; }
done
# Load configuration from .config file
# Parses Makefile-style .config and sets global variables
# Handles both KEY=VALUE and KEY?=VALUE syntax (for ?=, only sets if not already set)
# Since .config uses Makefile syntax, we parse it directly rather than using make
load_config() {
local config_file="${1:-.config}"
[ ! -f "$config_file" ] && { log_error "Config file not found: $config_file"; return 1; }
# Extract variables from .config file
# Pattern: KEY?=VALUE or KEY=VALUE (ignores comments and blank lines)
while IFS= read -r line; do
# Skip comments and blank lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "${line// }" ]] && continue
# Extract key, conditional flag, and value
# Match: optional whitespace, key, optional ?, =, optional whitespace, value
if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*(\?)?=[[:space:]]*(.*)$ ]]; then
local key="${BASH_REMATCH[1]}"
local conditional="${BASH_REMATCH[2]}" # "?" if present
local value="${BASH_REMATCH[3]}"
# Remove surrounding quotes if present
value="${value#\"}"
value="${value%\"}"
# Strip trailing whitespace from value
value="${value%"${value##*[![:space:]]}"}"
# For ?= syntax, only set if variable is not already set
if [ -n "$conditional" ]; then
# Check if variable is already set using indirect reference
# ${!key} expands to the value of the variable named by $key
if [ -z "${!key:-}" ]; then
# Variable not set, assign it using declare
declare -g "${key}=${value}"
fi
else
# Always set for = syntax
declare -g "${key}=${value}"
fi
fi
done < <(grep -E '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*(\?)?=' "$config_file" 2>/dev/null || true)
# Export all config variables as globals
export IMAGE_HEADER_SIZE SIGN HASH SECONDARY_SIGN_OPTIONS SECONDARY_PRIVATE_KEY
# Calculate IMAGE_SIGNATURE_SIZE based on SIGN algorithm
case "${SIGN:-}" in
ECC256) IMAGE_SIGNATURE_SIZE=64 ;;
ECC384) IMAGE_SIGNATURE_SIZE=96 ;;
ECC521) IMAGE_SIGNATURE_SIZE=132 ;;
ED25519) IMAGE_SIGNATURE_SIZE=64 ;;
ED448) IMAGE_SIGNATURE_SIZE=114 ;;
RSA2048) IMAGE_SIGNATURE_SIZE=256 ;;
RSA3072) IMAGE_SIGNATURE_SIZE=384 ;;
RSA4096) IMAGE_SIGNATURE_SIZE=512 ;;
*) IMAGE_SIGNATURE_SIZE=96 ;; # Default to ECC384
esac
export IMAGE_SIGNATURE_SIZE
# Build SIGN_OPTIONS from SIGN and HASH
SIGN_OPTIONS=""
case "${SIGN:-}" in
ECC256) SIGN_OPTIONS="--ecc256" ;;
ECC384) SIGN_OPTIONS="--ecc384" ;;
ECC521) SIGN_OPTIONS="--ecc521" ;;
ED25519) SIGN_OPTIONS="--ed25519" ;;
ED448) SIGN_OPTIONS="--ed448" ;;
RSA2048) SIGN_OPTIONS="--rsa2048" ;;
RSA3072) SIGN_OPTIONS="--rsa3072" ;;
RSA4096) SIGN_OPTIONS="--rsa4096" ;;
esac
case "${HASH:-}" in
SHA256) SIGN_OPTIONS="$SIGN_OPTIONS --sha256" ;;
SHA384) SIGN_OPTIONS="$SIGN_OPTIONS --sha384" ;;
SHA3) SIGN_OPTIONS="$SIGN_OPTIONS --sha3" ;;
esac
export SIGN_OPTIONS
}
# Initialize UART capture variables
UART_PIDS=()
UART_PTY=""
@ -236,10 +320,12 @@ test_boot() {
# Check for test modes
FLASH_TEST_APP=false
FLASH_UPDATE_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 ;;
--test-update) FLASH_TEST_APP=true; FLASH_UPDATE_APP=true ;;
esac
# Build wolfBoot
@ -249,16 +335,63 @@ 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
if [ "$FLASH_UPDATE_APP" = "true" ]; then
log_info "Building and signing test application version 2..."
make test-app/image.bin
testapp_size=$(stat -c%s "test-app/image_v1_signed.bin")
log_info "Test app size: $testapp_size bytes"
# Sign as version 2 for update testing
# Load all config values from .config file
load_config .config
# 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"
IMAGE_TRAILER_SIZE=0 # Usually 0 unless delta updates are used
PRIVATE_KEY="${PRIVATE_KEY:-wolfboot_signing_private_key.der}"
BOOT_IMG="test-app/image.bin"
# Build sign command with environment variables
# The sign tool needs IMAGE_HEADER_SIZE and IMAGE_SIGNATURE_SIZE as environment variables
export IMAGE_HEADER_SIZE IMAGE_SIGNATURE_SIZE IMAGE_TRAILER_SIZE
log_info "Signing test app as version 2..."
log_info " IMAGE_HEADER_SIZE=$IMAGE_HEADER_SIZE"
log_info " IMAGE_SIGNATURE_SIZE=$IMAGE_SIGNATURE_SIZE"
log_info " SIGN=$SIGN"
log_info " HASH=$HASH"
log_info " SIGN_OPTIONS=$SIGN_OPTIONS"
log_info " PRIVATE_KEY=$PRIVATE_KEY"
# Sign the image as version 2
if [ "$SIGN" != "NONE" ] && [ -n "$SECONDARY_PRIVATE_KEY" ]; then
./tools/keytools/sign $SIGN_OPTIONS $SECONDARY_SIGN_OPTIONS "$BOOT_IMG" "$PRIVATE_KEY" "$SECONDARY_PRIVATE_KEY" 2 || {
log_error "Signing failed with secondary key"
exit 1
}
elif [ "$SIGN" != "NONE" ]; then
./tools/keytools/sign $SIGN_OPTIONS "$BOOT_IMG" "$PRIVATE_KEY" 2 || {
log_error "Signing failed"
exit 1
}
else
./tools/keytools/sign $SIGN_OPTIONS "$BOOT_IMG" 2 || {
log_error "Signing failed (SIGN=NONE)"
exit 1
}
fi
testapp_size=$(stat -c%s "test-app/image_v2_signed.bin")
log_info "Test app v2 size: $testapp_size bytes"
cp test-app/image_v2_signed.bin "${TFTP_DIR}/"
log_ok "Test app v2 copied to TFTP: ${TFTP_DIR}/image_v2_signed.bin"
else
log_info "Building and signing test application version 1..."
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"
cp test-app/image_v1_signed.bin "${TFTP_DIR}/"
log_ok "Test app copied to TFTP: ${TFTP_DIR}/image_v1_signed.bin"
fi
fi
# Generate BOOT.BIN
@ -271,7 +404,7 @@ export PREBUILT_DIR="${WOLFBOOT_ROOT}/../soc-prebuilt-firmware/vmk180-versal"
log_info "Copying prebuilt firmware files..."
[ ! -d "${PREBUILT_DIR}" ] && {
log_error "Prebuilt firmware directory not found: ${PREBUILT_DIR}"
log_info "Clone with: git clone --branch xlnx_rel_v2024.1 https://github.com/Xilinx/soc-prebuilt-firmware.git"
log_info "Clone with: git clone --branch xlnx_rel_v2024.2 https://github.com/Xilinx/soc-prebuilt-firmware.git"
exit 1
}
@ -283,7 +416,7 @@ cp "${PREBUILT_DIR}/system-default.dtb" .
# Generate BOOT.BIN from wolfBoot root directory
source "${VITIS_PATH}/settings64.sh"
bootgen -arch versal -image ./tools/scripts/vmk180/boot_wolfboot.bif -w -o BOOT.BIN
bootgen -arch versal -image ./tools/scripts/versal_boot.bif -w -o BOOT.BIN
# Copy BOOT.BIN to TFTP directory
cp BOOT.BIN "${TFTP_DIR}/"
@ -295,9 +428,15 @@ 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"
if [ "$FLASH_UPDATE_APP" = "true" ]; then
testapp_size=$(stat -c%s "${TFTP_DIR}/image_v2_signed.bin")
testapp_size_hex=$(printf "0x%x" $testapp_size)
log_info "Test app v2 size: $testapp_size bytes"
else
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
fi
# Flash QSPI via U-Boot TFTP
@ -310,6 +449,7 @@ set pty "$UART_PTY"
set filesize_hex "$filesize_hex"
set testapp_size_hex "$testapp_size_hex"
set flash_test_app "$FLASH_TEST_APP"
set flash_update_app "$FLASH_UPDATE_APP"
set board_ip "$BOARD_IP"
set server_ip "$SERVER_IP"
@ -452,33 +592,70 @@ puts "BOOT.BIN flash and verification complete!"
# Flash test app if requested
if { \$flash_test_app eq "true" } {
puts ""
puts "=== Flashing test app to boot partition at 0x800000 ==="
if { \$flash_update_app eq "true" } {
puts ""
puts "=== Flashing test app v2 to UPDATE partition at 0x3400000 ==="
puts "Downloading test app via TFTP..."
send "tftpboot 0x10000000 image_v1_signed.bin\r"
expect {
"Bytes transferred" { puts "TFTP download successful" }
"Error" { puts "TFTP download failed"; exit 1 }
timeout { puts "TFTP timeout"; exit 1 }
puts "Downloading test app v2 via TFTP..."
send "tftpboot 0x10000000 image_v2_signed.bin\r"
expect {
"Bytes transferred" { puts "TFTP download successful" }
"Error" { puts "TFTP download failed"; exit 1 }
timeout { puts "TFTP timeout"; exit 1 }
}
expect "Versal>"
puts "Erasing UPDATE partition at 0x3400000 (128KB sector)..."
send "sf erase 0x3400000 0x20000\r"
expect {
"Versal>" { puts "Erase complete" }
timeout { puts "Erase timeout"; exit 1 }
}
puts "Writing test app v2 to 0x3400000..."
send "sf write 0x10000000 0x3400000 \$testapp_size_hex\r"
expect {
"Versal>" { puts "Write complete" }
timeout { puts "Write timeout"; exit 1 }
}
puts "Test app v2 flashed to UPDATE partition!"
} else {
puts ""
puts "=== Flashing test app to boot partition at 0x800000 ==="
puts "Downloading test app via TFTP..."
send "tftpboot 0x10000000 image_v1_signed.bin\r"
expect {
"Bytes transferred" { puts "TFTP download successful" }
"Error" { puts "TFTP download failed"; exit 1 }
timeout { puts "TFTP timeout"; exit 1 }
}
expect "Versal>"
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 "Erasing update partition at 0x3400000 (128KB sector)..."
send "sf erase 0x3400000 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!"
}
expect "Versal>"
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 ""

View File

@ -1,133 +0,0 @@
# build_flash_qspi.sh
All-in-one script for building wolfBoot, generating BOOT.BIN, flashing QSPI, and booting VMK180.
wolfBoot replaces U-Boot in the Versal boot flow:
```
PLM (PMC) -> PSM -> BL31 (EL3) -> wolfBoot (EL2) -> Linux (EL1)
```
## Usage
```bash
# Full build, flash, and boot from QSPI
./build_flash_qspi.sh
# Test boot mode switching only (no build/flash)
./build_flash_qspi.sh --boot-sdcard # Test SD card boot mode
./build_flash_qspi.sh --boot-qspi # Test QSPI boot mode
```
## What It Does
1. **Builds wolfBoot**: Compiles wolfBoot from source
2. **Generates BOOT.BIN**:
- Copies prebuilt firmware files from `../soc-prebuilt-firmware/vmk180-versal/` to wolfBoot root
- Runs bootgen to create BOOT.BIN
- Copies BOOT.BIN to TFTP directory
3. **Flashes QSPI**:
- Sets board to SD card boot mode
- Captures UART output via PTY bridge
- Interrupts U-Boot autoboot
- Configures network (TFTP server/client)
- Downloads BOOT.BIN via TFTP
- Erases and programs QSPI flash
- Verifies flash contents
4. **Boots from QSPI**:
- Switches boot mode to QSPI
- Captures UART output for 30 seconds
## Prerequisites
- **Prebuilt firmware**: Clone `soc-prebuilt-firmware` repository:
```bash
git clone --branch xlnx_rel_v2024.1 https://github.com/Xilinx/soc-prebuilt-firmware.git
```
Place it as a sibling directory to wolfBoot (i.e., `../soc-prebuilt-firmware/`)
- **TFTP server**: Install and configure:
```bash
sudo apt install tftpd-hpa
sudo mkdir -p /srv/tftp
sudo chmod 777 /srv/tftp
```
- **Required tools**: `expect` and `socat`
```bash
sudo apt install expect socat
```
- **Vitis 2024.1 or 2024.2**: Required for bootgen
```bash
export VITIS_PATH=/opt/Xilinx/Vitis/2024.1
```
- **Relay board**: Connected to `/dev/ttyACM2` (configurable via `RELAY_PORT`)
- **UART connection**: VMK180 UART0 connected (default: `/dev/ttyUSB2`)
- **ARM Toolchain**: `aarch64-none-elf-gcc`
## Configuration
Environment variables can be customized:
```bash
export UART_PORT=/dev/ttyUSB2 # VMK180 UART port
export UART_BAUD=115200 # UART baud rate
export SERVER_IP=10.0.4.24 # TFTP server IP (host PC)
export BOARD_IP=10.0.4.90 # VMK180 IP address
export TFTP_DIR=/srv/tftp # TFTP directory
export VITIS_PATH=/opt/Xilinx/Vitis/2024.1 # Vitis installation
export RELAY_PORT=/dev/ttyACM2 # Relay board serial port
export UART_LOG=./uart_log.txt # UART log file
```
## UART Capture
The script automatically captures UART output:
- Creates a PTY bridge using `socat` for reliable capture
- Logs all output to `uart_log.txt` (default)
- Continues capturing after flash completes
- Press Ctrl+C to stop early (capture continues in background)
View live output:
```bash
tail -f uart_log.txt
```
## Troubleshooting
### Prebuilt firmware not found
Ensure `soc-prebuilt-firmware` is cloned as a sibling directory to wolfBoot:
```bash
cd ..
git clone --branch xlnx_rel_v2024.1 https://github.com/Xilinx/soc-prebuilt-firmware.git
```
### TFTP timeout
- Check network connection between host PC and VMK180
- Verify IP addresses match (`SERVER_IP` and `BOARD_IP`)
- Ensure TFTP server is running: `sudo systemctl status tftpd-hpa`
### UART capture fails
- Verify UART port permissions: `sudo chmod 666 /dev/ttyUSB*`
- Check UART port is correct: `ls -la /dev/ttyUSB*`
- Ensure no other process is using the port
### Relay control fails
- Check relay board connection and port (`RELAY_PORT`)
- Verify relay board is powered and connected
- Check serial port permissions: `sudo chmod 666 /dev/ttyACM*`
### Boot hangs or wolfBoot doesn't start
- Verify BL31 is correctly jumping to 0x8000000
- Check that wolfBoot entry point matches: `aarch64-none-elf-readelf -h wolfboot.elf`
- Check UART output for error messages
- Verify prebuilt firmware files are correct for your board revision