From 23b9437efb72167fcdf5455351d66855bc83aa94 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 11:31:08 -0700 Subject: [PATCH] va416x0: fix vector table alignment and length, FRAM defects and ML-DSA --- config/examples/vorago_va416x0.config | 11 +- docs/Targets.md | 86 ++++++++--- hal/va416x0.c | 96 ++++++------ hal/va416x0.h | 6 +- hal/va416x0.ld | 11 +- src/boot_arm.c | 206 ++++++++++++++++++++++++++ test-app/ARM-va416x0.ld | 11 +- test-app/app_va416x0.c | 47 +++++- test-app/startup_arm.c | 206 ++++++++++++++++++++++++++ tools/scripts/va416x0/build_test.sh | 51 +++++-- tools/unit-tests/unit-va416x0-fram.c | 173 ++++++++++++++++++++- 11 files changed, 808 insertions(+), 96 deletions(-) diff --git a/config/examples/vorago_va416x0.config b/config/examples/vorago_va416x0.config index ba223fb2ab..473822abf8 100644 --- a/config/examples/vorago_va416x0.config +++ b/config/examples/vorago_va416x0.config @@ -5,9 +5,16 @@ TARGET?=va416x0 # ECDSA P384 and SHA384 SIGN?=ECC384 HASH?=SHA384 -IMAGE_HEADER_SIZE=512 -# ML-DSA Level 5 (87) +# The application vector table sits at BOOT_ADDRESS + IMAGE_HEADER_SIZE and +# do_boot() writes that into VTOR. The core ORs the vector offset into VTOR +# instead of adding it, so with a 212-entry table any IRQ >= 112 (offset +# >= 0x200) is misdispatched unless that address is 1024-aligned. Keep +# (BOOT_ADDRESS + IMAGE_HEADER_SIZE) % 1024 == 0; hal/va416x0.c checks it. +IMAGE_HEADER_SIZE=1024 + +# ML-DSA Level 5 (87). Uses ~48KB of the 64KB SRAM and 34KB of the 36KB +# bootloader region, so both are close to full. #SIGN=ML_DSA #HASH=SHA256 #ML_DSA_LEVEL=5 diff --git a/docs/Targets.md b/docs/Targets.md index 3289930bcb..f12af0cbb1 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -8860,19 +8860,46 @@ Tested on VA41620 and VA41630 MCU's. MCU: Cortex-M4 with Triple-Mode Redundancy (TMR) RAD hardening at up to 100MHz. FLASH: The VA41630 has 256KB of internal SPI FRAM (for the VA41620 its external). FRAM is Infineon FM25V20A. -Default flash layout: +Default flash layout, which fills the 256KB FRAM exactly: | Partition | Size | Address | Description | |-------------|-------|---------|-------------| -| Bootloader | 38KB | 0x0 | Bootloader partition | -| Application | 108KB | 0x9800 | Boot partition | -| Update | 108KB | 0x24800 | Update partition | +| Bootloader | 46KB | 0x0 | Bootloader partition | +| Application | 104KB | 0xB800 | Boot partition | +| Update | 104KB | 0x25800 | Update partition | | Swap | 2KB | 0x3F800 | Swap area | +The sector size is 2KB (`WOLFBOOT_SECTOR_SIZE=0x800`) and the manifest header is 1KB (`IMAGE_HEADER_SIZE=1024`), so the application image starts at 0xBC00 and has 0x19C00 bytes of usable space. + SRAM: 64KB on-chip SRAM and 256KB on-chip instruction/program memory +The 64KB of SRAM is two contiguous 32KB banks, SRAM_0 at 0x1FFF8000 and SRAM_1 at 0x20000000. The linker scripts pool them into a single region so that data and stack can span both; they remain separate EDAC scrub banks (`RAM0_SCRUB` and `RAM1_SCRUB`). The ML-DSA Level 5 configuration needs about 48KB of it and does not fit in one bank alone. + Boot ROM loads at 20MHz from SPI bus to internal data SRAM. +#### Interrupt vector tables + +The VA416xx implements 212 exceptions: the 16 Cortex-M4 system exceptions plus 196 external interrupts, IRQ 0 through `TXEV_IRQn`. Both vector tables are sized for all of them, wolfBoot's in `src/boot_arm.c` and the application's in `test-app/startup_arm.c`. wolfBoot enables the EDAC single-bit and multi-bit error interrupts (76 and 77) when it configures scrubbing, and those vector fetches land at offsets 0x170 and 0x174, so a shorter table sends them into `.text`. + +The application's vector table sits at `WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE` and `do_boot()` writes that address into `SCB->VTOR`. ARMv7-M requires a table this size to be aligned to the next power of two at or above (number of exceptions x 4), which is 1024 bytes for 212 entries, and this part enforces it in a way that is easy to miss. + +`VTOR` itself accepts a finer value: writing `0xFFFFFFFF` reads back `0xFFFFFF80`, so the implemented field is `VTOR[31:7]`. The vector *fetch*, however, ORs the vector offset into `VTOR` rather than adding it, so any vector whose byte offset shares a set bit with the low bits of `VTOR` resolves to the wrong entry. Measured on a VA41630 with a relocated 212-entry table at a 512-aligned (not 1024-aligned) address: + +| IRQ | Vector offset | Result | +|-----|---------------|--------| +| 77 (`EDAC_SBE`) | 0x174 | dispatched | +| 111 | 0x1FC | dispatched | +| 112 | 0x200 | hard fault (fetches vector 0, the initial MSP) | +| 128 (`PORTD2`) | 0x240 | wrong handler | + +The same table at a 1024-aligned address dispatches IRQ 128 correctly. So a 512-aligned table appears to work -- SysTick, the EDAC interrupts and anything below IRQ 112 are fine -- while every IRQ from 112 upward is silently broken. That covers the PORTA through PORTG pin interrupts, the DMA interrupts and the ADC/DAC interrupts. + +The default layout raises `IMAGE_HEADER_SIZE` to 1024 so that `0xB800 + 0x400 = 0xBC00` satisfies this. That costs 512 bytes of each partition. If you need those bytes back, the alternative is to keep a smaller header and move `WOLFBOOT_PARTITION_BOOT_ADDRESS` instead, so long as the sum stays 1024-aligned; `WOLFBOOT_SECTOR_SIZE` can be reduced to give finer placement, subject to `WOLFBOOT_SECTOR_SIZE >= IMAGE_HEADER_SIZE`. Either way keep `(WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE) % 1024 == 0`. `hal/va416x0.c` checks this at build time, so a layout that breaks the rule fails to compile rather than shipping. + +This applies whichever startup file the application uses. Linking against the Vorago SDK's `startup_va416xx.s` rather than `test-app/startup_arm.c` does not change where wolfBoot places the image, and the SDK's `SystemInit()` sets `VTOR` to the same address wolfBoot already wrote. + +The demo application prints `VTOR`, whether it is 1024-aligned, and a SysTick liveness check at startup, so a truncated or misplaced table is visible on the console. Note that SysTick alone does not prove the table is placed correctly: it is exception 15 at offset 0x3C, below the bit that alignment affects. Testing dispatch properly means triggering an IRQ at or above 112, for example with `NVIC_SetPendingIRQ()`. + By default the bootloader is built showing logs on UART0. To use UART1 set `DEBUG_UART_NUM=1`. To disable the bootloader UART change `DEBUG_UART=0` in the `.config`. ### Building Vorago VA416x0 @@ -8943,6 +8970,10 @@ Example of wolfBoot binary sizes based on algorithms: | RSA4096 | SHA3-384 | 19,216 | | ML-DSA 87 | SHA256 | 25,168 | +#### Post-quantum (ML-DSA) configuration + +The example config carries a commented-out ML-DSA Level 5 block: uncomment it, comment out the ECC384/SHA384 lines and the default layout, and use the larger sector and partition sizes it lists. The `sign` tool reads `ML_DSA_LEVEL` from the environment and otherwise falls back to level 2, which rejects a level 5 key with `error: unrecognized ml-dsa key size: 7488`. The top-level Makefile and `tools/scripts/va416x0/build_test.sh` both pass it for you; a hand-run `sign` needs it on the command line. + ### Flashing Vorago VA416x0 Flash using Segger JLink: `JLinkExe -CommanderScript tools/scripts/va416x0/flash_va416xx.jlink` @@ -8967,14 +8998,16 @@ The `loader.elf` programs the external SPI FRAM with the IRAM image. It is creat See `tools/scripts/va416x0/build_test.sh clean` for flashing examples. -Example boot ouput on UART 0 (MCU TX): +Example boot output on UART 0 (MCU TX): ``` wolfBoot HAL Init -Boot partition: 0x9800 (sz 5060, ver 0x1, type 0x601) -Partition 1 header magic 0x00000000 invalid at 0x24800 -Boot partition: 0x9800 (sz 5060, ver 0x1, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x1, type 0x601) +Partition 1 header magic 0x00000000 invalid at 0x25800 +Boot partition: 0xB800 (sz 5600, ver 0x1, type 0x601) Booting version: 0x1 +Checking integrity...done +Verifying signature...done ======================== VA416x0 wolfBoot demo Application Copyright 2025 wolfSSL Inc @@ -9000,8 +9033,19 @@ Number of public keys: 1 9B BE B7 BB 11 75 01 81 45 14 19 7E B2 BD C0 A6 11 0C FA F6 B5 F9 59 BA B9 A5 8E 34 4A CD C5 83 7E 43 EF 61 6E C4 15 88 3C FE D6 76 47 D9 82 A4 + +Vector table +==================================== +VTOR : 0x0000BC00 (expected 0x0000BC00) OK +SysTick : ticking (113 -> 1738 ms) ``` +`VTOR` is the address the core fetches exceptions from, and it must match +`WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE`. The SysTick line is a +liveness check: `HAL_Init()` starts SysTick and `SysTick_Handler()` advances +`HAL_time_ms`, so a counter that never moves means exceptions are not reaching +the application's vector table. + ### Debugging Vorago VA416x0 Start the GDB server: `JLinkGDBServer -device VA416XX -if SWD -speed 2000 -port 3333` @@ -9014,7 +9058,7 @@ See `tools/scripts/va416x0/build_test.sh update`: ```sh # Sign a new test app with version 2 -IMAGE_HEADER_SIZE=512 ./tools/keytools/sign --ecc384 --sha384 test-app/image.bin wolfboot_signing_private_key.der 2 +IMAGE_HEADER_SIZE=1024 ./tools/keytools/sign --ecc384 --sha384 test-app/image.bin wolfboot_signing_private_key.der 2 # Create a bin footer with wolfBoot trailer "BOOT" and "p" (ASCII for 0x70 == IMG_STATE_UPDATING) echo -n "pBOOT" > trigger_magic.bin @@ -9046,17 +9090,17 @@ Example update output: ``` wolfBoot HAL Init -Boot partition: 0x9800 (sz 5060, ver 0x1, type 0x601) -Update partition: 0x24800 (sz 5060, ver 0x2, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x1, type 0x601) +Update partition: 0x25800 (sz 5600, ver 0x2, type 0x601) Starting Update (fallback allowed 0) -Update partition: 0x24800 (sz 5060, ver 0x2, type 0x601) -Boot partition: 0x9800 (sz 5060, ver 0x1, type 0x601) +Update partition: 0x25800 (sz 5600, ver 0x2, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x1, type 0x601) Versions: Current 0x1, Update 0x2 Copy sector 0 (part 1->2) Copy sector 0 (part 0->1) Copy sector 0 (part 2->0) -Boot partition: 0x9800 (sz 5060, ver 0x2, type 0x601) -Update partition: 0x24800 (sz 5060, ver 0x1, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x2, type 0x601) +Update partition: 0x25800 (sz 5600, ver 0x1, type 0x601) Copy sector 1 (part 1->2) Copy sector 1 (part 0->1) Copy sector 1 (part 2->0) @@ -9064,11 +9108,11 @@ Copy sector 2 (part 1->2) Copy sector 2 (part 0->1) Copy sector 2 (part 2->0) Erasing remainder of partition (50 sectors)... -Boot partition: 0x9800 (sz 5060, ver 0x2, type 0x601) -Update partition: 0x24800 (sz 5060, ver 0x1, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x2, type 0x601) +Update partition: 0x25800 (sz 5600, ver 0x1, type 0x601) Copy sector 52 (part 0->2) Copied boot sector to swap -Boot partition: 0x9800 (sz 5060, ver 0x2, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x2, type 0x601) Booting version: 0x1 ======================== VA416x0 wolfBoot demo Application @@ -9105,9 +9149,9 @@ Boot logs after hard reset: ``` wolfBoot HAL Init -Boot partition: 0x9800 (sz 5060, ver 0x2, type 0x601) -Update partition: 0x24800 (sz 5060, ver 0x1, type 0x601) -Boot partition: 0x9800 (sz 5060, ver 0x2, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x2, type 0x601) +Update partition: 0x25800 (sz 5600, ver 0x1, type 0x601) +Boot partition: 0xB800 (sz 5600, ver 0x2, type 0x601) Booting version: 0x2 ======================== VA416x0 wolfBoot demo Application diff --git a/hal/va416x0.c b/hal/va416x0.c index b94d027f80..43109d3231 100644 --- a/hal/va416x0.c +++ b/hal/va416x0.c @@ -41,6 +41,15 @@ #include "printf.h" #include "loader.h" + +/* The application vector table must be 1024-aligned: this core ORs the vector + * offset into VTOR rather than adding it, so with 212 entries every IRQ >= 112 + * misdispatches otherwise. See docs/Targets.md. */ +#if defined(WOLFBOOT_PARTITION_BOOT_ADDRESS) && defined(IMAGE_HEADER_SIZE) +#if (((WOLFBOOT_PARTITION_BOOT_ADDRESS) + (IMAGE_HEADER_SIZE)) & 0x3FF) != 0 +#error "VA416x0: application vector table must be 1024-aligned. Raise IMAGE_HEADER_SIZE (1024) or move WOLFBOOT_PARTITION_BOOT_ADDRESS." +#endif +#endif #endif #ifndef WOLFBOOT_UNIT_TEST_VA416X0_FRAM @@ -117,22 +126,11 @@ static void UartInit(VOR_UART_Type* uart, uint32_t baudrate) uart->CTRL |= UART_CTRL_AUTORTS_Msk; #endif - /* Enable RX interrupts as soon as a character is received */ - uart->IRQ_ENB = UART_IRQ_ENB_IRQ_RX_Msk; - uart->RXFIFOIRQTRG = 1; + /* Transmit-only: nothing reads the UART, and __HAL_DISABLE_UART0/1/2 + * means the SDK builds no UART IRQ handler to vector to. */ + uart->IRQ_ENB = 0; uart->TXFIFOIRQTRG = 8; - if (VOR_UART0 == uart) { - NVIC_SetPriority(UART0_RX_IRQn, 1); - NVIC_EnableIRQ(UART0_RX_IRQn); - } else if (VOR_UART1 == uart) { - NVIC_SetPriority(UART1_RX_IRQn, 1); - NVIC_EnableIRQ(UART1_RX_IRQn); - } else { - NVIC_SetPriority(UART2_RX_IRQn, 1); - NVIC_EnableIRQ(UART2_RX_IRQn); - } - /* Enable UART */ uart->ENABLE = (UART_ENABLE_RXENABLE_Msk | UART_ENABLE_TXENABLE_Msk); @@ -181,7 +179,10 @@ void uart_flush(void) #define FRAM_SLEEP 0xB9 #ifndef USE_HAL_SPI_FRAM +/* One FRAM device at a time: transmits always use spiHandle, so record its + * bank and reject any other rather than drain one bank and transmit on it. */ static hal_spi_handle_t spiHandle; +static uint8_t spiFramBank = SPI_NUM_BANKS; /* invalid until a good init */ static void FRAM_WaitIdle(uint8_t spiBank) { @@ -213,6 +214,11 @@ hal_status_t FRAM_Init(uint8_t spiBank, uint8_t csNum) hal_status_t status = hal_status_ok; uint8_t spiData[2]; + /* Bounds check before indexing BANK[], as FRAM_WaitIdle() does */ + if (spiBank >= SPI_NUM_BANKS) { + return hal_status_badParam; + } + /* Initialize the SPI handle */ memset(&spiHandle, 0, sizeof(spiHandle)); spiHandle.locked = false; @@ -243,6 +249,16 @@ hal_status_t FRAM_Init(uint8_t spiBank, uint8_t csNum) FRAM_WaitIdle(spiBank); spiHandle.state = hal_spi_state_ready; } + if (status == hal_status_ok) { + /* Only bind on success: a failed init must not leave the gate open */ + spiFramBank = spiBank; + } + else { + /* spiHandle was already repointed at spiBank but never brought up. + * Close the gate rather than leave it naming a previously good + * bank that the handle no longer refers to. */ + spiFramBank = SPI_NUM_BANKS; + } wolfBoot_printf("FRAM_Init: status %d\n", status); return status; } @@ -254,11 +270,11 @@ hal_status_t FRAM_Write(uint8_t spiBank, uint32_t addr, uint8_t *buf, uint8_t spiData[4]; /* Validate input parameters */ - if (buf == NULL || len == 0) { + if (buf == NULL || len == 0 || spiBank != spiFramBank) { return hal_status_badParam; } - /* Bounds check: ensure write doesn't exceed FRAM size */ - if (addr >= FRAM_SIZE || (addr + len) > FRAM_SIZE) { + /* Compare against the space remaining: addr + len could wrap */ + if (addr >= FRAM_SIZE || len > (uint32_t)FRAM_SIZE - addr) { return hal_status_badParam; } @@ -291,11 +307,11 @@ hal_status_t FRAM_Read(uint8_t spiBank, uint32_t addr, uint8_t *buf, uint8_t spiData[4]; /* Validate input parameters */ - if (buf == NULL || len == 0) { + if (buf == NULL || len == 0 || spiBank != spiFramBank) { return hal_status_badParam; } - /* Bounds check: ensure read doesn't exceed FRAM size */ - if (addr >= FRAM_SIZE || (addr + len) > FRAM_SIZE) { + /* Compare against the space remaining: addr + len could wrap */ + if (addr >= FRAM_SIZE || len > (uint32_t)FRAM_SIZE - addr) { return hal_status_badParam; } @@ -331,7 +347,7 @@ hal_status_t FRAM_Erase(uint8_t spiBank, uint32_t addr, uint32_t len) while (len > 0) { uint32_t erase_len = (len > sizeof(data)) ? sizeof(data) : len; - status = FRAM_Write(ROM_SPI_BANK, addr, data, erase_len); + status = FRAM_Write(spiBank, addr, data, erase_len); if (status != hal_status_ok) { /* Return the hal_status_t unmodified; ext_flash_erase() is * the single negation point to a negative error code. */ @@ -355,21 +371,21 @@ void RAMFUNCTION hal_flash_lock(void) } +/* No internal flash. All partitions are external (PART_*_EXT), so these are + * unreachable; return an error so a config that reaches them fails loudly. */ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) { - /* not supported - no internal flash */ (void)address; (void)data; (void)len; - return 0; + return -1; } int RAMFUNCTION hal_flash_erase(uint32_t address, int len) { - /* not supported - no internal flash */ (void)address; (void)len; - return 0; + return -1; } #endif /* !WOLFBOOT_UNIT_TEST_VA416X0_FRAM */ @@ -448,6 +464,8 @@ int ext_flash_write(uintptr_t address, const uint8_t *data, int len) return len; } +/* The shadow update below re-syncs IRAM from FRAM, so it is a repair rather + * than a cache fill, and is a no-op unless the caller holds ROM_PROT.WREN. */ int ext_flash_read(uintptr_t address, uint8_t *data, int len) { hal_status_t status; @@ -494,7 +512,8 @@ static int test_ext_flash(void) { int ret; uint32_t i; - uint8_t pageData[WOLFBOOT_SECTOR_SIZE] = { 0 }; + /* static: WOLFBOOT_SECTOR_SIZE is far too large for the stack */ + static uint8_t pageData[WOLFBOOT_SECTOR_SIZE]; #ifndef READONLY /* Erase sector */ @@ -563,7 +582,7 @@ void hal_init(void) /* Configure PLL to set CPU clock to 100MHz - 40MHz crystal * 2.5 */ status = HAL_Clkgen_PLL(CLK_CTRL0_XTAL_N_PLL2P5X); if (status != hal_status_ok) { - /* continue anyways */ + /* continue anyways: no UART yet, so this cannot be reported */ } /* Disable Watchdog - should be already disabled out of reset */ @@ -581,13 +600,13 @@ void hal_init(void) /* Call SDK HAL initialization function */ status = HAL_Init(); if (status != hal_status_ok) { - /* continue anyways */ + /* continue anyways: no UART yet, so this cannot be reported */ } /* Configure the pins */ status = HAL_Iocfg_SetupPins(bootDefaultConfig); if (status != hal_status_ok) { - /* continue anyways */ + /* continue anyways: no UART yet, so this cannot be reported */ } #ifdef DEBUG_UART @@ -600,10 +619,8 @@ void hal_init(void) /* Init the FRAM SPI device */ status = FRAM_Init(ROM_SPI_BANK, ROM_SPI_CSN); if (status != hal_status_ok) { - #ifdef DEBUG - wolfBoot_printf("FRAM_Init failed\n"); - #endif - /* continue anyways */ + wolfBoot_printf("FRAM_Init failed: status %d\n", status); + /* Continue: the image checks fail closed if FRAM is unreadable */ } #ifdef TEST_EXT_FLASH @@ -616,19 +633,10 @@ void hal_prepare_boot(void) #ifdef DEBUG_UART uart_flush(); - /* Disable UART to give app a clean state */ + /* Disable UART to give app a clean state. UartInit() enables no UART + * interrupt, so there is no NVIC state to unwind here. */ DEBUG_UART_BASE->IRQ_ENB = 0; DEBUG_UART_BASE->ENABLE = 0; - #if defined(DEBUG_UART_NUM) && DEBUG_UART_NUM == 1 - NVIC_DisableIRQ(UART1_RX_IRQn); - NVIC_ClearPendingIRQ(UART1_RX_IRQn); - #elif defined(DEBUG_UART_NUM) && DEBUG_UART_NUM == 2 - NVIC_DisableIRQ(UART2_RX_IRQn); - NVIC_ClearPendingIRQ(UART2_RX_IRQn); - #else /* default: UART0 */ - NVIC_DisableIRQ(UART0_RX_IRQn); - NVIC_ClearPendingIRQ(UART0_RX_IRQn); - #endif #endif #ifdef WOLFBOOT_RESTORE_CLOCK diff --git a/hal/va416x0.h b/hal/va416x0.h index d8294ca92f..752422d69a 100644 --- a/hal/va416x0.h +++ b/hal/va416x0.h @@ -70,11 +70,13 @@ /** Default pin IOCONFIG register. type: un_iocfg_reg_t - see va416xx_hal_ioconfig.h */ /** A pin's IOCONFIG is set to this by HAL_Iocfg_Init() if that pin is not in the cfg array */ -#define DEFAULT_PIN_IOCFG (IOCFG_REG_PULLDN) // internal pulldown enabled for input pin +/* internal pulldown enabled for input pin */ +#define DEFAULT_PIN_IOCFG (IOCFG_REG_PULLDN) /** Default pin direction (input/output) type: en_iocfg_dir_t - see va416xx_hal_ioconfig.h */ /** A pin's DIR is set to this by HAL_Iocfg_Init() if that pin is not in the cfg array */ -#define DEFAULT_PIN_DIR (en_iocfg_dir__input) // default pin input +/* default pin direction is input */ +#define DEFAULT_PIN_DIR (en_iocfg_dir__input) /* PEB1-VA416XX-EVK */ diff --git a/hal/va416x0.ld b/hal/va416x0.ld index 922bb40456..c8ed1fd8c9 100644 --- a/hal/va416x0.ld +++ b/hal/va416x0.ld @@ -6,8 +6,9 @@ MEMORY { IRAM (rx) :ORIGIN = 0x00000000, LENGTH = 256K EBI (xrw) :ORIGIN = 0x10000000, LENGTH = 16M - RAM0 (xrw) :ORIGIN = 0x1FFF8000, LENGTH = 32K - RAM1 (xrw) :ORIGIN = 0x20000000, LENGTH = 32K + /* SRAM_0 (0x1FFF8000) and SRAM_1 (0x20000000) are contiguous, so they are + pooled as one 64K region. They stay separate EDAC scrub banks. */ + RAM0 (xrw) :ORIGIN = 0x1FFF8000, LENGTH = 64K } /* Define output sections */ @@ -132,13 +133,13 @@ SECTIONS __bss_end__ = _end_bss; } >RAM0 - /* DMA control block, goes into fixed location in RAM1 */ + /* DMA control block. Empty unless the SDK DMA driver is linked in; the + engine only requires the 128 byte alignment. */ .dma_blk (NOLOAD) : { . = ALIGN(128); - /**(.ARM.__at_0x20000000) */ *(dma_blk) - } >RAM1 + } >RAM0 /* User_stack section, used to check that there is enough RAM left */ ._user_stack : diff --git a/src/boot_arm.c b/src/boot_arm.c index f59c76c2d5..763f55cf5b 100644 --- a/src/boot_arm.c +++ b/src/boot_arm.c @@ -837,8 +837,214 @@ void (* const IV[])(void) = isr_empty, isr_empty, isr_empty, +#elif defined(TARGET_va416x0) + /* IRQ 0 through TXEV_IRQn (195), for 212 entries. wolfBoot enables the + * EDAC error IRQs (76, 77), so those slots must exist. */ + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, #endif }; + +#ifdef TARGET_va416x0 +/* 16 system exceptions + 196 external IRQs. A short table sends the EDAC + * error IRQs (76, 77) into .text; fail the build instead. */ +typedef char va416x0_iv_len_check[ + (sizeof(IV) / sizeof(IV[0]) == 212) ? 1 : -1]; +#endif #endif #ifdef RAM_CODE diff --git a/test-app/ARM-va416x0.ld b/test-app/ARM-va416x0.ld index b245407561..5570c79ef8 100644 --- a/test-app/ARM-va416x0.ld +++ b/test-app/ARM-va416x0.ld @@ -6,8 +6,9 @@ MEMORY { IRAM (rx) :ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ EBI (xrw) :ORIGIN = 0x10000000, LENGTH = 16M - RAM0 (xrw) :ORIGIN = 0x1FFF8000, LENGTH = 32K - RAM1 (xrw) :ORIGIN = 0x20000000, LENGTH = 32K + /* SRAM_0 (0x1FFF8000) and SRAM_1 (0x20000000) are contiguous, so they are + pooled as one 64K region. They stay separate EDAC scrub banks. */ + RAM0 (xrw) :ORIGIN = 0x1FFF8000, LENGTH = 64K } /* Define output sections */ @@ -132,13 +133,13 @@ SECTIONS __bss_end__ = _end_bss; } >RAM0 - /* DMA control block, goes into fixed location in RAM1 */ + /* DMA control block. Empty unless the SDK DMA driver is linked in; the + engine only requires the 128 byte alignment. */ .dma_blk (NOLOAD) : { . = ALIGN(128); - /**(.ARM.__at_0x20000000) */ *(dma_blk) - } >RAM1 + } >RAM0 /* User_heap_stack section, used to check that there is enough RAM left */ ._user_heap_stack : diff --git a/test-app/app_va416x0.c b/test-app/app_va416x0.c index c1e6f98cd5..ba86d4c78f 100644 --- a/test-app/app_va416x0.c +++ b/test-app/app_va416x0.c @@ -55,6 +55,44 @@ int benchmark_test(void *args); #include "va416xx_hal_timer.h" #include "va416xx_hal_ioconfig.h" +/* Confirm VTOR points at the application's table and that exceptions reach it. + * SysTick advances HAL_time_ms, so a frozen counter means they do not. */ +static void print_vector_table_info(void) +{ + uint32_t vtor; + uint32_t expect; + uint32_t t0, t1; /* short delta; keeps the print 32-bit */ + volatile uint32_t spin; + + vtor = SCB->VTOR; + expect = (uint32_t)(WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE); + + wolfBoot_printf("\r\n"); + wolfBoot_printf("Vector table\r\n"); + wolfBoot_printf("====================================\r\n"); + wolfBoot_printf("VTOR : 0x%08lx (expected 0x%08lx) %s\r\n", + (unsigned long)vtor, (unsigned long)expect, + (vtor == expect) ? "OK" : "MISMATCH"); + + /* The core ORs the vector offset into VTOR rather than adding it, so a + * 212-entry table that is not 1024-aligned misdispatches every IRQ from + * 112 up while SysTick and the EDAC IRQs still look healthy. */ + wolfBoot_printf("VT alignment : %lu %s\r\n", + (unsigned long)(vtor & 0x3FFU), + ((vtor & 0x3FFU) == 0) ? "(1024-aligned) OK" + : "*** NOT 1024-ALIGNED: IRQ >= 112 broken ***"); + + t0 = (uint32_t)HAL_time_ms; + /* Bounded: HAL_Timer_DelayMs() spins on HAL_time_ms and would hang */ + for (spin = 0; spin < 2000000UL; spin++) { + } + t1 = (uint32_t)HAL_time_ms; + + wolfBoot_printf("SysTick : %s (%lu -> %lu ms)\r\n", + (t1 != t0) ? "ticking" : "STOPPED", + (unsigned long)t0, (unsigned long)t1); +} + static uint8_t boot_part_state = IMG_STATE_NEW; static uint8_t update_part_state = IMG_STATE_NEW; @@ -87,10 +125,9 @@ static const char *part_state_name(uint8_t state) static int print_info(void) { - int i, j; + uint32_t i, j; uint32_t cur_fw_version, update_fw_version; uint32_t n_keys; - uint16_t hdrSz; cur_fw_version = wolfBoot_current_firmware_version(); update_fw_version = wolfBoot_update_firmware_version(); @@ -130,8 +167,8 @@ static int print_info(void) uint8_t *keybuf = keystore_get_buffer(i); wolfBoot_printf("\r\n"); - wolfBoot_printf(" Public Key #%d: size %lu, type %lx, mask %08lx\r\n", i, - size, type, mask); + wolfBoot_printf(" Public Key #%lu: size %lu, type %lx, mask %08lx\r\n", + (unsigned long)i, size, type, mask); wolfBoot_printf(" ====================================\r\n "); for (j = 0; j < size; j++) { wolfBoot_printf("%02X ", keybuf[j]); @@ -164,6 +201,8 @@ void main(void) print_info(); + print_vector_table_info(); + #ifdef WOLFCRYPT_TEST wolfBoot_printf("\r\nRunning wolfCrypt tests...\r\n"); wolfCrypt_Init(); diff --git a/test-app/startup_arm.c b/test-app/startup_arm.c index 480a4482a8..94691cbbe5 100644 --- a/test-app/startup_arm.c +++ b/test-app/startup_arm.c @@ -582,5 +582,211 @@ void (* const IV[])(void) = isr_empty, // TIM8_CC_IRQ 46 isr_empty, // DMA1_STREAM7_IRQ 47 + +#elif defined(TARGET_va416x0) /* For Vorago VA416xx */ + /* IRQ 0 through TXEV_IRQn (195), for 212 entries. wolfBoot points VTOR + * at this table; see docs/Targets.md for the alignment it needs. */ + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, + isr_empty, #endif }; + +#ifdef TARGET_va416x0 +/* 16 system exceptions + 196 external IRQs; see src/boot_arm.c. */ +typedef char va416x0_app_iv_len_check[ + (sizeof(IV) / sizeof(IV[0]) == 212) ? 1 : -1]; +#endif diff --git a/tools/scripts/va416x0/build_test.sh b/tools/scripts/va416x0/build_test.sh index 818b7b905d..b7d161eee7 100755 --- a/tools/scripts/va416x0/build_test.sh +++ b/tools/scripts/va416x0/build_test.sh @@ -30,6 +30,13 @@ else fi fi +# Select a specific probe when more than one J-Link is attached: +# JLINK_SN=660016516 tools/scripts/va416x0/build_test.sh clean +JLINK_SEL="" +if [ -n "${JLINK_SN}" ]; then + JLINK_SEL="-SelectEmuBySN ${JLINK_SN}" +fi + # Function to get value from .config file get_config_value() { grep "^${1}" .config | sed -E "s/^${1}[?]?=//" | head -n1 @@ -46,15 +53,35 @@ HASH=$(get_config_value "HASH") SIGN_ARG="--$(echo "${SIGN}" | tr '[:upper:]' '[:lower:]')" HASH_ARG="--$(echo "${HASH}" | tr '[:upper:]' '[:lower:]')" -# Common build steps -make clean && make wolfboot.bin && make test-app/image.bin +# Keep as separate statements: under `set -e` a failure mid `a && b` does not +# exit, which would let a broken build sign and flash a stale image. +make clean +make wolfboot.bin +make test-app/image.bin + +# The sign tool reads several settings from the environment at run time, so +# mirror SIGN_ENV from the top-level Makefile. Only non-empty values are +# forwarded: sign.c uses getenv(), where an empty string is not the same as +# unset -- ML_DSA_LEVEL="" would parse as level 0 rather than the built-in +# default. Without ML_DSA_LEVEL an ML-DSA key is rejected with +# "unrecognized ml-dsa key size". +SIGN_ENV="" +for _sign_var in ML_DSA_LEVEL IMAGE_SIGNATURE_SIZE NVM_FLASH_WRITEONCE \ + WOLFBOOT_PARTITION_UPDATE_SIZE LMS_LEVELS LMS_HEIGHT \ + LMS_WINTERNITZ XMSS_PARAMS; do + _sign_val=$(get_config_value "${_sign_var}") + if [ -n "${_sign_val}" ]; then + SIGN_ENV="${SIGN_ENV} ${_sign_var}=${_sign_val}" + fi +done # Function to sign image sign_image() { - IMAGE_HEADER_SIZE="${IMAGE_HEADER_SIZE}" \ - WOLFBOOT_PARTITION_SIZE="${PARTITION_SIZE}" \ - WOLFBOOT_SECTOR_SIZE="${SECTOR_SIZE}" \ - ./tools/keytools/sign "${SIGN_ARG}" "${HASH_ARG}" test-app/image.bin wolfboot_signing_private_key.der "$1" + env IMAGE_HEADER_SIZE="${IMAGE_HEADER_SIZE}" \ + WOLFBOOT_PARTITION_SIZE="${PARTITION_SIZE}" \ + WOLFBOOT_SECTOR_SIZE="${SECTOR_SIZE}" \ + ${SIGN_ENV} \ + ./tools/keytools/sign "${SIGN_ARG}" "${HASH_ARG}" test-app/image.bin wolfboot_signing_private_key.der "$1" } # Function to print summary @@ -72,21 +99,25 @@ print_summary() { if [ "$MODE" = "clean" ]; then sign_image "${VERSION}" - dd if=/dev/zero of=blank_update.bin bs=1K count=108 + # Blank the update partition so the header magic reads invalid. Exactly + # PARTITION_SIZE bytes: rounding up would overrun the partition into swap. + dd if=/dev/zero of=blank_update.bin bs=$((PARTITION_SIZE)) \ + count=1 2>/dev/null ./tools/bin-assemble/bin-assemble factory.bin 0x0 wolfboot.bin \ "${BOOT_ADDRESS}" test-app/image_v${VERSION}_signed.bin \ "${UPDATE_ADDRESS}" blank_update.bin - "${JLINK}" -CommanderScript tools/scripts/va416x0/flash_va416xx.jlink + "${JLINK}" ${JLINK_SEL} -CommanderScript tools/scripts/va416x0/flash_va416xx.jlink print_summary else TRIGGER_ADDRESS=$(printf "0x%X" $(("${UPDATE_ADDRESS}" + "${PARTITION_SIZE}" - 5))) PREV_VERSION=$(("${VERSION}" - 1)) - sign_image "${PREV_VERSION}" && sign_image "${VERSION}" + sign_image "${PREV_VERSION}" + sign_image "${VERSION}" echo -n "pBOOT" > trigger_magic.bin ./tools/bin-assemble/bin-assemble update.bin 0x0 wolfboot.bin \ "${BOOT_ADDRESS}" test-app/image_v${PREV_VERSION}_signed.bin \ "${UPDATE_ADDRESS}" test-app/image_v${VERSION}_signed.bin \ "${TRIGGER_ADDRESS}" trigger_magic.bin - "${JLINK}" -CommanderScript tools/scripts/va416x0/flash_va416xx_update.jlink + "${JLINK}" ${JLINK_SEL} -CommanderScript tools/scripts/va416x0/flash_va416xx_update.jlink print_summary "${TRIGGER_ADDRESS}" "${PREV_VERSION}" fi diff --git a/tools/unit-tests/unit-va416x0-fram.c b/tools/unit-tests/unit-va416x0-fram.c index bd6ef04056..728b914596 100644 --- a/tools/unit-tests/unit-va416x0-fram.c +++ b/tools/unit-tests/unit-va416x0-fram.c @@ -10,8 +10,8 @@ * FRAM bounds check from rejecting the test buffer (the hardware value * is 256 KiB). */ #define FRAM_SIZE (0xFFFFFFFFU) -#define ROM_SPI_BANK 0 -#define SPI_NUM_BANKS 1 +#define ROM_SPI_BANK 3 +#define SPI_NUM_BANKS 4 #define SPI_STATUS_TFE_Msk 0x01U #define SPI_STATUS_BUSY_Msk 0x02U #define SPI_FIFO_CLR_RXFIFO_Msk 0x01U @@ -142,8 +142,11 @@ int wolfBoot_printf(const char *fmt, ...) static void reset_spi_mocks(void) { + int i; + memset(&mock_vor_spi, 0, sizeof(mock_vor_spi)); - mock_vor_spi.BANK[0].STATUS = SPI_STATUS_TFE_Msk; + for (i = 0; i < SPI_NUM_BANKS; i++) + mock_vor_spi.BANK[i].STATUS = SPI_STATUS_TFE_Msk; } START_TEST(test_fram_write_command_failure_aborts_split_transaction) @@ -232,12 +235,19 @@ END_TEST */ START_TEST(test_ext_flash_erase_success_fills_iram) { + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; hal_status_t ok_script[] = { hal_status_ok, hal_status_ok, hal_status_ok, hal_status_ok, hal_status_ok, hal_status_ok }; int i; + reset_spi_mocks(); + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(ROM_SPI_BANK, 0), hal_status_ok); + reset_spi_mocks(); memset(g_iram, 0xA5, sizeof(g_iram)); @@ -256,12 +266,19 @@ END_TEST * diag_erase in src/libwolfboot.c). */ START_TEST(test_ext_flash_erase_failure_returns_negative) { + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; hal_status_t fail_script[] = { hal_status_ok, hal_status_ok, hal_status_err }; int i; int ret; + reset_spi_mocks(); + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(ROM_SPI_BANK, 0), hal_status_ok); + reset_spi_mocks(); memset(g_iram, 0xA5, sizeof(g_iram)); @@ -275,6 +292,150 @@ START_TEST(test_ext_flash_erase_failure_returns_negative) } END_TEST +/* FRAM_Erase() must drive the bank it was handed; pre-fix it hardcoded + * ROM_SPI_BANK. FRAM_WaitIdle() clears that bank's FIFO, so it is visible. */ +START_TEST(test_fram_erase_uses_requested_bank) +{ + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + hal_status_t ok_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + + reset_spi_mocks(); + + /* Bind the driver to bank 1, not the default ROM_SPI_BANK. */ + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(1, 0), hal_status_ok); + + reset_spi_mocks(); + set_transmit_script(ok_script, 3); + ck_assert_int_eq(FRAM_Erase(1, 0x40, 32), hal_status_ok); + + ck_assert_uint_eq(mock_vor_spi.BANK[1].FIFO_CLR, + SPI_FIFO_CLR_RXFIFO_Msk | SPI_FIFO_CLR_TXFIFO_Msk); + ck_assert_uint_eq(mock_vor_spi.BANK[ROM_SPI_BANK].FIFO_CLR, 0); +} +END_TEST + +/* One device at a time: a bank other than the one FRAM_Init() bound to must + * be rejected, not drained while transmitting on the init handle's bank. */ +START_TEST(test_fram_rejects_bank_other_than_initialized) +{ + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + uint8_t buf[4] = { 1, 2, 3, 4 }; + + reset_spi_mocks(); + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(1, 0), hal_status_ok); + + reset_spi_mocks(); + set_transmit_script(init_script, 0); + ck_assert_int_eq(FRAM_Write(2, 0x40, buf, sizeof(buf)), + hal_status_badParam); + ck_assert_int_eq(FRAM_Read(2, 0x40, buf, sizeof(buf)), + hal_status_badParam); + /* No SPI traffic at all on a rejected request. */ + ck_assert_int_eq(transmit_call_count, 0); +} +END_TEST + +/* FRAM_Init() must bounds check spiBank before indexing BANK[], as + * FRAM_WaitIdle() already did. */ +START_TEST(test_fram_init_rejects_out_of_range_bank) +{ + reset_spi_mocks(); + set_transmit_script(NULL, 0); + ck_assert_int_eq(FRAM_Init(SPI_NUM_BANKS, 0), hal_status_badParam); + ck_assert_int_eq(transmit_call_count, 0); +} +END_TEST + +/* NULL buffer and zero length must be rejected before any SPI traffic. */ +START_TEST(test_fram_rejects_null_buffer_and_zero_length) +{ + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + uint8_t buf[4] = { 0 }; + + reset_spi_mocks(); + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(ROM_SPI_BANK, 0), hal_status_ok); + + reset_spi_mocks(); + set_transmit_script(init_script, 0); + ck_assert_int_eq(FRAM_Write(ROM_SPI_BANK, 0x40, NULL, 4), + hal_status_badParam); + ck_assert_int_eq(FRAM_Write(ROM_SPI_BANK, 0x40, buf, 0), + hal_status_badParam); + ck_assert_int_eq(FRAM_Read(ROM_SPI_BANK, 0x40, NULL, 4), + hal_status_badParam); + ck_assert_int_eq(FRAM_Read(ROM_SPI_BANK, 0x40, buf, 0), + hal_status_badParam); + ck_assert_int_eq(transmit_call_count, 0); +} +END_TEST + +/* addr 0xFFFFFFF0 leaves 15 bytes, so a 32 byte request must be rejected. + * The pre-fix `(addr + len) > FRAM_SIZE` wrapped to 0x10 and allowed it. */ +START_TEST(test_fram_bounds_check_survives_address_wrap) +{ + hal_status_t init_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + uint8_t buf[32] = { 0 }; + + reset_spi_mocks(); + set_transmit_script(init_script, 3); + ck_assert_int_eq(FRAM_Init(ROM_SPI_BANK, 0), hal_status_ok); + + reset_spi_mocks(); + set_transmit_script(init_script, 0); + ck_assert_int_eq( + FRAM_Write(ROM_SPI_BANK, 0xFFFFFFF0U, buf, sizeof(buf)), + hal_status_badParam); + ck_assert_int_eq( + FRAM_Read(ROM_SPI_BANK, 0xFFFFFFF0U, buf, sizeof(buf)), + hal_status_badParam); + ck_assert_int_eq(transmit_call_count, 0); +} +END_TEST + +/* A failed FRAM_Init() must not bind the bank: doing so would let a later + * transfer through the gate onto an incompletely initialized handle. */ +START_TEST(test_failed_init_does_not_bind_bank) +{ + hal_status_t ok_script[] = { + hal_status_ok, hal_status_ok, hal_status_ok + }; + hal_status_t fail_script[] = { hal_status_err }; + uint8_t buf[4] = { 1, 2, 3, 4 }; + + reset_spi_mocks(); + set_transmit_script(ok_script, 3); + ck_assert_int_eq(FRAM_Init(ROM_SPI_BANK, 0), hal_status_ok); + + /* Now fail an init on a different bank. */ + reset_spi_mocks(); + set_transmit_script(fail_script, 1); + ck_assert_int_ne(FRAM_Init(1, 0), hal_status_ok); + + /* The failed init leaves spiHandle pointing at bank 1, so the gate must + * close entirely rather than keep naming ROM_SPI_BANK. */ + reset_spi_mocks(); + set_transmit_script(fail_script, 0); + ck_assert_int_eq(FRAM_Write(1, 0x40, buf, sizeof(buf)), + hal_status_badParam); + ck_assert_int_eq(FRAM_Write(ROM_SPI_BANK, 0x40, buf, sizeof(buf)), + hal_status_badParam); + ck_assert_int_eq(transmit_call_count, 0); +} +END_TEST + Suite *va416x0_fram_suite(void) { Suite *s = suite_create("va416x0-fram"); @@ -285,6 +446,12 @@ Suite *va416x0_fram_suite(void) tcase_add_test(tc, test_iram_fill_unaligned_64bit_addr); tcase_add_test(tc, test_ext_flash_erase_success_fills_iram); tcase_add_test(tc, test_ext_flash_erase_failure_returns_negative); + tcase_add_test(tc, test_fram_erase_uses_requested_bank); + tcase_add_test(tc, test_fram_rejects_bank_other_than_initialized); + tcase_add_test(tc, test_fram_init_rejects_out_of_range_bank); + tcase_add_test(tc, test_fram_rejects_null_buffer_and_zero_length); + tcase_add_test(tc, test_fram_bounds_check_survives_address_wrap); + tcase_add_test(tc, test_failed_init_does_not_bind_bank); suite_add_tcase(s, tc); return s;