mirror of https://github.com/wolfSSL/wolfBoot.git
F-3962: fix hal_flash_erase loop bound in stm32u3.c skipping final partial page
end_address was computed as address + len - 1 (inclusive last byte) but the loop used strict p < end_address, so a non-page-aligned len caused the final page to be skipped. Change to end_address = address + len (exclusive) to match stm32u5.c, the claimed reference implementation. Add WOLFBOOT_UNIT_TEST_FLASH_ERASE guards to hal/stm32u3.c and hal/stm32u3.h (DMB/ISB/DSB, FLASH_NS_CR/SR register macros, and all non-erase functions) so that hal_flash_erase() can be compiled and tested in isolation on the host. Add tools/unit-tests/unit-flash-erase-u3.c with four check-based tests; the regression case (len = PAGE_SIZE + 1) failed before this fix.pull/795/head
parent
4b95dc2474
commit
ef85a1ec3f
|
|
@ -27,13 +27,16 @@
|
|||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
#include <image.h>
|
||||
#include <string.h>
|
||||
#include "hal/stm32u3.h"
|
||||
#include "hal.h"
|
||||
#include "printf.h"
|
||||
#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */
|
||||
#include "hal/stm32u3.h"
|
||||
|
||||
|
||||
#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
static void RAMFUNCTION flash_set_waitstates(unsigned int waitstates)
|
||||
{
|
||||
uint32_t reg = FLASH_ACR;
|
||||
|
|
@ -148,6 +151,7 @@ void RAMFUNCTION hal_flash_opt_lock(void)
|
|||
if ((FLASH_NS_CR & FLASH_CR_OPTLOCK) == 0)
|
||||
FLASH_NS_CR |= FLASH_CR_OPTLOCK;
|
||||
}
|
||||
#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */
|
||||
|
||||
/* Erase — matches STM32U5 hal pattern exactly (same Cortex-M33 flash controller) */
|
||||
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
||||
|
|
@ -161,7 +165,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
|||
if (address < ARCH_FLASH_OFFSET)
|
||||
return -1;
|
||||
|
||||
end_address = address + len - 1;
|
||||
end_address = address + len;
|
||||
for (p = address; p < end_address; p += FLASH_PAGE_SIZE) {
|
||||
uint32_t reg;
|
||||
uint32_t bker = 0;
|
||||
|
|
@ -194,6 +198,7 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
/* --- UART: USART1 on PA9 (TX) / PA10 (RX), AF7 --- */
|
||||
|
||||
#define USART1_BASE (0x40013800U)
|
||||
|
|
@ -356,3 +361,4 @@ void RAMFUNCTION hal_cache_invalidate(void)
|
|||
;
|
||||
ICACHE_SR |= ICACHE_SR_BSYENDF;
|
||||
}
|
||||
#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@
|
|||
#include <stdint.h>
|
||||
|
||||
/* Assembly helpers */
|
||||
#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
#define DMB() __asm__ volatile ("dmb")
|
||||
#define ISB() __asm__ volatile ("isb")
|
||||
#define DSB() __asm__ volatile ("dsb")
|
||||
#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */
|
||||
|
||||
/* -------- RCC (AHB1 + 0x10C00 = 0x40030C00) -------- */
|
||||
#define RCC_BASE (0x40030C00)
|
||||
|
|
@ -125,10 +127,12 @@
|
|||
#define FLASH_ACR_LPM (1 << 11)
|
||||
|
||||
/* Non-secure flash registers (fail with PGSERR on programmed pages) */
|
||||
#ifndef WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
#define FLASH_NS_KEYR (*(volatile uint32_t *)(FLASH_BASE + 0x08))
|
||||
#define FLASH_NS_OPTKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x10))
|
||||
#define FLASH_NS_SR (*(volatile uint32_t *)(FLASH_BASE + 0x20))
|
||||
#define FLASH_NS_CR (*(volatile uint32_t *)(FLASH_BASE + 0x28))
|
||||
#endif /* !WOLFBOOT_UNIT_TEST_FLASH_ERASE */
|
||||
|
||||
/* Secure flash registers (unused when TZEN=0, kept for reference) */
|
||||
#define FLASH_SKEYR (*(volatile uint32_t *)(FLASH_BASE + 0x0C))
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ TESTS+=unit-flash-erase-wb
|
|||
TESTS+=unit-flash-erase-l0
|
||||
TESTS+=unit-flash-erase-g0
|
||||
TESTS+=unit-flash-erase-c0
|
||||
TESTS+=unit-flash-erase-u3
|
||||
TESTS+=unit-otp-keystore
|
||||
TESTS+=unit-x86-paging-oob
|
||||
TESTS+=unit-fwtpm-nv-oob
|
||||
|
|
@ -309,6 +310,12 @@ unit-flash-erase-g0: unit-flash-erase-g0.c ../../hal/stm32g0.c
|
|||
unit-flash-erase-c0: unit-flash-erase-c0.c ../../hal/stm32c0.c
|
||||
gcc -o $@ unit-flash-erase-c0.c $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
# unit-flash-erase-u3 includes hal/stm32u3.c directly (guarded to hal_flash_erase
|
||||
# via WOLFBOOT_UNIT_TEST_FLASH_ERASE), so stm32u3.c is not a separate input.
|
||||
# -I../../ so that #include "hal/stm32u3.h" resolves from the repo root.
|
||||
unit-flash-erase-u3: unit-flash-erase-u3.c ../../hal/stm32u3.c ../../hal/stm32u3.h
|
||||
gcc -o $@ unit-flash-erase-u3.c -I../../ $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
# unit-otp-keystore includes src/flash_otp_keystore.c directly (guarded to its
|
||||
# host-portable code via WOLFBOOT_UNIT_TEST_OTP_KEYSTORE), so it is not a
|
||||
# separate input.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
/* unit-flash-erase-u3.c
|
||||
*
|
||||
* Unit tests for the page-loop bound in hal_flash_erase() (hal/stm32u3.c).
|
||||
* Regression for F-3962: end_address = address + len - 1 (inclusive) combined
|
||||
* with p < end_address (strict) skips the final page when len % PAGE_SIZE != 0.
|
||||
*
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* hal/stm32u3.c is tightly coupled to STM32U3 hardware registers.
|
||||
* Compile only hal_flash_erase() in isolation by defining this guard;
|
||||
* all other functions and hardware macros are excluded and replaced below. */
|
||||
#define WOLFBOOT_UNIT_TEST_FLASH_ERASE
|
||||
|
||||
/* RAMFUNCTION must be empty on the host */
|
||||
#define RAMFUNCTION
|
||||
|
||||
/* DMB is a no-op on the host (capture erase commands in flash_wait_complete). */
|
||||
#define DMB() /* nothing */
|
||||
|
||||
/* ARCH_FLASH_OFFSET is normally supplied by image.h; define it directly. */
|
||||
#define ARCH_FLASH_OFFSET (0x08000000U)
|
||||
|
||||
/* Mocked non-secure flash control/status registers */
|
||||
static uint32_t mock_FLASH_NS_CR;
|
||||
static uint32_t mock_FLASH_NS_SR;
|
||||
#define FLASH_NS_CR mock_FLASH_NS_CR
|
||||
#define FLASH_NS_SR mock_FLASH_NS_SR
|
||||
|
||||
/* Record the FLASH_NS_CR value on each page-erase command (captured at the
|
||||
* start of flash_wait_complete, when STRT has just been written). */
|
||||
#define ERASE_LOG_MAX 128
|
||||
static uint32_t erase_cr[ERASE_LOG_MAX];
|
||||
static int erase_log_n;
|
||||
|
||||
/* Stubs for functions called by hal_flash_erase().
|
||||
* flash_wait_complete is called immediately after FLASH_NS_CR |= FLASH_CR_STRT,
|
||||
* so it is the right place to capture the current register state.
|
||||
* It clears STRT to mirror what real hardware does automatically. */
|
||||
static void flash_wait_complete(void)
|
||||
{
|
||||
if ((mock_FLASH_NS_CR & (1 << 16)) && erase_log_n < ERASE_LOG_MAX) {
|
||||
erase_cr[erase_log_n] = mock_FLASH_NS_CR;
|
||||
erase_log_n++;
|
||||
}
|
||||
mock_FLASH_NS_CR &= ~(1 << 16); /* clear STRT */
|
||||
}
|
||||
static void flash_clear_errors(void) {}
|
||||
void hal_cache_invalidate(void) {}
|
||||
|
||||
#include "../../hal/stm32u3.c"
|
||||
|
||||
/* Decode the page number field from a captured FLASH_NS_CR value.
|
||||
* Bits [9:3] = PNB (7 bits); bit 11 = BKER (bank select). */
|
||||
static uint32_t page_of(uint32_t cr)
|
||||
{
|
||||
return (cr >> FLASH_CR_PNB_SHIFT) & FLASH_CR_PNB_MASK;
|
||||
}
|
||||
static int bank_of(uint32_t cr)
|
||||
{
|
||||
return (cr & FLASH_CR_BKER) ? 1 : 0;
|
||||
}
|
||||
|
||||
static void reset_mocks(void)
|
||||
{
|
||||
mock_FLASH_NS_CR = 0;
|
||||
mock_FLASH_NS_SR = 0;
|
||||
erase_log_n = 0;
|
||||
}
|
||||
|
||||
/* Erasing exactly one page must issue exactly one erase command. */
|
||||
START_TEST(test_erase_single_page_aligned)
|
||||
{
|
||||
reset_mocks();
|
||||
hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE);
|
||||
|
||||
ck_assert_int_eq(erase_log_n, 1);
|
||||
ck_assert_int_eq(bank_of(erase_cr[0]), 0);
|
||||
ck_assert_uint_eq(page_of(erase_cr[0]), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Erasing two full pages must issue exactly two erase commands. */
|
||||
START_TEST(test_erase_two_pages_aligned)
|
||||
{
|
||||
reset_mocks();
|
||||
hal_flash_erase(0x08000000UL, 2 * FLASH_PAGE_SIZE);
|
||||
|
||||
ck_assert_int_eq(erase_log_n, 2);
|
||||
ck_assert_uint_eq(page_of(erase_cr[0]), 0);
|
||||
ck_assert_uint_eq(page_of(erase_cr[1]), 1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Regression for F-3962: len = PAGE_SIZE + 1 spans two pages and both must be
|
||||
* erased. Before the fix, end_address = address + len - 1 landed exactly on
|
||||
* the start of page 1, so the strict `p < end_address` guard excluded it. */
|
||||
START_TEST(test_erase_unaligned_len_covers_last_page)
|
||||
{
|
||||
reset_mocks();
|
||||
/* len = 0x1001: bytes [0..0x1000], crossing into page 1 */
|
||||
hal_flash_erase(0x08000000UL, FLASH_PAGE_SIZE + 1);
|
||||
|
||||
ck_assert_int_eq(erase_log_n, 2);
|
||||
ck_assert_uint_eq(page_of(erase_cr[0]), 0);
|
||||
ck_assert_uint_eq(page_of(erase_cr[1]), 1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Erasing exactly one page in bank 2 must set BKER and page 0. */
|
||||
START_TEST(test_erase_bank2_page)
|
||||
{
|
||||
reset_mocks();
|
||||
hal_flash_erase(FLASH_BANK2_BASE, FLASH_PAGE_SIZE);
|
||||
|
||||
ck_assert_int_eq(erase_log_n, 1);
|
||||
ck_assert_int_eq(bank_of(erase_cr[0]), 1);
|
||||
ck_assert_uint_eq(page_of(erase_cr[0]), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *flash_erase_u3_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("flash-erase-u3");
|
||||
TCase *tc = tcase_create("flash-erase-u3");
|
||||
|
||||
tcase_add_test(tc, test_erase_single_page_aligned);
|
||||
tcase_add_test(tc, test_erase_two_pages_aligned);
|
||||
tcase_add_test(tc, test_erase_unaligned_len_covers_last_page);
|
||||
tcase_add_test(tc, test_erase_bank2_page);
|
||||
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fails;
|
||||
Suite *s = flash_erase_u3_suite();
|
||||
SRunner *sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
fails = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
|
||||
return fails;
|
||||
}
|
||||
Loading…
Reference in New Issue