Skip to content

Add MPFS250 QSPI support#677

Open
dgarske wants to merge 1 commit intowolfSSL:masterfrom
dgarske:polarfire_soc_qspi
Open

Add MPFS250 QSPI support#677
dgarske wants to merge 1 commit intowolfSSL:masterfrom
dgarske:polarfire_soc_qspi

Conversation

@dgarske
Copy link
Contributor

@dgarske dgarske commented Jan 29, 2026

Summary

  • Add MPFS250 QSPI support with two access paths: direct MSS QSPI controller and System Controller SPI services.
  • Introduce a QSPI-focused PolarFire config and extend the HAL/external-flash APIs for QSPI read/write/erase and SCB mailbox reads.
  • Tweak RISC-V build flow to select update loader based on disk usage and sign ELF outputs when ELF mode is enabled.
  • Add a PolarFire programming helper script and document QSPI usage under PolarFire targets.

Key Changes

  • New example config: config/examples/polarfire_mpfs250_qspi.config
  • QSPI init + drivers in hal/mpfs250.c (MSS QSPI + SC SPI paths)
  • Build logic updates in arch.mk for update loader selection + ELF signing
  • PolarFire QSPI documentation in docs/Targets.md
  • New tooling script: tools/scripts/mpfs_program.sh

Testing

  • Example output (with DEBUG_QSPI set):
wolfBoot Version: 2.7.0 (Jan 29 2026 13:03:10)
QSPI: Using System Controller SPI services
SC SPI: Read 0x20000 -> 0x80013680, len 512
SC SPI: Read 0x2000000 -> 0x80013680, len 512
Versions: Boot 1, Update 0
Trying Boot partition at 0x20000
Loading header 512 bytes from 0x20000 to 0x8DFFFE00
SC SPI: Read 0x20000 -> 0x8DFFFE00, len 512
Loading image 128664 bytes from 0x20200 to 0x8E000000...
SC SPI: Read 0x20200 -> 0x8E000000, len 128664
done
Boot partition: 0x8DFFFE00 (sz 128664, ver 0x1, type 0x601)
Checking integrity...done
Verifying signature...done
Successfully selected image in part: 0
Firmware Valid
SC SPI: Read 0x1FFFFFC -> 0x80013880, len 4
Loading elf at 0x8E000000
Found valid elf64 (little endian)
Program Headers 1 (size 56)
Load 21304 bytes (offset 0x1000) to 0x8E000000 (p 0x8E000000)
Clear 16392 bytes at 0x8E000000 (p 0x8E000000)
Entry point 0x8E0012B8
Booting at 0x8E0012B8
FDT: Invalid header! -1
QSPI: Using System Controller SPI services
========================
PolarFire SoC MPFS250 wolfBoot demo Application
Copyright 2025 wolfSSL Inc
GPL v3
========================

@dgarske dgarske self-assigned this Jan 29, 2026
@dgarske dgarske force-pushed the polarfire_soc_qspi branch 2 times, most recently from 633e7f8 to 5e963dc Compare January 30, 2026 22:19
@dgarske dgarske assigned danielinux and wolfSSL-Bot and unassigned dgarske Jan 30, 2026
@dgarske dgarske requested a review from danielinux January 30, 2026 22:19
Copy link
Member

@danielinux danielinux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, some minor visibility issues

@dgarske dgarske force-pushed the polarfire_soc_qspi branch from 5e963dc to 2f8ce14 Compare February 4, 2026 00:20
@dgarske dgarske requested a review from danielinux February 4, 2026 00:22
@dgarske dgarske assigned danielinux and unassigned danielinux Feb 4, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +451 to +452
/* Wait for controller to be ready */
while (!(QSPI_STATUS & QSPI_STATUS_READY));
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I guess this can safely be a wontfix)

return -2;
}
QSPI_TX_DATA = cmd[i];
__asm__ __volatile__("fence o,i" ::: "memory");
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
__asm__ __volatile__("fence o,i" ::: "memory");
__asm__ __volatile__("fence iorw, iorw" ::: "memory");

Copilot uses AI. Check for mistakes.
}
/* Wait for receive complete */
timeout = 1000000;
while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
}
/* Wait for transmit complete */
timeout = 100000;
while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
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@
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
IRAM (rx) :ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@
IRAM (rwx) :ORIGIN = @WOLFBOOT_LOAD_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +70
/* use from libwolfboot.c */
_stored_data = _start_data;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_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(...)).

Suggested change
/* use from libwolfboot.c */
_stored_data = _start_data;
/* use from libwolfboot.c: _stored_data now refers to LOADADDR(.data) */

Copilot uses AI. Check for mistakes.
Comment on lines 162 to 165
/* System Service command opcodes */
#define SYS_SERV_CMD_SERIAL_NUMBER 0x00u
#define SYS_SERV_CMD_SPI_COPY 0x50u

Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@danielinux danielinux assigned dgarske and unassigned danielinux and wolfSSL-Bot Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants