F-9764: keep STM32U5 hal_flash_write within the requested length

hal_flash_write() looped while (i < len) but programmed whole 16-byte
units: each iteration read all four 32-bit source words unconditionally
and wrote the full quadword to flash, so a write whose length is not a
multiple of 16 read up to 12 bytes past the caller's buffer - which
the NSC update path validates for exactly len - and wrote those bytes
to flash.

Program the unit word by word: each word is written only when at least
one of its bytes is requested, and a partial final word is padded with
the erased value (0xFF) in its upper bytes so no source bytes past len
are read. The single PG/wait/clear sequence per unit is unchanged.

Test: tools/unit-tests/unit-stm32u5-write.c is the 16-byte twin of the
STM32L5 test (F-9763): it extracts the real hal_flash_write() and its
wait/clear helpers, maps the destination flash at a 32-bit host address
(the parameter is uint32_t on this 32-bit target) pre-filled with stale
data, and runs short writes; pre-fix the bytes past len landed in the
destination flash in all four partial-unit cases.
pull/862/head
Daniele Lacamera 2026-08-18 05:52:08 +02:00
parent 51d147ebb6
commit 263d3d5fbf
3 changed files with 285 additions and 11 deletions

View File

@ -93,19 +93,41 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
#endif
while (i < len) {
qword[0] = src[i >> 2];
qword[1] = src[(i >> 2) + 1];
qword[2] = src[(i >> 2) + 2];
qword[3] = src[(i >> 2) + 3];
uint32_t rem = (uint32_t)(len - i);
uint32_t w0, w1, w2, w3;
/* Program the unit word by word and never read past len: the
* flash programs whole 32-bit words, so a partial final word is
* padded with the erased value (0xFF) in its upper bytes, and
* words past the requested length are not programmed at all. */
w0 = src[i >> 2];
if (rem < 4)
w0 |= ~0u << (8 * rem);
*cr |= FLASH_CR_PG;
dst[i >> 2] = qword[0];
ISB();
dst[(i >> 2) + 1] = qword[1];
ISB();
dst[(i >> 2) + 2] = qword[2];
ISB();
dst[(i >> 2) + 3] = qword[3];
dst[i >> 2] = w0;
ISB();
if (rem > 4) {
w1 = src[(i >> 2) + 1];
if (rem < 8)
w1 |= ~0u << (8 * (rem - 4));
dst[(i >> 2) + 1] = w1;
ISB();
}
if (rem > 8) {
w2 = src[(i >> 2) + 2];
if (rem < 12)
w2 |= ~0u << (8 * (rem - 8));
dst[(i >> 2) + 2] = w2;
ISB();
}
if (rem > 12) {
w3 = src[(i >> 2) + 3];
if (rem < 16)
w3 |= ~0u << (8 * (rem - 12));
dst[(i >> 2) + 3] = w3;
ISB();
}
hal_flash_wait_complete(0);
if ((*sr & FLASH_SR_EOP) != 0)
*sr |= FLASH_SR_EOP;

View File

@ -112,6 +112,7 @@ TESTS+=unit-aurix-erased-fill
TESTS+=unit-aurix-erased-fill-invert
TESTS+=unit-t2080-fman-loader
TESTS+=unit-stm32l5-write
TESTS+=unit-stm32u5-write
TESTS+=unit-sdhci-uhs-recover
TESTS+=unit-sdhci-wait-busy
TESTS+=unit-ti-hercules-write
@ -940,6 +941,20 @@ stm32l5_write_extract.h: ../../hal/stm32l5.c
unit-stm32l5-write: unit-stm32l5-write.c stm32l5_write_extract.h
gcc -o $@ unit-stm32l5-write.c $(CFLAGS) $(LDFLAGS)
# unit-stm32u5-write is the 16-byte-unit twin of the STM32L5 test
# (F-9764): the real hal_flash_write() and its wait/clear helpers from
# hal/stm32u5.c, FLASH_NS_SR/CR on a host register file, destination
# flash at a 32-bit host address, canary after the source buffer.
# NB: gawk 5.x mis-lexes an action brace directly after a regex
# literal, so each pattern is followed by a space.
stm32u5_write_extract.h: ../../hal/stm32u5.c
awk '/^void RAMFUNCTION hal_flash_wait_complete/ {f=1} f {print} f && /^\}/ {exit}' $< > $@
awk '/^void RAMFUNCTION hal_flash_clear_errors/ {f=1} f {print} f && /^\}/ {exit}' $< >> $@
awk '/^int RAMFUNCTION hal_flash_write/ {f=1} f {print} f && /^\}/ {exit}' $< >> $@
unit-stm32u5-write: unit-stm32u5-write.c stm32u5_write_extract.h
gcc -o $@ unit-stm32u5-write.c $(CFLAGS) $(LDFLAGS)
# unit-sdhci-uhs-recover drives disk_read()'s UHS recovery path from the
# real src/sdhci.c (F-9735: any read error permanently switched the host
# to 1.8V signaling with no rollback). sdhci_host.c (generated below) is

View File

@ -0,0 +1,237 @@
/* unit-stm32u5-write.c
*
* Regression test for F-9764: hal_flash_write() in hal/stm32u5.c
* looped while (i < len) but each iteration unconditionally read all
* four 32-bit words of the 16-byte program unit (src[i>>2] through
* src[(i>>2)+3]) and programmed the full quadword regardless of the
* remaining length. A write whose length is not a multiple of 16
* therefore read up to 12 bytes past the caller's buffer (validated
* for exactly len by the NSC update path) and wrote those bytes to
* flash.
*
* The test extracts the real hal_flash_wait_complete(),
* hal_flash_clear_errors() and hal_flash_write() from hal/stm32u5.c
* (generated by the Makefile) and runs them with the FLASH_NS_SR/CR
* registers on a host register file and the destination flash at a
* 32-bit host address pre-filled with stale data. The source buffer
* is followed by a canary: pre-fix, a short write copies bytes past
* len into the destination flash.
*
* 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 <string.h>
#include <sys/mman.h>
/* Host stand-ins for the ARM primitives and the TZ build selection
* (non-secure path: FLASH_NS_CR / FLASH_NS_SR). */
#define RAMFUNCTION
#define ISB() do {} while (0)
#define TZ_SECURE() (0)
/* Host FLASH register file (offsets as in hal/stm32u5.h). */
static uint32_t g_flash_regs[0x40 / sizeof(uint32_t)];
#define FLASH_NS_BASE ((uintptr_t)g_flash_regs)
#define FLASH_NS_SR (*(volatile uint32_t *)(FLASH_NS_BASE + 0x20))
#define FLASH_NS_CR (*(volatile uint32_t *)(FLASH_NS_BASE + 0x28))
#define FLASH_SR_EOP (1 << 0)
#define FLASH_SR_OPERR (1 << 1)
#define FLASH_SR_PROGERR (1 << 3)
#define FLASH_SR_WRPERR (1 << 4)
#define FLASH_SR_PGAERR (1 << 5)
#define FLASH_SR_SIZERR (1 << 6)
#define FLASH_SR_PGSERR (1 << 7)
#define FLASH_SR_OPTWERR (1 << 13)
#define FLASH_SR_BSY (1 << 16)
#define FLASH_SR_WDW (1 << 17)
#define FLASH_CR_PG (1 << 0)
/* Destination flash: pre-filled with stale data (rewrite scenario).
* hal_flash_write() takes the address as uint32_t (32-bit MCU), so on
* the 64-bit host the flash must live at an address that fits in 32
* bits: map it at a fixed low location. */
#define FLASH_MEM_SZ 256
#define FLASH_MEM_ADDR 0x11000000UL
static uint8_t *g_flash_mem;
/* Source buffer followed by a canary: pre-fix, a short write reads
* bytes past len and lands them in the destination flash. The canary
* range avoids the data bytes (0x30..0x6F), the stale fill (0x12) and
* the erased-value padding (0xFF). */
#define DATA_SZ 64
#define CANARY_SZ 32
static uint8_t g_data[DATA_SZ + CANARY_SZ];
#define g_canary (g_data + DATA_SZ)
/* The real functions from hal/stm32u5.c (extracted by the Makefile). */
#include "stm32u5_write_extract.h"
static void setup(void)
{
int i;
memset(g_flash_regs, 0, sizeof(g_flash_regs));
for (i = 0; i < FLASH_MEM_SZ; i++)
g_flash_mem[i] = 0x12; /* stale */
for (i = 0; i < DATA_SZ; i++)
g_data[i] = (uint8_t)(0x30 + i);
for (i = 0; i < CANARY_SZ; i++)
g_canary[i] = (uint8_t)(0x70 + i);
}
static void teardown(void)
{
}
static int canary_in_flash(void)
{
int i;
for (i = 0; i < CANARY_SZ; i++)
if (memchr(g_flash_mem, g_canary[i], FLASH_MEM_SZ) != NULL)
return 1;
return 0;
}
/* A write of 60 bytes (not a multiple of 16): the requested bytes
* land, the partial final word is padded to the erased value, and
* nothing past len is read or written. */
START_TEST(test_write_60_no_overread)
{
int i;
ck_assert_int_eq(hal_flash_write((uint32_t)(uintptr_t)g_flash_mem,
g_data, 60), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 60), 0);
/* word 14 (bytes 56..59) is the last complete word; word 15
* (bytes 60..63) holds no requested byte and keeps its stale value */
for (i = 60; i < FLASH_MEM_SZ; i++)
ck_assert_uint_eq(g_flash_mem[i], 0x12);
ck_assert_int_eq(canary_in_flash(), 0);
}
END_TEST
/* A write of 58 bytes: the final word is partial (2 bytes); its upper
* bytes are padded with the erased value, nothing past len is read. */
START_TEST(test_write_58_partial_word_padded)
{
int i;
ck_assert_int_eq(hal_flash_write((uint32_t)(uintptr_t)g_flash_mem,
g_data, 58), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 58), 0);
/* word 14 (bytes 56..59): bytes 58,59 padded to the erased value */
ck_assert_uint_eq(g_flash_mem[58], 0xFF);
ck_assert_uint_eq(g_flash_mem[59], 0xFF);
for (i = 60; i < FLASH_MEM_SZ; i++)
ck_assert_uint_eq(g_flash_mem[i], 0x12);
ck_assert_int_eq(canary_in_flash(), 0);
}
END_TEST
/* A write of 18 bytes: the second unit's first word is partial (2 of
* 4 bytes); its upper bytes are padded, no further word is touched. */
START_TEST(test_write_18_second_word_padded)
{
int i;
ck_assert_int_eq(hal_flash_write((uint32_t)(uintptr_t)g_flash_mem,
g_data, 18), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 18), 0);
/* word 4 (bytes 16..19): bytes 18,19 padded to the erased value */
ck_assert_uint_eq(g_flash_mem[18], 0xFF);
ck_assert_uint_eq(g_flash_mem[19], 0xFF);
for (i = 20; i < FLASH_MEM_SZ; i++)
ck_assert_uint_eq(g_flash_mem[i], 0x12);
ck_assert_int_eq(canary_in_flash(), 0);
}
END_TEST
/* A write of 3 bytes: only the first word, padded in its upper three
* bytes; no other word is programmed. */
START_TEST(test_write_3_single_word_padded)
{
int i;
ck_assert_int_eq(hal_flash_write((uint32_t)(uintptr_t)g_flash_mem,
g_data, 3), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 3), 0);
ck_assert_uint_eq(g_flash_mem[3], 0xFF);
for (i = 4; i < FLASH_MEM_SZ; i++)
ck_assert_uint_eq(g_flash_mem[i], 0x12);
ck_assert_int_eq(canary_in_flash(), 0);
}
END_TEST
/* A write that is a multiple of 16 behaves exactly as before. */
START_TEST(test_write_64_full_units)
{
int i;
ck_assert_int_eq(hal_flash_write((uint32_t)(uintptr_t)g_flash_mem,
g_data, 64), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 64), 0);
for (i = 64; i < FLASH_MEM_SZ; i++)
ck_assert_uint_eq(g_flash_mem[i], 0x12);
}
END_TEST
Suite *stm32u5_write_suite(void)
{
Suite *s = suite_create("stm32u5-write");
TCase *tc = tcase_create("stm32u5-write");
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, test_write_60_no_overread);
tcase_add_test(tc, test_write_58_partial_word_padded);
tcase_add_test(tc, test_write_18_second_word_padded);
tcase_add_test(tc, test_write_3_single_word_padded);
tcase_add_test(tc, test_write_64_full_units);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
Suite *s = stm32u5_write_suite();
SRunner *sr = srunner_create(s);
g_flash_mem = mmap((void *)FLASH_MEM_ADDR, FLASH_MEM_SZ,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1, 0);
if (g_flash_mem == MAP_FAILED)
return 99;
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
munmap(g_flash_mem, FLASH_MEM_SZ);
return fails;
}