Conversation
633e7f8 to
5e963dc
Compare
danielinux
left a comment
There was a problem hiding this comment.
Looks good, some minor visibility issues
5e963dc to
2f8ce14
Compare
There was a problem hiding this comment.
Pull request overview
Adds QSPI NOR flash support for PolarFire SoC MPFS250 and updates the RISC-V/PolarFire boot flow to better support RAM-loaded (NO_XIP) and ELF-based images.
Changes:
- Introduces MPFS250 QSPI NOR driver + EXT_FLASH integration (including SC QSPI vs MSS QSPI selection).
- Updates RISC-V trap/vector handling to switch between S-mode and M-mode via
WOLFBOOT_RISCV_MMODE. - Adjusts build/link flow for RISC-V (update loader selection based on disk config; sign ELF when
ELF=1) and documents PolarFire QSPI usage.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
hal/mpfs250.c |
Implements QSPI init + transfer/read/write/erase and hooks it into hal_init() when EXT_FLASH is enabled. |
hal/mpfs250.h |
Adds SCB mailbox helpers/constants and CoreQSPI v2 register/flash command definitions under EXT_FLASH. |
hal/riscv.h |
Centralizes privilege-mode selection helpers and adds an I-cache sync helper for RISC-V. |
src/elf.c |
Uses memmove() for safer in-place ELF segment loading and syncs I-cache on RISC-V. |
src/vector_riscv.S |
Switches S-mode/M-mode selection logic to WOLFBOOT_RISCV_MMODE. |
src/boot_riscv_start.S |
Uses MODE_PREFIX from hal/riscv.h and updates mode checks to WOLFBOOT_RISCV_MMODE. |
src/boot_riscv.c |
Updates satp/MMU teardown conditional for S-mode vs M-mode. |
test-app/vector_riscv.S |
Adds RV64 trap entry/exit macros and uses sret vs mret based on WOLFBOOT_RISCV_MMODE. |
test-app/startup_riscv.c |
Sets stvec vs mtvec and reads scause vs mcause based on privilege mode. |
test-app/RISCV64-mpfs250.ld |
Adjusts placement for RAM boot / NO_XIP style execution and changes where .data/.bss live. |
arch.mk |
Chooses update loader based on disk enablement and switches signing target to .elf when ELF=1. |
docs/Targets.md |
Documents PolarFire QSPI usage and build-time selection between MSS QSPI and SC QSPI. |
config/examples/polarfire_mpfs250_qspi.config |
New example configuration for MPFS250 QSPI + NO_XIP + ELF flows. |
config/examples/polarfire_mpfs250.config |
Clarifies EXT_FLASH meaning for PolarFire MPFS250. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /* Wait for controller to be ready */ | ||
| while (!(QSPI_STATUS & QSPI_STATUS_READY)); |
There was a problem hiding this comment.
qspi_init() waits for QSPI_STATUS_READY with an unbounded busy-loop (while (!(QSPI_STATUS & QSPI_STATUS_READY));). If the controller never becomes ready (clock/reset misconfig, missing flash, SC left it wedged), wolfBoot will hang permanently. Add a bounded timeout and propagate/handle the error (e.g., disable EXT_FLASH path or wolfBoot_panic() with a clear message).
There was a problem hiding this comment.
(I guess this can safely be a wontfix)
| return -2; | ||
| } | ||
| QSPI_TX_DATA = cmd[i]; | ||
| __asm__ __volatile__("fence o,i" ::: "memory"); |
There was a problem hiding this comment.
The fence o,i after writing QSPI_TX_DATA does not order MMIO writes against subsequent MMIO reads of QSPI_STATUS (it orders device output before instruction fetch). If you need to prevent posted TX FIFO stores from being observed late on RVWMO, use an appropriate fence for IO ordering (e.g., fence iorw, iorw or at least one that orders prior writes before subsequent reads).
| __asm__ __volatile__("fence o,i" ::: "memory"); | |
| __asm__ __volatile__("fence iorw, iorw" ::: "memory"); |
| } | ||
| /* Wait for receive complete */ | ||
| timeout = 1000000; | ||
| while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout); |
There was a problem hiding this comment.
In the read path, the RXDONE wait loop doesn't check whether it timed out. If RXDONE never asserts, the function still returns success, which can silently corrupt reads. After the while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout); loop, check timeout == 0 and return an error.
| while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout); | |
| while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout); | |
| if (timeout == 0) { | |
| wolfBoot_printf("QSPI: RXDONE timeout\n"); | |
| return -5; | |
| } |
| } | ||
| /* Wait for transmit complete */ | ||
| timeout = 100000; | ||
| while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout); |
There was a problem hiding this comment.
In the write path, the TXDONE wait loop doesn't check whether it timed out. If TXDONE never asserts, qspi_transfer_block() returns 0 anyway, which can make callers believe a program/erase completed when it didn't. After the while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout); loop, check timeout == 0 and return an error.
| while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout); | |
| while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout); | |
| if (timeout == 0) { | |
| wolfBoot_printf("QSPI: TXDONE timeout, status=0x%x\n", QSPI_STATUS); | |
| return -5; | |
| } |
| IRAM (rx) :ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ | ||
| /* For RAM boot (NO_XIP), use WOLFBOOT_LOAD_ADDRESS for execution | ||
| * For XIP boot, use WOLFBOOT_TEST_APP_ADDRESS (partition + header) */ | ||
| IRAM (rx) :ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ |
There was a problem hiding this comment.
The IRAM memory region is declared as (rx) but this script places writable sections there (.data, .bss, heap/stack). With GNU ld this can produce permission mismatch warnings/errors and is conceptually incorrect. Either mark IRAM as writable (rwx) or keep writable sections in a RAM region (DDR/LSRAM) while keeping .text in an executable region.
| IRAM (rx) :ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ | |
| IRAM (rwx) :ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ |
| /* use from libwolfboot.c */ | ||
| _stored_data = _start_data; |
There was a problem hiding this comment.
_stored_data is assigned twice: first to LOADADDR(.data) and later overwritten with _start_data. Overwriting it removes the ability for startup code to copy initialized data from a distinct load address (e.g., XIP or ROM->RAM init). If the intent is “no copy needed for RAM-boot”, consider removing the symbol override (or guarding it behind NO_XIP) and keep a consistent LMA/VMA model (.data : AT(...)).
| /* use from libwolfboot.c */ | |
| _stored_data = _start_data; | |
| /* use from libwolfboot.c: _stored_data now refers to LOADADDR(.data) */ |
| /* System Service command opcodes */ | ||
| #define SYS_SERV_CMD_SERIAL_NUMBER 0x00u | ||
| #define SYS_SERV_CMD_SPI_COPY 0x50u | ||
|
|
There was a problem hiding this comment.
The PR description mentions a “System Controller SPI services” access path, but the codebase doesn’t appear to implement an SCB mailbox-based SPI copy/read path (this opcode is defined here but unused). Either implement and wire up the SCB SPI service flow (likely using SYS_SERV_CMD_SPI_COPY) or remove the unused opcode/claims and align the documentation/testing notes with the actual direct-register SC QSPI implementation.
Summary
Key Changes
config/examples/polarfire_mpfs250_qspi.confighal/mpfs250.c(MSS QSPI + SC SPI paths)arch.mkfor update loader selection + ELF signingdocs/Targets.mdtools/scripts/mpfs_program.shTesting