/* mpfs250.c * * Copyright (C) 2026 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 */ /* Microchip PolarFire SoC MPFS250T HAL for wolfBoot */ /* Supports: * RISC-V 64-bit architecture * External flash operations * UART communication * System initialization */ #include #include #include #include "target.h" #include "mpfs250.h" #include "riscv.h" #include "image.h" #ifndef ARCH_RISCV64 # error "wolfBoot mpfs250 HAL: wrong architecture selected. Please compile with ARCH=RISCV64." #endif #include "printf.h" #include "loader.h" #include "hal.h" #include "gpt.h" #include "fdt.h" /* UART base addresses for per-hart access (LO addresses, M-mode compatible) */ const unsigned long MSS_UART_BASE_ADDR[5] = { MSS_UART0_LO_BASE, /* Hart 0 (E51) */ MSS_UART1_LO_BASE, /* Hart 1 (U54_1) */ MSS_UART2_LO_BASE, /* Hart 2 (U54_2) */ MSS_UART3_LO_BASE, /* Hart 3 (U54_3) */ MSS_UART4_LO_BASE, /* Hart 4 (U54_4) */ }; #if defined(DISK_SDCARD) || defined(DISK_EMMC) #include "sdhci.h" /* Forward declaration of SDHCI IRQ handler */ extern void sdhci_irq_handler(void); #endif /* Video Kit DDR/Clock configuration is included in mpfs250.h */ /* Configure L2 cache: enable ways 0,1,3 (0x0B) and set way masks for all masters */ #ifdef WOLFBOOT_RISCV_MMODE static void mpfs_config_l2_cache(void) { L2_WAY_ENABLE = 0x0B; /* ways 0, 1, 3 — matches DDR demo config */ SYSREG_L2_SHUTDOWN_CR = 0; L2_WAY_MASK_DMA = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_AXI4_PORT0 = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_AXI4_PORT1 = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_AXI4_PORT2 = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_AXI4_PORT3 = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_E51_DCACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_E51_ICACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_1_DCACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_1_ICACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_2_DCACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_2_ICACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_3_DCACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_3_ICACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_4_DCACHE = L2_WAY_MASK_CACHE_ONLY; L2_WAY_MASK_U54_4_ICACHE = L2_WAY_MASK_CACHE_ONLY; __asm__ volatile("fence iorw, iorw" ::: "memory"); } /* Busy-loop delay — MTIME not running in M-mode without HSS. * E51 at 80 MHz reset: ~8 iters/us accounting for loop overhead. */ static __attribute__((noinline)) void udelay(uint32_t us) { volatile uint32_t i; for (i = 0; i < us * 8; i++) ; } #endif /* WOLFBOOT_RISCV_MMODE */ /* Multi-Hart Support */ #ifdef WOLFBOOT_RISCV_MMODE extern uint8_t _main_hart_hls; /* linker-provided address symbol; typed as uint8_t to avoid size confusion */ /* Watchdog timeout configuration. * WATCHDOG=0 (default): WDT disabled in hal_init() then restored to boot * ROM defaults in hal_prepare_boot() before do_boot. * WATCHDOG=1: WDT kept enabled with WATCHDOG_TIMEOUT_MS during wolfBoot. * Verify is bounded at ~5s; default 30s leaves ample headroom and avoids * the need to pet the WDT during the long ECDSA verify call. */ #ifdef WATCHDOG # ifndef WATCHDOG_TIMEOUT_MS # define WATCHDOG_TIMEOUT_MS 30000U # endif /* MPFS MSS WDT clock is AHB / 256 ≈ 150 MHz / 256 ≈ 585 kHz at S-mode rate * but ~80 MHz / 256 ≈ 312 kHz on E51 reset clocks. Use a conservative * 300 ticks/ms; the actual rate may be a bit higher but a slightly longer * timeout is safe. Caller can override WATCHDOG_TIMEOUT_MS at build time. */ # define WATCHDOG_TIMEOUT_TICKS ((WATCHDOG_TIMEOUT_MS) * 300U) #endif /* Saved boot ROM watchdog value, restored in hal_prepare_boot() */ static uint32_t mpfs_wdt_default_mvrp = 0; /* CLINT MSIP register for IPI delivery */ #define CLINT_MSIP_REG(hart) (*(volatile uint32_t*)(CLINT_BASE + (hart) * 4)) /* Signal secondary harts that E51 (main hart) is ready. */ static void mpfs_signal_main_hart_started(void) { HLS_DATA* hls = (HLS_DATA*)&_main_hart_hls; hls->in_wfi_indicator = HLS_MAIN_HART_STARTED; hls->my_hart_id = MPFS_FIRST_HART; __asm__ volatile("fence iorw, iorw" ::: "memory"); } /* Wake secondary U54 harts by sending software IPIs via CLINT MSIP. */ int mpfs_wake_secondary_harts(void) { int hart_id; int woken_count = 0; wolfBoot_printf("Waking secondary harts...\n"); for (hart_id = MPFS_FIRST_U54_HART; hart_id <= MPFS_LAST_U54_HART; hart_id++) { CLINT_MSIP_REG(hart_id) = 0x01; __asm__ volatile("fence iorw, iorw" ::: "memory"); udelay(1000); woken_count++; } wolfBoot_printf("Woke %d secondary harts\n", woken_count); return woken_count; } /* Secondary hart (U54) entry: init per-hart UART and spin in WFI for Linux/SBI. */ void secondary_hart_entry(unsigned long hartid, HLS_DATA* hls) { char msg[] = "Hart X: Woken, waiting for Linux boot...\n"; (void)hls; uart_init_hart(hartid); msg[5] = '0' + (char)hartid; uart_write_hart(hartid, msg, sizeof(msg) - 1); while (1) __asm__ volatile("wfi"); } #endif /* WOLFBOOT_RISCV_MMODE */ #if defined(EXT_FLASH) && defined(TEST_EXT_FLASH) && defined(__WOLFBOOT) static int test_ext_flash(void); #endif #if defined(EXT_FLASH) && defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT) static void qspi_uart_program(void); #endif void hal_init(void) { #ifdef WOLFBOOT_RISCV_MMODE /* Capture boot ROM WDT default for restoration in hal_prepare_boot() */ mpfs_wdt_default_mvrp = MSS_WDT_MVRP(MSS_WDT_E51_BASE); #ifndef WATCHDOG /* WATCHDOG=0 (default): disable WDT for the duration of wolfBoot. * It will be re-enabled in hal_prepare_boot() before do_boot. */ MSS_WDT_CONTROL(MSS_WDT_E51_BASE) &= ~MSS_WDT_CTRL_ENABLE; #else /* WATCHDOG=1: keep WDT enabled with a generous timeout for crypto. * Verify is bounded at ~5s; configure a much larger timeout so we * never have to pet the WDT during ECDSA verify. */ MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU; MSS_WDT_MVRP(MSS_WDT_E51_BASE) = WATCHDOG_TIMEOUT_TICKS; #endif mpfs_config_l2_cache(); mpfs_signal_main_hart_started(); #endif #ifdef DEBUG_UART SYSREG_SUBBLK_CLOCK_CR |= (MSS_PERIPH_MMUART0 << DEBUG_UART_PORT); SYSREG_SOFT_RESET_CR &= ~(MSS_PERIPH_MMUART0 << DEBUG_UART_PORT); uart_init(); #endif #ifdef WOLFBOOT_REPRODUCIBLE_BUILD wolfBoot_printf("wolfBoot Version: %s\n", LIBWOLFBOOT_VERSION_STRING); #else wolfBoot_printf("wolfBoot Version: %s (%s %s)\n", LIBWOLFBOOT_VERSION_STRING, __DATE__, __TIME__); #endif #ifdef WOLFBOOT_RISCV_MMODE wolfBoot_printf("Running on E51 (hart 0) in M-mode\n"); #endif #ifdef EXT_FLASH if (qspi_init() != 0) { wolfBoot_printf("QSPI: Init failed\n"); } else { #if defined(TEST_EXT_FLASH) && defined(__WOLFBOOT) test_ext_flash(); #endif #if defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT) qspi_uart_program(); #endif } #endif /* EXT_FLASH */ } /* System Controller Mailbox */ static int mpfs_scb_mailbox_busy(void) { return (SCBCTRL_REG(SERVICES_SR_OFFSET) & SERVICES_SR_BUSY_MASK); } /* Read 16-byte device serial number via SCB system service (opcode 0x00). */ int mpfs_read_serial_number(uint8_t *serial) { uint32_t cmd, status; int i, timeout; if (serial == NULL) { return -1; } /* Check if mailbox is busy */ if (mpfs_scb_mailbox_busy()) { wolfBoot_printf("SCB mailbox busy\n"); return -2; } /* Send serial number request command (opcode 0x00) * Command format: [31:16] = opcode, [0] = request bit */ cmd = (SYS_SERV_CMD_SERIAL_NUMBER << SERVICES_CR_COMMAND_SHIFT) | SERVICES_CR_REQ_MASK; SCBCTRL_REG(SERVICES_CR_OFFSET) = cmd; /* Wait for request bit to clear (command accepted) */ timeout = MPFS_SCB_TIMEOUT; while ((SCBCTRL_REG(SERVICES_CR_OFFSET) & SERVICES_CR_REQ_MASK) && timeout > 0) { timeout--; } if (timeout == 0) { wolfBoot_printf("SCB mailbox request timeout\n"); return -3; } /* Wait for busy bit to clear (command completed) */ timeout = MPFS_SCB_TIMEOUT; while (mpfs_scb_mailbox_busy() && timeout > 0) { timeout--; } if (timeout == 0) { wolfBoot_printf("SCB mailbox busy timeout\n"); return -4; } /* Check status (upper 16 bits of status register) */ status = (SCBCTRL_REG(SERVICES_SR_OFFSET) >> SERVICES_SR_STATUS_SHIFT) & 0xFFFF; if (status != 0) { wolfBoot_printf("SCB mailbox error: 0x%x\n", status); return -5; } /* Read serial number from mailbox RAM (16 bytes) */ for (i = 0; i < DEVICE_SERIAL_NUMBER_SIZE; i++) { serial[i] = SCBMBOX_BYTE(i); } return 0; } /* Linux kernel command line arguments */ #ifndef LINUX_BOOTARGS #ifndef LINUX_BOOTARGS_ROOT #define LINUX_BOOTARGS_ROOT "/dev/mmcblk0p4" #endif #define LINUX_BOOTARGS \ "earlycon root="LINUX_BOOTARGS_ROOT" rootwait uio_pdrv_genirq.of_id=generic-uio" #endif /* Microchip OUI (Organizationally Unique Identifier) for MAC address */ #define MICROCHIP_OUI_0 0x00 #define MICROCHIP_OUI_1 0x04 #define MICROCHIP_OUI_2 0xA3 int hal_dts_fixup(void* dts_addr) { int off, ret; struct fdt_header *fdt = (struct fdt_header *)dts_addr; uint8_t device_serial_number[DEVICE_SERIAL_NUMBER_SIZE]; uint8_t mac_addr[6]; /* Verify FDT header */ ret = fdt_check_header(dts_addr); if (ret != 0) { wolfBoot_printf("FDT: Invalid header! %d\n", ret); return ret; } wolfBoot_printf("FDT: Version %d, Size %d\n", fdt_version(fdt), fdt_totalsize(fdt)); /* Expand total size to allow adding/modifying properties */ fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512); /* Find /chosen node */ off = fdt_find_node_offset(fdt, -1, "chosen"); if (off < 0) { /* Create /chosen node if it doesn't exist */ off = fdt_add_subnode(fdt, 0, "chosen"); } if (off >= 0) { /* Set bootargs property */ fdt_fixup_str(fdt, off, "chosen", "bootargs", LINUX_BOOTARGS); } /* Read device serial number from system controller */ ret = mpfs_read_serial_number(device_serial_number); if (ret != 0) { wolfBoot_printf("FDT: Failed to read serial number (%d)\n", ret); /* Continue without setting MAC addresses */ return 0; } wolfBoot_printf("FDT: Device serial: %02x%02x%02x%02x-%02x%02x%02x%02x-" "%02x%02x%02x%02x-%02x%02x%02x%02x\n", device_serial_number[15], device_serial_number[14], device_serial_number[13], device_serial_number[12], device_serial_number[11], device_serial_number[10], device_serial_number[9], device_serial_number[8], device_serial_number[7], device_serial_number[6], device_serial_number[5], device_serial_number[4], device_serial_number[3], device_serial_number[2], device_serial_number[1], device_serial_number[0]); /* Build MAC address: Microchip OUI + lower 3 bytes of serial number * Format: {0x00, 0x04, 0xA3, serial[2], serial[1], serial[0]} */ mac_addr[0] = MICROCHIP_OUI_0; mac_addr[1] = MICROCHIP_OUI_1; mac_addr[2] = MICROCHIP_OUI_2; mac_addr[3] = device_serial_number[2]; mac_addr[4] = device_serial_number[1]; mac_addr[5] = device_serial_number[0]; wolfBoot_printf("FDT: MAC0 = %02x:%02x:%02x:%02x:%02x:%02x\n", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); /* Set local-mac-address for ethernet@20110000 (mac0) */ off = fdt_find_node_offset(fdt, -1, "ethernet@20110000"); if (off >= 0) { ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6); if (ret != 0) { wolfBoot_printf("FDT: Failed to set mac0 address (%d)\n", ret); } } else { wolfBoot_printf("FDT: ethernet@20110000 not found\n"); } /* Set local-mac-address for ethernet@20112000 (mac1) * Use MAC address + 1 for the second interface */ mac_addr[5] = device_serial_number[0] + 1; wolfBoot_printf("FDT: MAC1 = %02x:%02x:%02x:%02x:%02x:%02x\n", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); off = fdt_find_node_offset(fdt, -1, "ethernet@20112000"); if (off >= 0) { ret = fdt_setprop(fdt, off, "local-mac-address", mac_addr, 6); if (ret != 0) { wolfBoot_printf("FDT: Failed to set mac1 address (%d)\n", ret); } } else { wolfBoot_printf("FDT: ethernet@20112000 not found\n"); } return 0; } void hal_prepare_boot(void) { #ifdef WOLFBOOT_RISCV_MMODE /* Restore boot ROM WDT default so the application sees a normal WDT. * Refresh first so the timer doesn't fire immediately after we apply * the new MVRP. Re-enable in case it was disabled by hal_init(). */ MSS_WDT_REFRESH(MSS_WDT_E51_BASE) = 0xDEADC0DEU; MSS_WDT_MVRP(MSS_WDT_E51_BASE) = mpfs_wdt_default_mvrp; MSS_WDT_CONTROL(MSS_WDT_E51_BASE) |= MSS_WDT_CTRL_ENABLE; #endif /* reset the eMMC/SD card? */ } void RAMFUNCTION hal_flash_unlock(void) { } void RAMFUNCTION hal_flash_lock(void) { } int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) { (void)address; (void)data; (void)len; return 0; } int RAMFUNCTION hal_flash_erase(uint32_t address, int len) { (void)address; (void)len; return 0; } /* Wait for SCB register bits to clear, with timeout */ static int mpfs_scb_wait_clear(uint32_t reg_offset, uint32_t mask, uint32_t timeout) { while ((SCBCTRL_REG(reg_offset) & mask) && --timeout) ; return (timeout == 0) ? -1 : 0; } #ifdef EXT_FLASH /* ========================================================================== * QSPI Flash Controller Implementation * * Both MSS QSPI (0x21000000) and SC QSPI (0x37020100) use CoreQSPI v2 * with identical register layouts. The controller is selected at build * time via MPFS_SC_SPI, which changes QSPI_BASE in the header. * ========================================================================== */ /* Microsecond delay using RISC-V time CSR (1 MHz tick rate) */ #ifndef WOLFBOOT_RISCV_MMODE static void udelay(uint32_t us) { uint64_t start = csr_read(time); while ((uint64_t)(csr_read(time) - start) < us) ; } #endif /* Forward declarations */ static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd, uint32_t cmd_len, uint8_t *data, uint32_t data_len, uint8_t dummy_cycles); static int qspi_read_id(uint8_t *id_buf); static int qspi_enter_4byte_mode(void); /* Send Release from Deep Power-Down / Wake up command */ static void qspi_flash_wakeup(void) { uint8_t cmd = 0xAB; /* Release from Deep Power-Down */ qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0); /* Flash needs tRES1 (3us typ) to wake up */ udelay(10); } int qspi_init(void) { uint8_t id[3]; uint32_t timeout; #ifdef MPFS_SC_SPI wolfBoot_printf("QSPI: Using SC QSPI Controller (0x%x)\n", QSPI_BASE); /* Wait for system controller to finish any pending operations before * taking direct control of the SC QSPI peripheral */ mpfs_scb_wait_clear(SERVICES_SR_OFFSET, SERVICES_SR_BUSY_MASK, QSPI_TIMEOUT_TRIES); #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Initial CTRL=0x%x, STATUS=0x%x, DIRECT=0x%x\n", QSPI_CONTROL, QSPI_STATUS, QSPI_DIRECT); #endif /* Disable direct access / XIP mode (SC may have left it enabled) */ QSPI_DIRECT = 0; #else wolfBoot_printf("QSPI: Using MSS QSPI Controller (0x%x)\n", QSPI_BASE); /* Enable QSPI peripheral clock (MSS only) */ SYSREG_SUBBLK_CLOCK_CR |= SYSREG_SUBBLK_CLOCK_CR_QSPI; udelay(1); /* Release MSS QSPI from reset (MSS only) */ SYSREG_SOFT_RESET_CR &= ~SYSREG_SOFT_RESET_CR_QSPI; udelay(10); #endif /* Disable controller before configuration */ QSPI_CONTROL = 0; /* Disable all interrupts */ QSPI_IEN = 0; /* Configure QSPI Control Register: * - Clock divider for ~5MHz (conservative) * - CPOL=1 (clock idle high) for SPI Mode 3 * - Sample on SCK edge * - Enable controller */ QSPI_CONTROL = (QSPI_CLK_DIV_30 << QSPI_CTRL_CLKRATE_OFFSET) | QSPI_CTRL_CLKIDLE | QSPI_CTRL_SAMPLE_SCK | QSPI_CTRL_EN; /* Wait for controller to be ready */ timeout = QSPI_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_READY) && --timeout); if (timeout == 0) { wolfBoot_printf("QSPI: Controller not ready\n"); return -1; } /* Wake up flash from deep power-down (if applicable) */ qspi_flash_wakeup(); /* Read and display JEDEC ID for verification */ if (qspi_read_id(id) == 0) { wolfBoot_printf("QSPI: Flash ID = 0x%02x 0x%02x 0x%02x\n", id[0], id[1], id[2]); } /* Enter 4-byte addressing mode for >16MB flash */ qspi_enter_4byte_mode(); return 0; } /* QSPI Block Transfer Function * Modeled after Microchip's MSS_QSPI_polled_transfer_block reference driver. * * read_mode: 0=write (QSPI_MODE_WRITE), 1=read (QSPI_MODE_READ) * cmd: Command buffer (opcode + address bytes) * cmd_len: Length of command (opcode + address, NOT including opcode separately) * data: Data buffer for read/write * data_len: Length of data phase * dummy_cycles: Number of idle cycles between command and data phase */ static int qspi_transfer_block(uint8_t read_mode, const uint8_t *cmd, uint32_t cmd_len, uint8_t *data, uint32_t data_len, uint8_t dummy_cycles) { uint32_t total_bytes = cmd_len + data_len; uint32_t frames; uint32_t i; uint32_t timeout; uint32_t frame_cmd; /* Wait for controller to be ready before starting */ timeout = QSPI_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_READY) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Timeout waiting for READY\n"); #endif return -1; } /* Drain RX FIFO of any stale data from previous transfers. */ timeout = QSPI_TIMEOUT_TRIES; while ((QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout) { (void)QSPI_RX_DATA; } #ifdef DEBUG_QSPI if (timeout == 0) { /* log warning and continue trying to transfer data */ wolfBoot_printf("QSPI: Timeout draining RX FIFO\n"); } #endif /* Configure FRAMES register: * - Total bytes: command + data (idle cycles handled by hardware) * - Command bytes: TX-only bytes before data phase * - Idle cycles: inserted by hardware between command and data * - FBYTE: status flags (RXAVAIL/TXAVAIL) refer to individual bytes * * For write-mode transfers, set CMDBYTES = TOTALBYTES so the entire * transfer occurs in the command phase (TX-only). The CoreQSPI data * phase shifts TX FIFO output by a fixed offset on writes, causing * data rotation in the programmed page. Keeping everything in the * command phase avoids this. The flash determines command vs data * boundaries from the opcode, not the controller's phase. */ frame_cmd = read_mode ? cmd_len : total_bytes; frames = ((total_bytes & 0xFFFF) << QSPI_FRAMES_TOTALBYTES_OFFSET) | ((frame_cmd & 0x1FF) << QSPI_FRAMES_CMDBYTES_OFFSET) | ((dummy_cycles & 0xF) << QSPI_FRAMES_IDLE_OFFSET) | (1u << QSPI_FRAMES_FBYTE_OFFSET); QSPI_FRAMES = frames; /* Send command bytes (opcode + address). * Use TXAVAIL (bit 3) to check for FIFO space -- CoreQSPI v2 does NOT * have a TXFULL status bit (bit 5 is reserved/always 0). * A fence (iorw, iorw) after each TX write ensures the store reaches the * peripheral before we read STATUS again (RISC-V RVWMO allows posted * stores that could cause stale TXAVAIL reads and FIFO overflow). */ for (i = 0; i < cmd_len; i++) { timeout = QSPI_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_TXAVAIL) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: TX FIFO full timeout\n"); #endif return -2; } QSPI_TX_DATA = cmd[i]; QSPI_IO_FENCE(); } if (read_mode) { /* Read mode: poll RXAVAIL for each data byte. */ for (i = 0; i < data_len; i++) { timeout = QSPI_RX_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_RXAVAIL) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: RX timeout at byte %d, status=0x%x\n", i, QSPI_STATUS); #endif return -3; } data[i] = QSPI_RX_DATA; } /* Wait for receive complete */ timeout = QSPI_RX_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_RXDONE) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: RXDONE timeout\n"); #endif return -5; } } else { /* Write mode: send data bytes. * Must push bytes without delay -- any gap causes FIFO underflow * since CoreQSPI continues clocking with empty FIFO. * Fence (iorw, iorw) after each write ensures the store reaches the * FIFO before we re-read STATUS (prevents FIFO overflow from posted * stores). */ if (data && data_len > 0) { for (i = 0; i < data_len; i++) { timeout = QSPI_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_TXAVAIL) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: TX data timeout\n"); #endif return -4; } QSPI_TX_DATA = data[i]; QSPI_IO_FENCE(); } } /* Wait for transmit complete */ timeout = QSPI_TIMEOUT_TRIES; while (!(QSPI_STATUS & QSPI_STATUS_TXDONE) && --timeout); if (timeout == 0) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: TXDONE timeout, status=0x%x\n", QSPI_STATUS); #endif return -5; } } #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: cmd[0]=0x%x, cmd_len=%d, data_len=%d, frames=0x%x\n", cmd[0], cmd_len, data_len, frames); #endif return 0; } /* Read JEDEC ID from flash */ static int qspi_read_id(uint8_t *id_buf) { uint8_t cmd = QSPI_CMD_READ_ID_OPCODE; return qspi_transfer_block(QSPI_MODE_READ, &cmd, 1, id_buf, 3, 0); } /* Send Write Enable command */ static int qspi_write_enable(void) { uint8_t cmd = QSPI_CMD_WRITE_ENABLE_OPCODE; return qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0); } /* Wait for flash to be ready (poll status register) */ static int qspi_wait_ready(uint32_t timeout_ms) { uint8_t cmd = QSPI_CMD_READ_STATUS_OPCODE; uint8_t status; uint32_t count = 0; uint32_t max_count = timeout_ms * 1000; /* Rough timing */ int ret; do { ret = qspi_transfer_block(QSPI_MODE_READ, &cmd, 1, &status, 1, 0); if (ret != 0) { return ret; /* Propagate transfer error */ } if (!(status & 0x01)) { /* Bit 0 = WIP (Write In Progress) */ return 0; /* Ready */ } count++; } while (count < max_count); return -1; /* Timeout */ } /* Enter 4-byte addressing mode (required for >32MB flash) */ static int qspi_enter_4byte_mode(void) { uint8_t cmd = QSPI_CMD_ENTER_4BYTE_MODE; return qspi_transfer_block(QSPI_MODE_WRITE, &cmd, 1, NULL, 0, 0); } /* Read from QSPI flash (4-byte addressing) */ static int qspi_flash_read(uint32_t address, uint8_t *data, uint32_t len) { const uint32_t max_chunk = 0xFFFF - 5; /* total_bytes is 16-bit, cmd is 5 */ uint8_t cmd[5]; uint32_t remaining = len; uint32_t chunk_len; int ret; while (remaining > 0) { chunk_len = (remaining > max_chunk) ? max_chunk : remaining; /* Build 4-byte read command */ cmd[0] = QSPI_CMD_4BYTE_READ_OPCODE; cmd[1] = (address >> 24) & 0xFF; cmd[2] = (address >> 16) & 0xFF; cmd[3] = (address >> 8) & 0xFF; cmd[4] = address & 0xFF; ret = qspi_transfer_block(QSPI_MODE_READ, cmd, 5, data, chunk_len, 0); if (ret != 0) { return ret; } address += chunk_len; data += chunk_len; remaining -= chunk_len; } return (int)len; } /* Write to QSPI flash - single page (max 256 bytes) */ static int qspi_flash_write_page(uint32_t address, const uint8_t *data, uint32_t len) { uint8_t cmd[5]; int ret; /* Ensure page alignment and length */ if (len > FLASH_PAGE_SIZE) { len = FLASH_PAGE_SIZE; } /* Enable write */ ret = qspi_write_enable(); if (ret != 0) { return ret; } /* Build 4-byte page program command */ cmd[0] = QSPI_CMD_4BYTE_PAGE_PROG_OPCODE; cmd[1] = (address >> 24) & 0xFF; cmd[2] = (address >> 16) & 0xFF; cmd[3] = (address >> 8) & 0xFF; cmd[4] = address & 0xFF; /* Send command + data */ ret = qspi_transfer_block(QSPI_MODE_WRITE, cmd, 5, (uint8_t *)data, len, 0); if (ret != 0) { return ret; } /* Wait for write to complete */ return qspi_wait_ready(1000); /* 1 second timeout */ } /* Erase 64KB sector */ static int qspi_flash_sector_erase(uint32_t address) { uint8_t cmd[5]; int ret; /* Enable write */ ret = qspi_write_enable(); if (ret != 0) { return ret; } /* Build 4-byte sector erase command */ cmd[0] = QSPI_CMD_4BYTE_SECTOR_ERASE; cmd[1] = (address >> 24) & 0xFF; cmd[2] = (address >> 16) & 0xFF; cmd[3] = (address >> 8) & 0xFF; cmd[4] = address & 0xFF; ret = qspi_transfer_block(QSPI_MODE_WRITE, cmd, 5, NULL, 0, 0); if (ret != 0) { return ret; } /* Wait for erase to complete (64KB erase can take several seconds) */ return qspi_wait_ready(10000); /* 10 second timeout */ } /* ========================================================================== * External Flash API Implementation * ========================================================================== */ void ext_flash_lock(void) { /* Optional: Could implement write protection here */ } void ext_flash_unlock(void) { } int ext_flash_write(uintptr_t address, const uint8_t *data, int len) { uint32_t page_offset; uint32_t chunk_len; int ret; int remaining = len; int total = len; #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Write 0x%x, len %d\n", (uint32_t)address, len); #endif /* Write data page by page */ while (remaining > 0) { /* Calculate bytes to write in this page */ page_offset = address & (FLASH_PAGE_SIZE - 1); chunk_len = FLASH_PAGE_SIZE - page_offset; if (chunk_len > (uint32_t)remaining) { chunk_len = remaining; } /* Write page */ ret = qspi_flash_write_page(address, data, chunk_len); if (ret != 0) { return ret; } /* Update pointers */ address += chunk_len; data += chunk_len; remaining -= chunk_len; } return total; } int ext_flash_read(uintptr_t address, uint8_t *data, int len) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Read 0x%x -> 0x%lx, len %d\n", (uint32_t)address, (unsigned long)data, len); #endif return qspi_flash_read((uint32_t)address, data, (uint32_t)len); } int ext_flash_erase(uintptr_t address, int len) { uint32_t sector_addr; uint32_t end_addr; int ret; int total = len; #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Erase 0x%x, len %d\n", (uint32_t)address, len); #endif /* Check for invalid length or integer overflow */ if (len <= 0 || (uint32_t)len > UINT32_MAX - (uint32_t)address) { return -1; } /* Align to sector boundaries */ sector_addr = address & ~(FLASH_SECTOR_SIZE - 1); end_addr = (uint32_t)address + (uint32_t)len; /* Erase sectors */ while (sector_addr < end_addr) { #ifdef DEBUG_QSPI wolfBoot_printf("QSPI: Erasing sector at 0x%08X\n", sector_addr); #endif ret = qspi_flash_sector_erase(sector_addr); if (ret != 0) { wolfBoot_printf("QSPI: Erase failed\n"); return ret; } sector_addr += FLASH_SECTOR_SIZE; } return total; } /* ============================================================================ * UART QSPI Programmer * * Allows programming the QSPI flash over the debug UART without a JTAG/Libero * tool. Enabled at build time with UART_QSPI_PROGRAM=1 in the .config. * * Protocol (after wolfBoot prints the "QSPI-PROG" prompt): * 1. Host sends 'P' within the timeout window to enter programming mode * 2. wolfBoot sends "READY\r\n" * 3. Host sends [4-byte LE QSPI address][4-byte LE data length] * 4. wolfBoot erases required sectors, sends "ERASED\r\n" * 5. For each 256-byte chunk: * wolfBoot sends ACK byte (0x06) -> host sends chunk -> wolfBoot writes * 6. wolfBoot sends "DONE\r\n" and continues normal boot * * Host side: tools/scripts/mpfs_qspi_prog.py * ============================================================================ */ #if defined(UART_QSPI_PROGRAM) && defined(__WOLFBOOT) #define QSPI_PROG_CHUNK 256 #define QSPI_PROG_ACK 0x06 #define QSPI_RX_TIMEOUT_MS 10000U /* 10 s per byte — aborts if host disappears */ /* Returns 0-255 on success, -1 on timeout (so the boot path is never deadlocked). */ static int uart_qspi_rx(void) { uint32_t t; for (t = 0; t < QSPI_RX_TIMEOUT_MS; t++) { if (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR) return (int)(uint8_t)MMUART_RBR(DEBUG_UART_BASE); udelay(1000); } return -1; /* timeout */ } static void uart_qspi_tx(uint8_t c) { while (!(MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE)) ; MMUART_THR(DEBUG_UART_BASE) = c; } static void uart_qspi_puts(const char *s) { while (*s) uart_qspi_tx((uint8_t)*s++); } static void qspi_uart_program(void) { uint8_t ch = 0; uint32_t addr, size, n_sectors, written, t; uint32_t i, s; uint8_t chunk[QSPI_PROG_CHUNK]; /* Use uart_qspi_puts (direct UART) for ALL programmer output. * wolfBoot_printf uses uart_write which adds \r before \n and may * leave stale bytes in the UART TX pipeline that corrupt the * binary ACK/data protocol after ERASED. */ uart_qspi_puts("QSPI-PROG: Press 'P' within 3s to program flash\r\n"); /* Drain any stale RX bytes before opening the window */ while (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR) (void)MMUART_RBR(DEBUG_UART_BASE); /* Wait up to 3s: 3000 iterations of 1ms each */ for (t = 0; t < 3000U; t++) { udelay(1000); if (MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_DR) { ch = MMUART_RBR(DEBUG_UART_BASE); break; } } if (ch != 'P' && ch != 'p') { uart_qspi_puts("QSPI-PROG: No trigger, booting\r\n"); return; } uart_qspi_puts("READY\r\n"); /* Receive destination address then data length (4 bytes LE each) */ addr = 0; for (i = 0; i < 4; i++) { int b = uart_qspi_rx(); if (b < 0) { uart_qspi_puts("QSPI-PROG: RX timeout (addr)\r\n"); return; } addr |= ((uint32_t)(uint8_t)b << (i * 8)); } size = 0; for (i = 0; i < 4; i++) { int b = uart_qspi_rx(); if (b < 0) { uart_qspi_puts("QSPI-PROG: RX timeout (size)\r\n"); return; } size |= ((uint32_t)(uint8_t)b << (i * 8)); } if (size == 0 || size > 0x200000U) { uart_qspi_puts("QSPI-PROG: Invalid size\r\n"); return; } /* Reject writes to unaligned or out-of-partition addresses */ if ((addr & (FLASH_SECTOR_SIZE - 1U)) != 0U) { uart_qspi_puts("QSPI-PROG: Not sector-aligned\r\n"); return; } if (!((addr >= WOLFBOOT_PARTITION_BOOT_ADDRESS && addr + size <= WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE) || (addr >= WOLFBOOT_PARTITION_UPDATE_ADDRESS && addr + size <= WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE))) { uart_qspi_puts("QSPI-PROG: Outside partition\r\n"); return; } /* Erase all required sectors */ n_sectors = (size + FLASH_SECTOR_SIZE - 1) / FLASH_SECTOR_SIZE; uart_qspi_puts("QSPI-PROG: Erasing...\r\n"); ext_flash_unlock(); for (s = 0; s < n_sectors; s++) { int ret = ext_flash_erase(addr + s * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE); if (ret < 0) { uart_qspi_puts("QSPI-PROG: Erase failed\r\n"); ext_flash_lock(); return; } } uart_qspi_puts("ERASED\r\n"); /* Chunk transfer: wolfBoot requests each 256-byte block with ACK 0x06. * No wolfBoot_printf allowed in this loop — only direct UART via * uart_qspi_tx/uart_qspi_puts to avoid protocol corruption. */ written = 0; while (written < size) { int ret; uint32_t chunk_len = size - written; if (chunk_len > QSPI_PROG_CHUNK) chunk_len = QSPI_PROG_CHUNK; uart_qspi_tx(QSPI_PROG_ACK); /* request next chunk */ for (i = 0; i < chunk_len; i++) { int b = uart_qspi_rx(); if (b < 0) { uart_qspi_puts("QSPI-PROG: RX timeout\r\n"); ext_flash_lock(); return; } chunk[i] = (uint8_t)b; } ret = ext_flash_write(addr + written, chunk, (int)chunk_len); if (ret < 0) { uart_qspi_puts("QSPI-PROG: Write failed\r\n"); ext_flash_lock(); return; } written += chunk_len; } ext_flash_lock(); uart_qspi_puts("DONE\r\n"); } #endif /* UART_QSPI_PROGRAM */ /* Test for external QSPI flash erase/write/read */ #ifdef TEST_EXT_FLASH #ifndef TEST_EXT_ADDRESS #define TEST_EXT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS #endif static int test_ext_flash(void) { int ret; uint32_t i; uint8_t pageData[FLASH_PAGE_SIZE]; wolfBoot_printf("Ext Flash Test at 0x%x\n", TEST_EXT_ADDRESS); #ifndef TEST_FLASH_READONLY /* Erase sector */ ret = ext_flash_erase(TEST_EXT_ADDRESS, FLASH_SECTOR_SIZE); wolfBoot_printf("Sector Erase: Ret %d\n", ret); if (ret < 0) return ret; /* Verify erase (should be all 0xFF) */ memset(pageData, 0, sizeof(pageData)); ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData)); if (ret < 0) { wolfBoot_printf("Erase verify read failed: Ret %d\n", ret); return ret; } wolfBoot_printf("Erase verify: "); for (i = 0; i < 16; i++) { wolfBoot_printf("%02x ", pageData[i]); } wolfBoot_printf("\n"); /* Write Page */ for (i = 0; i < sizeof(pageData); i++) { pageData[i] = (i & 0xff); } ret = ext_flash_write(TEST_EXT_ADDRESS, pageData, sizeof(pageData)); wolfBoot_printf("Page Write: Ret %d\n", ret); if (ret < 0) return ret; #endif /* !TEST_FLASH_READONLY */ /* Read page */ memset(pageData, 0, sizeof(pageData)); ret = ext_flash_read(TEST_EXT_ADDRESS, pageData, sizeof(pageData)); wolfBoot_printf("Page Read: Ret %d\n", ret); if (ret < 0) return ret; /* Check data */ for (i = 0; i < sizeof(pageData); i++) { if (pageData[i] != (i & 0xff)) { wolfBoot_printf("Check Data @ %d failed (0x%02x != 0x%02x)\n", i, pageData[i], (i & 0xff)); wolfBoot_printf("First 16 bytes: "); for (i = 0; i < 16; i++) { wolfBoot_printf("%02x ", pageData[i]); } wolfBoot_printf("\n"); return -1; } } wolfBoot_printf("Ext Flash Test Passed\n"); return 0; } #endif /* TEST_EXT_FLASH */ #else /* !EXT_FLASH */ /* Stubs for when QSPI is disabled */ void ext_flash_lock(void) { } void ext_flash_unlock(void) { } int ext_flash_write(uintptr_t address, const uint8_t *data, int len) { (void)address; (void)data; (void)len; return 0; } int ext_flash_read(uintptr_t address, uint8_t *data, int len) { (void)address; (void)data; (void)len; return 0; } int ext_flash_erase(uintptr_t address, int len) { (void)address; (void)len; return 0; } #endif /* EXT_FLASH */ #if defined(MMU) && !defined(WOLFBOOT_NO_PARTITIONS) void* hal_get_dts_address(void) { #if defined(EXT_FLASH) && defined(NO_XIP) /* Flash is not memory-mapped when using NO_XIP with external flash * (e.g. SC SPI). DTS must be loaded via ext_flash_read, not direct * dereference. Return NULL so the caller skips the direct-access path. */ return NULL; #else return (void*)WOLFBOOT_DTS_BOOT_ADDRESS; #endif } #endif /* PLIC: E51(hart 0)->ctx 0 (M-mode only); U54(1-4)->ctx hart*2-1 (M), hart*2 (S) */ #ifdef WOLFBOOT_RISCV_MMODE uint32_t plic_get_context(void) { uint32_t hart_id; __asm__ volatile("csrr %0, mhartid" : "=r"(hart_id)); return (hart_id == 0) ? 0 : (hart_id * 2) - 1; } #else extern unsigned long get_boot_hartid(void); uint32_t plic_get_context(void) { return (uint32_t)get_boot_hartid() * 2; } #endif /* Dispatch IRQ to appropriate platform handler */ void plic_dispatch_irq(uint32_t irq) { switch (irq) { #if defined(DISK_SDCARD) || defined(DISK_EMMC) case PLIC_INT_MMC_MAIN: sdhci_irq_handler(); break; #endif default: /* Unknown interrupt - ignore */ break; } } #if defined(DISK_SDCARD) || defined(DISK_EMMC) /* SDHCI Platform HAL */ void sdhci_platform_init(void) { SYSREG_SOFT_RESET_CR &= ~MSS_PERIPH_MMC; } void sdhci_platform_irq_init(void) { /* Set priority for MMC main interrupt */ plic_set_priority(PLIC_INT_MMC_MAIN, PLIC_PRIORITY_DEFAULT); /* Set threshold to 0 (allow all priorities > 0) */ plic_set_threshold(0); /* Enable MMC interrupt for this hart */ plic_enable_interrupt(PLIC_INT_MMC_MAIN); #ifdef DEBUG_SDHCI wolfBoot_printf("sdhci_platform_irq_init: hart %d, context %d, irq %d enabled\n", get_boot_hartid(), plic_get_context(), PLIC_INT_MMC_MAIN); #endif } void sdhci_platform_set_bus_mode(int is_emmc) { (void)is_emmc; } uint32_t sdhci_reg_read(uint32_t offset) { return *((volatile uint32_t*)(EMMC_SD_BASE + offset)); } void sdhci_reg_write(uint32_t offset, uint32_t val) { *((volatile uint32_t*)(EMMC_SD_BASE + offset)) = val; } #endif /* DISK_SDCARD || DISK_EMMC */ /* DEBUG UART */ #ifdef DEBUG_UART /* Baud divisor: integer = PCLK/(baudrate*16), fractional (0-63) via 128x scaling. */ static void uart_config_baud(unsigned long base, uint32_t baudrate) { const uint64_t pclk = MSS_APB_AHB_CLK; uint32_t div_x128 = (uint32_t)((8UL * pclk) / baudrate); uint32_t div_x64 = div_x128 / 2u; uint32_t div_int = div_x64 / 64u; uint32_t div_frac = div_x64 - (div_int * 64u); div_frac += (div_x128 - (div_int * 128u)) - (div_frac * 2u); if (div_frac > 63u) div_frac = 63u; if (div_int > (uint32_t)UINT16_MAX) return; MMUART_LCR(base) |= DLAB_MASK; MMUART_DMR(base) = (uint8_t)(div_int >> 8); MMUART_DLR(base) = (uint8_t)div_int; MMUART_LCR(base) &= ~DLAB_MASK; if (div_int > 1u) { MMUART_MM0(base) |= EFBR_MASK; MMUART_DFR(base) = (uint8_t)div_frac; } else { MMUART_MM0(base) &= ~EFBR_MASK; } } static void uart_init_base(unsigned long base) { MMUART_MM0(base) &= ~ELIN_MASK; MMUART_MM1(base) &= ~EIRD_MASK; MMUART_MM2(base) &= ~EERR_MASK; MMUART_IER(base) = 0u; MMUART_FCR(base) = CLEAR_RX_FIFO_MASK | CLEAR_TX_FIFO_MASK | RXRDY_TXRDYN_EN_MASK; MMUART_MCR(base) &= ~(LOOP_MASK | RLOOP_MASK); MMUART_MCR(base) |= (1U << 1); /* Assert RTS — required for USB-UART bridge CTS */ MMUART_MM1(base) &= ~(E_MSB_TX_MASK | E_MSB_RX_MASK); MMUART_MM2(base) &= ~(EAFM_MASK | ESWM_MASK); MMUART_MM0(base) &= ~(ETTG_MASK | ERTO_MASK | EFBR_MASK); MMUART_GFR(base) = 0u; MMUART_TTG(base) = 0u; MMUART_RTO(base) = 0u; uart_config_baud(base, 115200); MMUART_LCR(base) = MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT; } void uart_init(void) { uart_init_base(DEBUG_UART_BASE); } void uart_write(const char* buf, unsigned int sz) { uint32_t pos = 0; while (sz-- > 0) { char c = buf[pos++]; if (c == '\n') { while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0); MMUART_THR(DEBUG_UART_BASE) = '\r'; } while ((MMUART_LSR(DEBUG_UART_BASE) & MSS_UART_THRE) == 0); MMUART_THR(DEBUG_UART_BASE) = c; } } #endif /* DEBUG_UART */ #ifdef WOLFBOOT_RISCV_MMODE /* Initialize UART for a secondary hart (1-4). Hart 0 uses uart_init(). */ void uart_init_hart(unsigned long hartid) { unsigned long base; if (hartid == 0 || hartid > 4) return; base = UART_BASE_FOR_HART(hartid); /* MSS_PERIPH_MMUART0 = bit 5; shift by hartid selects MMUART1-4 */ SYSREG_SUBBLK_CLOCK_CR |= (MSS_PERIPH_MMUART0 << hartid); __asm__ volatile("fence iorw, iorw" ::: "memory"); SYSREG_SOFT_RESET_CR &= ~(MSS_PERIPH_MMUART0 << hartid); __asm__ volatile("fence iorw, iorw" ::: "memory"); udelay(100); uart_init_base(base); udelay(10); } /* Write to a specific hart's UART (hart 0-4). */ void uart_write_hart(unsigned long hartid, const char* buf, unsigned int sz) { unsigned long base; uint32_t pos = 0; if (hartid > 4) return; base = UART_BASE_FOR_HART(hartid); while (sz-- > 0) { char c = buf[pos++]; if (c == '\n') { while ((MMUART_LSR(base) & MSS_UART_THRE) == 0); MMUART_THR(base) = '\r'; } while ((MMUART_LSR(base) & MSS_UART_THRE) == 0); MMUART_THR(base) = c; } } #endif /* WOLFBOOT_RISCV_MMODE */