mirror of https://github.com/wolfSSL/wolfBoot.git
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
parent
51d147ebb6
commit
263d3d5fbf
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue