From 46ed90012e16b27dafaea0367323e93d227cde85 Mon Sep 17 00:00:00 2001 From: Pushyanth Kamatham Date: Wed, 4 Mar 2026 01:23:59 +0530 Subject: [PATCH] Add external flash support and dual-bank flash swap functionality for PSoC6 devices. Removed NVM_FLASH_WRITEONCE restriction for psoc6. Update flash hal functions to support read-modify-erase-write way to perform the programming of flash. --- config/examples/cypsoc6.config | 4 +- docs/Targets.md | 29 +- hal/psoc6.c | 1095 ++++++++++++++++++++++++++++++-- 3 files changed, 1063 insertions(+), 65 deletions(-) diff --git a/config/examples/cypsoc6.config b/config/examples/cypsoc6.config index fb5afae099..1f97d228af 100644 --- a/config/examples/cypsoc6.config +++ b/config/examples/cypsoc6.config @@ -14,7 +14,7 @@ SPI_FLASH?=0 NO_XIP?=0 UART_FLASH?=0 ALLOW_DOWNGRADE?=0 -NVM_FLASH_WRITEONCE?=1 +NVM_FLASH_WRITEONCE?=0 WOLFBOOT_VERSION?=0 V?=0 SPMATH?=1 @@ -27,4 +27,4 @@ WOLFBOOT_PARTITION_SIZE?=0x80000 WOLFBOOT_SECTOR_SIZE?=512 WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x10080000 WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x10100000 -WOLFBOOT_PARTITION_SWAP_ADDRESS?=10010000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x10010000 diff --git a/docs/Targets.md b/docs/Targets.md index 3217652c0d..7a2d534c62 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -2377,7 +2377,6 @@ The following configuration has been tested on the PSoC CY8CKIT-62S2-43012: ``` make TARGET=psoc6 \ - NVM_FLASH_WRITEONCE=1 \ CYPRESS_PDL=./lib/psoc6pdl \ CYPRESS_TARGET_LIB=./lib/TARGET_CY8CKIT-062S2-43012 \ CYPRESS_CORE_LIB=./lib/core-lib \ @@ -2394,6 +2393,34 @@ To compile with hardware acceleration disabled, use the option in your wolfBoot configuration. +#### External Flash Support + +PSoC6 supports external QSPI flash for firmware storage. To enable: + +``` +make TARGET=psoc6 \ + EXT_FLASH=1 \ + CYPRESS_PDL=./lib/psoc6pdl \ + CYPRESS_TARGET_LIB=./lib/TARGET_CY8CKIT-062S2-43012 \ + CYPRESS_CORE_LIB=./lib/core-lib \ + WOLFBOOT_SECTOR_SIZE=4096 +``` + +External flash uses a temporary sector backup mechanism to handle the larger erase size (0x40000 bytes) compared to internal flash rows. The backup sector is automatically managed in external flash beyond the update partition. + +#### Dual-Bank Flash Swap + +PSoC6 supports hardware-assisted bank swapping for faster and more reliable firmware updates: + +``` +make TARGET=psoc6 \ + DUALBANK_SWAP=1 \ + CYPRESS_PDL=./lib/psoc6pdl \ + CYPRESS_TARGET_LIB=./lib/TARGET_CY8CKIT-062S2-43012 \ + CYPRESS_CORE_LIB=./lib/core-lib \ + WOLFBOOT_SECTOR_SIZE=4096 +``` + #### OpenOCD installation Compile and install the customized OpenOCD. diff --git a/hal/psoc6.c b/hal/psoc6.c index c6d4d2e957..53d3bdf6e0 100644 --- a/hal/psoc6.c +++ b/hal/psoc6.c @@ -1,6 +1,6 @@ /* psoc6.c * - * Copyright (C) 2025 wolfSSL Inc. + * Copyright (C) 2026 wolfSSL Inc. * * This file is part of wolfBoot. * @@ -19,57 +19,459 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +/* Standard library includes */ #include -#include #include -#include "image.h" -#include "cy_device_headers.h" +/* Project includes */ +#include "image.h" +#include "printf.h" +#include "psoc6_02_config.h" +#include -#include "cy_flash.h" -#include "cy_syspm.h" -#include "cy_sysclk.h" -#include "cy_syslib.h" -#include "cy_ipc_drv.h" #ifdef WOLFSSL_PSOC6_CRYPTO #include "wolfssl/wolfcrypt/port/cypress/psoc6_crypto.h" #endif -#include "psoc6_02_config.h" - -#define ROW_SIZE (WOLFBOOT_SECTOR_SIZE) -#define FLASH_BASE_ADDRESS (0x10000000) +#define EMULATED_EEPROM_START (CY_EM_EEPROM_BASE) +#define ROW_SIZE (CY_FLASH_SIZEOF_ROW) +#define FLASH_SUBSECTOR_SIZE (CY_FLASH_SIZEOF_ROW * 8) +#define FLASH_SECTOR_SIZE (CY_FLASH_SIZEOF_ROW * CPUSS_FLASHC_SONOS_MAIN_ROWS) +#define FLASH_BASE_ADDRESS (CY_FLASH_BASE) +#define FLASH_SIZE (CY_FLASH_SIZE) #define CPU_FREQ (100000000) -uint8_t psoc6_write_buffer[ROW_SIZE]; +/* Magic number constants */ +#define EXT_FLASH_TMP_MAGIC (0x4C465845U) /* 'EXFL' */ +#define BANKMODE_MAGIC (0x4B424C44U) /* 'DLBK' */ +#define EXT_FLASH_WORD_ERASED_VALUE (0xFFFFFFFFU) + +#ifdef EXT_FLASH + +static cy_stc_smif_context_t smif_context; + +/* + * SFDP-based external memory configuration (self-contained, no BSP generated + * source dependency). + */ +static cy_stc_smif_mem_cmd_t sfdp_cmd = { + .command = 0x5Au, + .cmdWidth = CY_SMIF_WIDTH_SINGLE, + .addrWidth = CY_SMIF_WIDTH_SINGLE, + .mode = 0xFFFFFFFFU, + .dummyCycles = 8u, + .dataWidth = CY_SMIF_WIDTH_SINGLE, +}; + +/* Zero-initialized command structs — populated at runtime by Cy_SMIF_MemInit() + * after reading the device's SFDP table */ +static cy_stc_smif_mem_cmd_t rdcmd0; +static cy_stc_smif_mem_cmd_t wrencmd0; +static cy_stc_smif_mem_cmd_t wrdiscmd0; +static cy_stc_smif_mem_cmd_t erasecmd0; +static cy_stc_smif_mem_cmd_t chiperasecmd0; +static cy_stc_smif_mem_cmd_t pgmcmd0; +static cy_stc_smif_mem_cmd_t readsts0; +static cy_stc_smif_mem_cmd_t readstsqecmd0; +static cy_stc_smif_mem_cmd_t writestseqcmd0; + +static cy_stc_smif_mem_device_cfg_t dev_sfdp_0 = { + .numOfAddrBytes = 4u, + .readSfdpCmd = &sfdp_cmd, + .readCmd = &rdcmd0, + .writeEnCmd = &wrencmd0, + .writeDisCmd = &wrdiscmd0, + .programCmd = &pgmcmd0, + .eraseCmd = &erasecmd0, + .chipEraseCmd = &chiperasecmd0, + .readStsRegWipCmd = &readsts0, + .readStsRegQeCmd = &readstsqecmd0, + .writeStsRegQeCmd = &writestseqcmd0, +}; + +static cy_stc_smif_mem_config_t mem_sfdp_0 = { + .baseAddress = CY_XIP_BASE, + .memMappedSize = CY_XIP_SIZE, + .flags = CY_SMIF_FLAG_DETECT_SFDP | CY_SMIF_FLAG_MEMORY_MAPPED, + .slaveSelect = CY_SMIF_SLAVE_SELECT_0, + .dataSelect = CY_SMIF_DATA_SEL0, + .deviceCfg = &dev_sfdp_0, +}; + +static cy_stc_smif_mem_config_t* mems_sfdp[1] = { + &mem_sfdp_0, +}; + +static cy_stc_smif_block_config_t smifBlockConfig_sfdp = { + .memCount = 1u, + .memConfig = mems_sfdp, +}; + +static cy_stc_smif_block_config_t* smif_blk_config; + +#define TMP_SECTOR_INFO_ADDR EMULATED_EEPROM_START + +/** + * Address of the temporary sector in external flash. + * + * External flash has a larger minimum erase size (determined at runtime via + * SFDP, e.g. 0x40000 bytes for S25FL512S) than internal flash rows. Since + * read-modify-erase-write cannot use SRAM as a temporary buffer due to size + * constraints, we use a dedicated sector in external flash for temporary + * storage during sector updates. + */ +#define SWAP_TMP_SECTOR_ADDR (WOLFBOOT_PARTITION_UPDATE_ADDRESS + CY_FLASH_SIZE) + +typedef struct tmp_sector_info { + uint32_t magic; + uint32_t sector_address; +} tmp_sector_info; + +/* + * QSPI / SMIF hardware configuration + * + * SS0 - P11_2 D3 - P11_3 D2 - P11_4 + * D1 - P11_5 D0 - P11_6 SCK - P11_7 + * + * SMIF Block - SMIF0 + */ +#define SMIF_HF_CLK_DIVIDER (CY_SYSCLK_CLKHF_DIVIDE_BY_4) +#define SMIF_INIT_TRY_COUNT (10U) +#define SMIF_INIT_RETRY_DELAY_MS (500U) +#define SMIF_INIT_TIMEOUT_US (1000U) + +/* + * SMIF SlaveSelect configuration — only SS0 is used here. + * Available slave select options + * SS0 - P11_2 (P11_2_SMIF_SPI_SELECT0) — default, external QSPI flash + * SS1 - P11_1 (P11_1_SMIF_SPI_SELECT1) + * SS2 - P11_0 (P11_0_SMIF_SPI_SELECT2) + * SS3 - P12_4 (P12_4_SMIF_SPI_SELECT3) — only on 1M/2M packages + */ +static GPIO_PRT_Type* qspi_SS_Port = GPIO_PRT11; +static uint32_t qspi_SS_Pin = 2u; +static en_hsiom_sel_t qspi_SS_Mux = P11_2_SMIF_SPI_SELECT0; + +static GPIO_PRT_Type* qspi_D3_Port = GPIO_PRT11; +static uint32_t qspi_D3_Pin = 3u; +static en_hsiom_sel_t qspi_D3_Mux = P11_3_SMIF_SPI_DATA3; -#ifndef NVM_FLASH_WRITEONCE -# error "wolfBoot psoc6 HAL: no WRITEONCE support detected. Please define NVM_FLASH_WRITEONCE" +static GPIO_PRT_Type* qspi_D2_Port = GPIO_PRT11; +static uint32_t qspi_D2_Pin = 4u; +static en_hsiom_sel_t qspi_D2_Mux = P11_4_SMIF_SPI_DATA2; + +static GPIO_PRT_Type* qspi_D1_Port = GPIO_PRT11; +static uint32_t qspi_D1_Pin = 5u; +static en_hsiom_sel_t qspi_D1_Mux = P11_5_SMIF_SPI_DATA1; + +static GPIO_PRT_Type* qspi_D0_Port = GPIO_PRT11; +static uint32_t qspi_D0_Pin = 6u; +static en_hsiom_sel_t qspi_D0_Mux = P11_6_SMIF_SPI_DATA0; + +static GPIO_PRT_Type* qspi_SCK_Port = GPIO_PRT11; +static uint32_t qspi_SCK_Pin = 7u; +static en_hsiom_sel_t qspi_SCK_Mux = P11_7_SMIF_SPI_CLK; + +/* SMIF GPIO pin configurations */ +static cy_stc_gpio_pin_config_t qspi_ss_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG_IN_OFF, + .hsiom = P11_2_SMIF_SPI_SELECT0, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +static const cy_stc_gpio_pin_config_t qspi_d3_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG, + .hsiom = P11_3_SMIF_SPI_DATA3, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +static const cy_stc_gpio_pin_config_t qspi_d2_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG, + .hsiom = P11_4_SMIF_SPI_DATA2, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +static const cy_stc_gpio_pin_config_t qspi_d1_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG, + .hsiom = P11_5_SMIF_SPI_DATA1, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +static const cy_stc_gpio_pin_config_t qspi_d0_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG, + .hsiom = P11_6_SMIF_SPI_DATA0, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +static const cy_stc_gpio_pin_config_t qspi_sck_config = { + .outVal = 1, + .driveMode = CY_GPIO_DM_STRONG_IN_OFF, + .hsiom = P11_7_SMIF_SPI_CLK, + .intEdge = CY_GPIO_INTR_DISABLE, + .intMask = 0UL, + .vtrip = CY_GPIO_VTRIP_CMOS, + .slewRate = CY_GPIO_SLEW_FAST, + .driveSel = CY_GPIO_DRIVE_1_2, + .vregEn = 0UL, + .ibufMode = 0UL, + .vtripSel = 0UL, + .vrefSel = 0UL, + .vohSel = 0UL, +}; + +/* SMIF block configuration */ +static const cy_stc_smif_config_t QSPI_config = { + .mode = (uint32_t)CY_SMIF_NORMAL, + .deselectDelay = 1, + .rxClockSel = (uint32_t)CY_SMIF_SEL_INV_INTERNAL_CLK, + .blockEvent = (uint32_t)CY_SMIF_BUS_ERROR, +}; + +#if CY_CPU_CORTEX_M0P +/* CM0+ SMIF interrupt configuration */ +static cy_stc_sysint_t smifIntConfig = { + /* ATTENTION: make sure proper Interrupts configured for CM0p or M4 cores */ + .intrSrc = NvicMux7_IRQn, + .cm0pSrc = smif_interrupt_IRQn, + .intrPriority = 1, +}; #endif -#ifdef __WOLFBOOT -static const cy_stc_pll_manual_config_t srss_0_clock_0_pll_0_pllConfig = +#if CY_CPU_CORTEX_M0P +static void Isr_SMIF(void) { - .feedbackDiv = 100, + Cy_SMIF_Interrupt(SMIF0, &smif_context); +} +#endif + +/** + * @brief Initialize external QSPI flash hardware + * + * Configures GPIO pins, SMIF clock, interrupt, and initializes the SMIF + * block and external memory device. + * + * Initialization steps: + * 1. Configure QSPI data, clock, and slave-select GPIO pins + * 2. Enable SMIF HF clock (CLK_HF2) with divide-by-4 + * 3. Setup SMIF interrupt for CM0+ + * 4. Initialize SMIF block in normal mode + * 5. Enable SMIF block + * 6. Initialize external memory configuration with retry + * + * @return CY_SMIF_SUCCESS on success, error code on failure + */ +cy_en_smif_status_t hal_ext_flash_init(void) +{ + cy_en_smif_status_t st; + uint32_t try_count; + + /* Step 1: Configure QSPI GPIO pins */ + (void)Cy_GPIO_Pin_Init(qspi_D3_Port, qspi_D3_Pin, &qspi_d3_config); + Cy_GPIO_SetHSIOM(qspi_D3_Port, qspi_D3_Pin, qspi_D3_Mux); + + (void)Cy_GPIO_Pin_Init(qspi_D2_Port, qspi_D2_Pin, &qspi_d2_config); + Cy_GPIO_SetHSIOM(qspi_D2_Port, qspi_D2_Pin, qspi_D2_Mux); + + (void)Cy_GPIO_Pin_Init(qspi_D1_Port, qspi_D1_Pin, &qspi_d1_config); + Cy_GPIO_SetHSIOM(qspi_D1_Port, qspi_D1_Pin, qspi_D1_Mux); + + (void)Cy_GPIO_Pin_Init(qspi_D0_Port, qspi_D0_Pin, &qspi_d0_config); + Cy_GPIO_SetHSIOM(qspi_D0_Port, qspi_D0_Pin, qspi_D0_Mux); + + (void)Cy_GPIO_Pin_Init(qspi_SCK_Port, qspi_SCK_Pin, &qspi_sck_config); + Cy_GPIO_SetHSIOM(qspi_SCK_Port, qspi_SCK_Pin, qspi_SCK_Mux); + + /* Configure SS pin */ + qspi_ss_config.hsiom = qspi_SS_Mux; + (void)Cy_GPIO_Pin_Init(qspi_SS_Port, qspi_SS_Pin, &qspi_ss_config); + Cy_GPIO_SetHSIOM(qspi_SS_Port, qspi_SS_Pin, qspi_SS_Mux); + + /* Step 2: Enable SMIF HF clock (CLK_HF2) sourced from CLK_PATH0 */ + (void)Cy_SysClk_ClkHfSetSource(CY_SYSCLK_CLKHF_IN_CLKPATH2, + CY_SYSCLK_CLKHF_IN_CLKPATH0); + (void)Cy_SysClk_ClkHfSetDivider(CY_SYSCLK_CLKHF_IN_CLKPATH2, + SMIF_HF_CLK_DIVIDER); + (void)Cy_SysClk_ClkHfEnable(CY_SYSCLK_CLKHF_IN_CLKPATH2); + + /* Step 3: Setup the interrupt for the SMIF block. For the CM0+ there + * is a two stage process to setup the interrupts. */ +#if CY_CPU_CORTEX_M0P + (void)Cy_SysInt_Init(&smifIntConfig, Isr_SMIF); +#endif + + /* Step 4: Initialize SMIF block */ + st = Cy_SMIF_Init(SMIF0, &QSPI_config, SMIF_INIT_TIMEOUT_US, &smif_context); + if (st != CY_SMIF_SUCCESS) { + return st; + } + +#if CY_CPU_CORTEX_M0P + NVIC_EnableIRQ( + smifIntConfig.intrSrc); /* Finally, Enable the SMIF interrupt */ +#endif + + /* Step 5: Enable SMIF block */ + Cy_SMIF_Enable(SMIF0, &smif_context); + + /* Step 6: Initialize external memory via SFDP with retry */ + smif_blk_config = &smifBlockConfig_sfdp; + try_count = SMIF_INIT_TRY_COUNT; + do { + st = Cy_SMIF_MemInit(SMIF0, smif_blk_config, &smif_context); + try_count--; + if (st != CY_SMIF_SUCCESS) { + Cy_SysLib_Delay(SMIF_INIT_RETRY_DELAY_MS); + } + } while ((st != CY_SMIF_SUCCESS) && (try_count > 0U)); + + return st; +} + +/** + * @brief Deinitialize external QSPI flash hardware + * + * Reverses the initialization performed by hal_ext_flash_init(). + * Deinitializes SMIF memory, disables the SMIF block, disables the + * HF clock, disconnects interrupts (on CM0+), and deinitializes + * GPIO ports. Matches mcuboot's qspi_deinit() sequence. + */ +static void hal_ext_flash_deinit(void) +{ + Cy_SMIF_MemDeInit(SMIF0); + Cy_SMIF_Disable(SMIF0); + + (void)Cy_SysClk_ClkHfDisable(CY_SYSCLK_CLKHF_IN_CLKPATH2); + +#if CY_CPU_CORTEX_M0P + NVIC_DisableIRQ(smifIntConfig.intrSrc); + Cy_SysInt_DisconnectInterruptSource(smifIntConfig.intrSrc, + smifIntConfig.cm0pSrc); +#endif + + Cy_GPIO_Port_Deinit(qspi_SS_Port); + Cy_GPIO_Port_Deinit(qspi_SCK_Port); + Cy_GPIO_Port_Deinit(qspi_D0_Port); + Cy_GPIO_Port_Deinit(qspi_D1_Port); + Cy_GPIO_Port_Deinit(qspi_D2_Port); + Cy_GPIO_Port_Deinit(qspi_D3_Port); +} + +#endif /* EXT_FLASH */ + +#ifdef DUALBANK_SWAP + +#define FLASH_BANK1_BASE (0x10000000) /* Base address of Flash Bank1 */ +#define FLASH_BANK2_BASE (0x12000000) /* Base address of Flash Bank2 */ + +typedef struct active_bank { + uint32_t magic; + uint32_t mapping; /* Changed from cy_en_maptype_t to uint32_t for 32-bit + alignment */ +} active_bank; + +/* EXT_FLASH and DUALBANK_SWAP are mutually exclusive, so its safe to overwrite + * the data at same address. */ +#define ACTIVE_BANK_MODE_DATA_ADDRESS (EMULATED_EEPROM_START) + +int RAMFUNCTION bankmode_init(void); +#endif + +/* Global write buffer to avoid stack allocation and enable RAMFUNCTION usage */ +uint8_t psoc6_write_buffer[ROW_SIZE]; + +#ifdef __WOLFBOOT +static const cy_stc_pll_manual_config_t srss_0_clock_0_pll_0_pllConfig = { + .feedbackDiv = 100, .referenceDiv = 2, - .outputDiv = 4, - .lfMode = false, - .outputMode = CY_SYSCLK_FLLPLL_OUTPUT_AUTO, + .outputDiv = 4, + .lfMode = false, + .outputMode = CY_SYSCLK_FLLPLL_OUTPUT_AUTO, }; +/** + * @brief Configure PLL for 100 MHz system clock + * + * Sets up the Phase-Locked Loop (PLL) to generate a 100 MHz clock from + * the Internal Main Oscillator (IMO). Configures clock paths and dividers + * for CM0+, CM4, and peripheral clocks. + * + * Clock configuration: + * - Clock path 1 source: IMO (8 MHz) + * - PLL output: 100 MHz (8 MHz * 100 / 2 / 4 = 100 MHz) + * - CLK_HF0: PLL output (100 MHz) + * - CM4 (fast clock): No division (100 MHz) + * - Peripheral clock: No division (100 MHz) + * - CM0+ (slow clock): No division (100 MHz) + * + * @note This function will hang in an infinite loop if PLL configuration + * or enable fails. + */ static void hal_set_pll(void) { /*Set clock path 1 source to IMO, this feeds PLL1*/ Cy_SysClk_ClkPathSetSource(1U, CY_SYSCLK_CLKPATH_IN_IMO); - /*Set the input for CLK_HF0 to the output of the PLL, which is on clock path 1*/ + /*Set the input for CLK_HF0 to the output of the PLL, which is on clock path + * 1*/ Cy_SysClk_ClkHfSetSource(0U, CY_SYSCLK_CLKHF_IN_CLKPATH1); Cy_SysClk_ClkHfSetDivider(0U, CY_SYSCLK_CLKHF_NO_DIVIDE); - /*Set divider for CM4 clock to 0, might be able to lower this to save power if needed*/ + /*Set divider for CM4 clock to 0, might be able to lower this to save power + * if needed*/ Cy_SysClk_ClkFastSetDivider(0U); - /*Set divider for peripheral and CM0 clock to 0 - This must be 0 to get fastest clock to CM0*/ + /*Set divider for peripheral and CM0 clock to 0 - This must be 0 to get + * fastest clock to CM0*/ Cy_SysClk_ClkPeriSetDivider(0U); /*Set divider for CM0 clock to 0*/ @@ -79,30 +481,53 @@ static void hal_set_pll(void) Cy_SysLib_SetWaitStates(false, 100); /*Configure PLL for 100 MHz*/ - if (CY_SYSCLK_SUCCESS != Cy_SysClk_PllManualConfigure(1U, &srss_0_clock_0_pll_0_pllConfig)) - { - while(1) + if (CY_SYSCLK_SUCCESS != + Cy_SysClk_PllManualConfigure(1U, &srss_0_clock_0_pll_0_pllConfig)) { + while (1) ; } /*Enable PLL*/ - if (CY_SYSCLK_SUCCESS != Cy_SysClk_PllEnable(1U, 10000u)) - { - while(1) + if (CY_SYSCLK_SUCCESS != Cy_SysClk_PllEnable(1U, 10000u)) { + while (1) ; } } - - +/** + * @brief Initialize hardware abstraction layer + * + * Performs essential hardware initialization for the PSoC6 bootloader: + * 1. Sets the Vector Table Offset Register (VTOR) to flash base address + * 2. Initializes Cypress PDL (Peripheral Driver Library) + * 3. Initializes the flash subsystem for program/erase operations + * 4. Configures PLL for 100 MHz system clock + * 5. Initializes crypto hardware acceleration (if WOLFSSL_PSOC6_CRYPTO enabled) + * + * @note Must be called early in the boot process before any flash operations + * or cryptographic functions. + */ void hal_init(void) { -#define VTOR (*(volatile uint32_t *)(0xE000ED08)) +#define VTOR (*(volatile uint32_t*)(0xE000ED08)) VTOR = FLASH_BASE_ADDRESS; #undef VTOR +#ifdef DUALBANK_SWAP + bankmode_init(); +#endif + Cy_PDL_Init(CY_DEVICE_CFG); Cy_Flash_Init(); hal_set_pll(); + +#ifdef EXT_FLASH + if (hal_ext_flash_init() != CY_SMIF_SUCCESS) { + wolfBoot_printf("hal_init: QSPI ext flash init failed\n"); + while (1) + ; + } +#endif + #ifdef WOLFSSL_PSOC6_CRYPTO psoc6_crypto_port_init(); #endif @@ -110,35 +535,99 @@ void hal_init(void) void hal_prepare_boot(void) { +#ifdef EXT_FLASH + hal_ext_flash_deinit(); +#endif } #endif - -/* Only Row-aligned writes allowed. This is guaranteed by wolfBoot if NVM_CACHE is - * in use (via NVM_FLASH_WRITEONCE=1), as unaligned writes become cached. +/** + * @brief Write data to internal flash memory + * + * Writes data to the PSoC6 internal flash with automatic handling of + * unaligned addresses and partial row writes. Uses read-modify-write + * for rows that are not fully overwritten. + * + * @param address Flash address to write to (must be >= FLASH_BASE_ADDRESS) + * @param data Pointer to source data buffer + * @param len Number of bytes to write (must be > 0) + * + * @return 0 on success, -1 on invalid parameters, or PDL error code on + * flash programming failure + * + * @note Uses global psoc6_write_buffer for row operations to avoid stack + * allocation. + * @note For optimal performance, align writes to ROW_SIZE (512 bytes) + * boundaries. */ -int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t* data, int len) { - const uint8_t *src = data; - int ret; - if (len < ROW_SIZE) + uint32_t write_len; + uint32_t row_start = address & ~(ROW_SIZE - 1); + int ret = 0; + uint32_t offset = address - row_start; + + /* Validate input parameters (address checked against base, PDL handles bank + * mapping) */ + if (!data || (len <= 0) || (address < FLASH_BASE_ADDRESS)) return -1; - if ((((uint32_t)data) & FLASH_BASE_ADDRESS) == FLASH_BASE_ADDRESS) { - if (len != ROW_SIZE) { - return -1; + + /* Handle unaligned start address with read-modify-write */ + if (address != row_start) { + /* Read existing row from flash */ + memcpy(psoc6_write_buffer, (const void*)row_start, ROW_SIZE); + + /* Merge new data at offset, limiting to row boundary */ + write_len = ((ROW_SIZE - offset) < (uint32_t)len) ? (ROW_SIZE - offset) + : (uint32_t)len; + memcpy(&psoc6_write_buffer[offset], data, write_len); + + /* Program the updated data into flash row */ + ret = + Cy_Flash_ProgramRow(row_start, (const uint32_t*)psoc6_write_buffer); + if (ret) { + wolfBoot_printf( + "hal_flash_write: ProgramRow failed at 0x%08X ret=%d\n", + row_start, ret); + return ret; } - memcpy(psoc6_write_buffer, data, len); - src = psoc6_write_buffer; + + row_start += ROW_SIZE; + data += write_len; + len -= write_len; } - while (len) { - ret = Cy_Flash_ProgramRow(address, (const uint32_t *) src); - if (ret) + + /* Write remaining rows (row-aligned) */ + while (len > 0) { + /* Choose write length for this row */ + write_len = ((uint32_t)len >= ROW_SIZE) ? ROW_SIZE : (uint32_t)len; + + if (write_len == ROW_SIZE) { + /* Full-row write - avoid unnecessary read */ + memcpy(psoc6_write_buffer, data, ROW_SIZE); + } + else { + /* Read-modify-write for partial row support */ + memcpy(psoc6_write_buffer, (const void*)row_start, ROW_SIZE); + memcpy(psoc6_write_buffer, data, write_len); + } + + /* Program the updated data into flash row */ + ret = + Cy_Flash_ProgramRow(row_start, (const uint32_t*)psoc6_write_buffer); + if (ret) { + wolfBoot_printf( + "hal_flash_write: ProgramRow failed at 0x%08X ret=%d\n", + row_start, ret); return ret; - len -= ROW_SIZE; - if ((len > 0) && (len < ROW_SIZE)) - return -1; + } + + row_start += ROW_SIZE; + data += write_len; + len -= (int)write_len; } + return 0; } @@ -150,20 +639,502 @@ void RAMFUNCTION hal_flash_lock(void) { } +/** + * @brief Erase internal flash memory with optimized erase size selection + * + * Erases flash memory by automatically selecting the most efficient erase + * operation based on address alignment and remaining length: + * - Sector erase (largest): When aligned to FLASH_SECTOR_SIZE and len >= sector + * - Subsector erase (medium): When aligned to FLASH_SUBSECTOR_SIZE and len >= + * subsector + * - Row erase (smallest): For unaligned addresses or small regions + * + * @param address Starting flash address (must be row-aligned and >= + * FLASH_BASE_ADDRESS) + * @param len Number of bytes to erase (must be > 0) + * + * @return 0 on success, -1 on invalid parameters or erase failure + * + * @note Address must be aligned to ROW_SIZE (512 bytes) + * @note This function optimizes erase time by using larger erase operations + * when possible. + */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) { - uint32_t end_address; - int ret; - uint32_t p = (uint32_t)address; - if (len == 0) + cy_rslt_t status; + + /* Validate input parameters */ + if ((len <= 0) || (address % ROW_SIZE) || (address < FLASH_BASE_ADDRESS)) return -1; - end_address = address + len; - while ((end_address - p) >= ROW_SIZE) { - ret = Cy_Flash_EraseRow(p); - if (ret) - return ret; - p += ROW_SIZE; + + /* Select optimal erase size based on alignment and length */ + while (len > 0) { + if ((address % FLASH_SECTOR_SIZE == 0) && + ((uint32_t)len >= FLASH_SECTOR_SIZE)) { + /* Erase full sector (most efficient) */ + status = Cy_Flash_EraseSector(address); + if (status != CY_RSLT_SUCCESS) + return -1; + + address += FLASH_SECTOR_SIZE; + len -= FLASH_SECTOR_SIZE; + } + else if ((address % FLASH_SUBSECTOR_SIZE == 0) && + ((uint32_t)len >= FLASH_SUBSECTOR_SIZE)) { + /* Erase subsector (medium efficiency) */ + status = Cy_Flash_EraseSubsector(address); + if (status != CY_RSLT_SUCCESS) + return -1; + + address += FLASH_SUBSECTOR_SIZE; + len -= FLASH_SUBSECTOR_SIZE; + } + else { + /* Erase single row (smallest granularity) */ + status = Cy_Flash_EraseRow(address); + if (status != CY_RSLT_SUCCESS) + return -1; + + address += ROW_SIZE; + len -= ROW_SIZE; + } } + return 0; } +/** + * @brief Perform a system reset + * + * Triggers a software reset of the PSoC6 device by calling NVIC_SystemReset(). + * + * @note This function does not return. + */ +void RAMFUNCTION arch_reboot(void) +{ + NVIC_SystemReset(); +} + +/****************************************************************************** + * External Flash Support + ******************************************************************************/ + +#ifdef EXT_FLASH + +/** + * @brief Write data to external flash with sector management + * + * Handles writing to external flash which has larger erase sectors + * (e.g. 0x40000 bytes for S25FL512S, determined at runtime via SFDP) than + * the row size used by wolfBoot. Implements a temporary sector backup + * mechanism to preserve existing data during partial sector writes. + * + * Algorithm: + * 1. Check if the target sector differs from the cached temporary sector + * 2. If different: backup entire source sector to temp sector, then erase + * source + * 3. If same: check if target area needs erasing (contains non-0xFF data) + * 4. Write new data to the target location + * + * @param address External flash address (must be row-aligned) + * @param data Pointer to source data buffer + * @param len Number of bytes to write (must be row-aligned) + * + * @return 0 on success, -1 on error + * + * @note Uses internal emulated EEPROM to track temporary sector state for + * power-fail safety. + * @note SWAP_TMP_SECTOR_ADDR is used as temporary storage during sector + * updates. + */ +int RAMFUNCTION ext_flash_write(uintptr_t address, const uint8_t* data, int len) +{ + tmp_sector_info info; + cy_rslt_t status; + uint32_t base_address; + uint32_t erase_size; + uint32_t sector_start; + uint8_t need_update; + uint32_t offset; + int ret = -1; + int i; + + /* Validate alignment requirements */ + if (!data || (address % ROW_SIZE) || (len % ROW_SIZE)) + return -1; + + base_address = smif_blk_config->memConfig[0]->baseAddress; + erase_size = smif_blk_config->memConfig[0]->deviceCfg->eraseSize; + + memcpy(&info, (void*)TMP_SECTOR_INFO_ADDR, sizeof(tmp_sector_info)); + sector_start = address - (address % erase_size); + + /* Determine if temporary sector needs updating (invalid magic or different + * sector) */ + need_update = ((info.magic != EXT_FLASH_TMP_MAGIC) || + (info.sector_address != sector_start)); + + if (need_update) { + /* Invalidate temp sector info before starting update process */ + if (hal_flash_erase(TMP_SECTOR_INFO_ADDR, ROW_SIZE) != 0) + goto error; + + /* Erase the temporary sector */ + status = Cy_SMIF_MemEraseSector(SMIF0, smif_blk_config->memConfig[0], + SWAP_TMP_SECTOR_ADDR - base_address, + erase_size, &smif_context); + if (status != CY_SMIF_SUCCESS) + goto error; + + /* Backup source sector to temporary location row by row */ + for (offset = 0; offset < erase_size; offset += ROW_SIZE) { + status = + Cy_SMIF_MemRead(SMIF0, smif_blk_config->memConfig[0], + sector_start - base_address + offset, + psoc6_write_buffer, ROW_SIZE, &smif_context); + if (status != CY_SMIF_SUCCESS) + goto error; + + status = + Cy_SMIF_MemWrite(SMIF0, smif_blk_config->memConfig[0], + SWAP_TMP_SECTOR_ADDR - base_address + offset, + psoc6_write_buffer, ROW_SIZE, &smif_context); + if (status != CY_SMIF_SUCCESS) + goto error; + } + + /* Mark temporary sector as valid */ + info.magic = EXT_FLASH_TMP_MAGIC; + info.sector_address = sector_start; + if (hal_flash_write(TMP_SECTOR_INFO_ADDR, (const void*)&info, + sizeof(tmp_sector_info)) != 0) + goto error; + + /* Erase the source sector */ + status = Cy_SMIF_MemEraseSector(SMIF0, smif_blk_config->memConfig[0], + sector_start - base_address, erase_size, + &smif_context); + if (status != CY_SMIF_SUCCESS) + goto error; + } + else { + /* Check if target write region needs erasing (contains non-erased data) + */ + for (i = 0; i < len; i += ROW_SIZE) { + status = + Cy_SMIF_MemRead(SMIF0, smif_blk_config->memConfig[0], + address - base_address + i, psoc6_write_buffer, + ROW_SIZE, &smif_context); + if (status != CY_SMIF_SUCCESS) { + wolfBoot_printf( + "ext_flash_write: SMIF read failed at 0x%08lX\n", + (unsigned long)(address + i)); + goto error; + } + + /* Check if all bytes are in erased state. */ + if ((*(uint32_t*)psoc6_write_buffer != + EXT_FLASH_WORD_ERASED_VALUE) || + (memcmp(psoc6_write_buffer + 4, psoc6_write_buffer, + ROW_SIZE - 4) != 0)) { + /* Non-erased data found, erase the containing sector */ + status = Cy_SMIF_MemEraseSector( + SMIF0, smif_blk_config->memConfig[0], + sector_start - base_address, erase_size, &smif_context); + if (status != CY_SMIF_SUCCESS) { + wolfBoot_printf("ext_flash_write: SMIF erase failed for " + "sector 0x%08lX\n", + (unsigned long)sector_start); + goto error; + } + break; + } + } + } + + /* Write the row data */ + status = Cy_SMIF_MemWrite(SMIF0, smif_blk_config->memConfig[0], + address - base_address, data, len, &smif_context); + if (status != CY_SMIF_SUCCESS) + goto error; + + ret = 0; + +error: + return ret; +} + +/** + * @brief Read data from external flash with temporary sector redirection + * + * Reads data from external flash, automatically redirecting reads to the + * temporary backup sector if the requested address falls within a sector + * that is currently being updated (indicated by valid tmp_sector_info). + * + * This redirection ensures data consistency during interrupted write + * operations, as the temporary sector contains the original data before + * modification. + * + * @param address External flash address to read from + * @param data Pointer to destination buffer + * @param len Number of bytes to read (must be > 0) + * + * @return Number of bytes read on success, -1 on invalid parameters or + * SMIF failure + * + * @note Redirection is transparent to the caller and ensures read consistency + * even during power-fail recovery scenarios. + */ +int RAMFUNCTION ext_flash_read(uintptr_t address, uint8_t* data, int len) +{ + tmp_sector_info info; + uint32_t base_address; + uint32_t erase_size; + uint32_t sector_start; + uint32_t offset; + cy_rslt_t status; + + if (!data || (len <= 0)) + return -1; + + base_address = smif_blk_config->memConfig[0]->baseAddress; + erase_size = smif_blk_config->memConfig[0]->deviceCfg->eraseSize; + + memcpy(&info, (void*)TMP_SECTOR_INFO_ADDR, sizeof(tmp_sector_info)); + + /* Redirect to temporary sector if active and address falls within that + * sector */ + if (info.magic == EXT_FLASH_TMP_MAGIC) { + sector_start = address - (address % erase_size); + if (sector_start == info.sector_address) { + /* Remap to temporary sector while preserving offset within sector + */ + address = SWAP_TMP_SECTOR_ADDR + (address % erase_size); + } + } + + offset = address - base_address; + status = Cy_SMIF_MemRead(SMIF0, smif_blk_config->memConfig[0], offset, data, + len, &smif_context); + if (status != CY_SMIF_SUCCESS) + return -1; + return len; +} + +/** + * @brief External flash erase operation (no-op) + * + * External flash erase is handled by ext_flash_write() due to sector size + * mismatch. wolfBoot expects row-sized erase granularity, but external + * flash minimum erase size is much larger (eg. 0x40000 bytes for S25FL512S). + * The write function manages sector erasing and preserves existing data + * via temporary sector backup. + * + * @param address External flash address (unused) + * @param len Number of bytes (unused) + * @return Always returns 0 (success) + */ +int RAMFUNCTION ext_flash_erase(uintptr_t address, int len) +{ + (void)address; + (void)len; + return 0; +} +#endif /* EXT_FLASH */ + +#ifdef EXT_FLASH + +/** + * @brief Lock external flash and finalize sector operations + * + * Invalidates the temporary sector tracking information stored in internal + * emulated EEPROM. This signals that any pending sector update has been + * completed and the temporary sector data is no longer needed. + * + * Should be called after a series of external flash write operations to + * commit the changes and free the temporary sector for future use. + * + * @note Erases the tmp_sector_info structure if it contains valid magic, + * which clears the EXT_FLASH_TMP_MAGIC marker. + */ +void ext_flash_lock(void) +{ + tmp_sector_info info; + memcpy(&info, (void*)TMP_SECTOR_INFO_ADDR, sizeof(tmp_sector_info)); + + /* Clear temporary sector metadata to invalidate any active state */ + if (info.magic == EXT_FLASH_TMP_MAGIC) { + hal_flash_erase(TMP_SECTOR_INFO_ADDR, ROW_SIZE); + } +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + +/****************************************************************************** + * Dual Bank Flash Swap Support + ******************************************************************************/ + +#if defined(DUALBANK_SWAP) && defined(__WOLFBOOT) + +#if (WOLFBOOT_BOOTLOADER_START != FLASH_BANK1_BASE) +#error "The bootloader must point to FLASH_BANK1_BASE" +#endif + +/** + * @brief Copy bootloader from Bank 1 to Bank 2 for dual-bank operation + * + * Creates an identical copy of the bootloader in the second flash bank. + * This enables the bootloader to execute from either bank when the flash + * mapping is swapped during firmware updates. + * + * The function is idempotent - if both banks already contain identical + * bootloader code, no flash operations are performed. + * + * @note Bootloader size is defined by WOLFBOOT_BOOTLOADER_SIZE. + * @note This function should be called once during initial setup or after + * bootloader updates. + */ +void RAMFUNCTION fork_bootloader(void) +{ + uint8_t* src = (uint8_t*)FLASH_BANK1_BASE; + uint8_t* dst = (uint8_t*)(FLASH_BANK1_BASE + (CY_FLASH_SIZE / 2)); + uint32_t i; + int ret; + + /* Skip fork if bootloader already identical in both banks (idempotent + * operation) */ + if (memcmp(src, dst, WOLFBOOT_BOOTLOADER_SIZE) == 0) + return; + + hal_flash_unlock(); + + /* Erase entire bootloader region in Bank 2, then copy from Bank 1 row by + * row */ + ret = hal_flash_erase((uintptr_t)dst, WOLFBOOT_BOOTLOADER_SIZE); + if (ret == 0) { + for (i = 0; i < WOLFBOOT_BOOTLOADER_SIZE; i += ROW_SIZE) { + ret = hal_flash_write((uintptr_t)(dst + i), src + i, ROW_SIZE); + if (ret != 0) { + wolfBoot_printf( + "Fork bootloader: write failed at offset 0x%X, ret=%d\n", i, + ret); + break; + } + } + } + else { + wolfBoot_printf("Fork bootloader: erase failed, ret=%d\n", ret); + } + + hal_flash_lock(); +} + +/** + * @brief Swap active flash bank mapping and persist the new state + * + * Toggles between Bank A and Bank B flash mapping to switch which physical + * flash bank appears at the base address. This enables A/B firmware update + * schemes where new firmware is written to the inactive bank and activated + * by swapping. + * + * The current bank mapping is persisted to emulated EEPROM so it survives + * reset and can be restored on subsequent boots. + * + * State transitions: + * - Bank A -> Bank B + * - Bank B -> Bank A + * - Invalid/first boot -> Bank A (initialized) + * + * @note Waits for flash controller to complete before returning. + * @note Bank mode data is stored at ACTIVE_BANK_MODE_DATA_ADDRESS in + * emulated EEPROM. + */ +void RAMFUNCTION hal_flash_dualbank_swap(void) +{ + active_bank bankmode_data = *(active_bank*)ACTIVE_BANK_MODE_DATA_ADDRESS; + + /* Read and validate saved bank mode from emulated EEPROM area */ + if (bankmode_data.magic == BANKMODE_MAGIC) { + if (bankmode_data.mapping == CY_FLASH_MAPPING_A) { + bankmode_data.mapping = CY_FLASH_MAPPING_B; + } + else { + bankmode_data.mapping = CY_FLASH_MAPPING_A; + } + } + else { + /* First boot: initialize to Bank A */ + bankmode_data.magic = BANKMODE_MAGIC; + bankmode_data.mapping = CY_FLASH_MAPPING_A; + } + + /* Set the active bank mapping */ + Cy_Flashc_SetMain_Flash_Mapping(bankmode_data.mapping); + + /* Persist bank mode to emulated EEPROM flash area */ + hal_flash_erase(ACTIVE_BANK_MODE_DATA_ADDRESS, ROW_SIZE); + hal_flash_write(ACTIVE_BANK_MODE_DATA_ADDRESS, (uint8_t*)&bankmode_data, + sizeof(active_bank)); + + /* Wait for flash command completion */ + while (FLASHC_FLASH_CMD & FLASHC_V2_FLASH_CMD_INV_Msk) + ; +} + +/** + * @brief Initialize dual-bank flash mode on system startup + * + * Sets up the flash controller for dual-bank operation and restores the + * previously active bank mapping from persistent storage. Called during + * bootloader initialization. + * + * Initialization sequence: + * 1. Fork bootloader to Bank 2 (if not already present) + * 2. Enable dual-bank mode in flash controller + * 3. Read saved bank mapping from emulated EEPROM + * 4. Restore bank mapping or initialize to Bank A on first boot + * 5. Wait for flash controller ready + * + * @return 0 on success + * + * @note Must be called before any firmware verification or boot operations + * that depend on correct flash mapping. + * @note On first boot (no valid saved state), initializes to Bank A and + * persists this configuration. + */ +int RAMFUNCTION bankmode_init(void) +{ + /* Ensure bootloader is present in both banks */ + fork_bootloader(); + + /* Enable dual bank mode */ + Cy_Flashc_SetMainBankMode(CY_FLASH_DUAL_BANK_MODE); + __ASM volatile("isb 0xF" ::: "memory"); + + /* Read and restore bank mode from emulated EEPROM flash area */ + active_bank bankmode_data = *(active_bank*)ACTIVE_BANK_MODE_DATA_ADDRESS; + if (bankmode_data.magic == BANKMODE_MAGIC) { + /* Restore previously saved bank mapping */ + Cy_Flashc_SetMain_Flash_Mapping(bankmode_data.mapping); + } + else { + /* First boot: initialize to Bank A */ + bankmode_data.magic = BANKMODE_MAGIC; + bankmode_data.mapping = CY_FLASH_MAPPING_A; + + /* Save initial bank mode to emulated EEPROM flash area */ + hal_flash_erase(ACTIVE_BANK_MODE_DATA_ADDRESS, ROW_SIZE); + hal_flash_write(ACTIVE_BANK_MODE_DATA_ADDRESS, (uint8_t*)&bankmode_data, + sizeof(active_bank)); + } + + /* Wait for flash controller ready */ + while (FLASHC_FLASH_CMD & FLASHC_V2_FLASH_CMD_INV_Msk) + ; + + return 0; +} +#endif /* DUALBANK_SWAP && __WOLFBOOT */