Address Fenrir PR review: make program-window observable in l5/u5 write tests

The new unaligned tests passed against the pre-fix HAL (identical
final bytes), so the alignment fix had no regression coverage. Mock
hal_flash_wait_complete now diffs the flash per program window and
asserts the changed bytes fit in one aligned unit; the 20-byte
unaligned test goes red on the pre-fix HAL (l5: bytes 4-11 across two
8-byte units, u5: bytes 4-19 across two 16-byte units).
pull/883/head
Daniele Lacamera 2026-09-07 19:19:22 +02:00
parent fc688f18c1
commit 609294789f
3 changed files with 85 additions and 18 deletions

View File

@ -1440,20 +1440,23 @@ unit-ecc-raw-der: unit-ecc-raw-der.c
# destination flash is a pre-filled host array, so the over-read is
# observable through a canary after the source buffer.
stm32l5_write_extract.h: ../../hal/stm32l5.c
sed -n '/^void RAMFUNCTION hal_flash_wait_complete/,/^}/p' $< > $@
sed -n '/^void RAMFUNCTION hal_flash_clear_errors/,/^}/p' $< >> $@
# hal_flash_wait_complete is mocked in the test (program-window
# check), so only the real clear_errors + write are extracted.
sed -n '/^void RAMFUNCTION hal_flash_clear_errors/,/^}/p' $< > $@
sed -n '/^int RAMFUNCTION hal_flash_write/,/^}/p' $< >> $@
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:
# 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.
# the real hal_flash_write() and clear_errors from hal/stm32u5.c
# (hal_flash_wait_complete is mocked in the test for the
# program-window check), FLASH_NS_SR/CR on a host register file,
# destination flash at a 32-bit host address, canary after the source.
stm32u5_write_extract.h: ../../hal/stm32u5.c
sed -n '/^void RAMFUNCTION hal_flash_wait_complete/,/^}/p' $< > $@
sed -n '/^void RAMFUNCTION hal_flash_clear_errors/,/^}/p' $< >> $@
# hal_flash_wait_complete is mocked in the test (program-window
# check), so only the real clear_errors + write are extracted.
sed -n '/^void RAMFUNCTION hal_flash_clear_errors/,/^}/p' $< > $@
sed -n '/^int RAMFUNCTION hal_flash_write/,/^}/p' $< >> $@
unit-stm32u5-write: unit-stm32u5-write.c stm32u5_write_extract.h

View File

@ -12,12 +12,14 @@
* The fix read-modify-writes the whole unit: the destination is
* aligned down to the unit, the bytes outside the requested span come
* from flash and go back unchanged, and both words are stored through
* the aligned pointer. That is asserted here for unaligned starts
* (the host data model cannot observe the program-unit split itself,
* only where the bytes end up). Both words are always stored in one
* PG window -- the flash has no 32-bit program mode -- which this
* host model cannot observe, so that is asserted by construction
* in the HAL, not here.
* the aligned pointer. That is asserted here for unaligned starts.
* The program-window invariant is enforced by the mock
* hal_flash_wait_complete below: the real one only spins on
* FLASH_SR_BSY (never set on the host register file), so the mock
* diffs the flash against the previous window and asserts that the
* changed bytes fit in one aligned 8-byte unit. A pre-fix HAL split
* the two word stores across two units for an unaligned start, and
* the 20-byte unaligned test goes red on it.
*
* Same harness as the STM32U5 twin: extracted functions, registers on
* a host file, stale destination flash, canary after the source.
@ -75,6 +77,10 @@ static uint32_t g_flash_regs[0x40 / sizeof(uint32_t)];
#define FLASH_MEM_ADDR 0x10000000UL
static uint8_t *g_flash_mem;
/* Snapshot of the flash at the last program-window boundary; the
* mock hal_flash_wait_complete() diffs against it. */
static uint8_t g_flash_prev[FLASH_MEM_SZ];
/* Source buffer followed by a canary: a pre-fix short write reads the
* canary and lands it in the destination flash. */
#define DATA_SZ 64
@ -82,6 +88,31 @@ static uint8_t *g_flash_mem;
static uint8_t g_data[DATA_SZ + CANARY_SZ];
#define g_canary (g_data + DATA_SZ)
/* Mock hal_flash_wait_complete(): the real one (hal/stm32l5.c) only
* spins on FLASH_SR_BSY, which the host register file never sets. This
* one adds the program-window check the host model cannot see any
* other way: the bytes changed since the previous window must fit
* within one aligned 8-byte program unit. The pre-fix HAL issued its
* two word stores relative to the caller address, so an unaligned
* start split them across two units and this assertion goes red. */
static void hal_flash_wait_complete(uint8_t bank)
{
int i;
int first = -1;
int last = -1;
for (i = 0; i < FLASH_MEM_SZ; i++) {
if (g_flash_mem[i] != g_flash_prev[i]) {
if (first < 0)
first = i;
last = i;
}
}
if (first >= 0)
ck_assert_int_le(last, (first & ~0x07) + 7);
memcpy(g_flash_prev, g_flash_mem, FLASH_MEM_SZ);
}
/* The real functions from hal/stm32l5.c (extracted by the Makefile). */
#include "stm32l5_write_extract.h"
@ -92,6 +123,7 @@ static void setup(void)
memset(g_flash_regs, 0, sizeof(g_flash_regs));
for (i = 0; i < FLASH_MEM_SZ; i++)
g_flash_mem[i] = 0x12; /* stale */
memcpy(g_flash_prev, g_flash_mem, FLASH_MEM_SZ);
for (i = 0; i < DATA_SZ; i++)
g_data[i] = (uint8_t)(0x30 + i);
/* 0x70..0x8F: distinct from the data bytes (0x30..0x6F), the stale

View File

@ -13,11 +13,13 @@
* aligned down to the unit, the bytes outside the requested span come
* from flash and go back unchanged, and all four words are stored
* through the aligned pointer. That is asserted here for unaligned
* starts (the host data model cannot observe the program-unit split
* itself, only where the bytes end up). All four words are always
* stored in one program -- the controller only starts it on the
* fourth word -- which this host model cannot observe, so that is
* asserted by construction in the HAL, not here.
* starts. The program-window invariant is enforced by the mock
* hal_flash_wait_complete below: the real one only spins on
* FLASH_SR_BSY (never set on the host register file), so the mock
* diffs the flash against the previous window and asserts that the
* changed bytes fit in one aligned 16-byte unit. A pre-fix HAL split
* the four word stores across two units for an unaligned start, and
* the 20-byte unaligned test goes red on it.
*
* Same harness as the STM32L5 twin: extracted functions, registers on
* a host file, stale destination flash, canary after the source.
@ -76,6 +78,10 @@ static uint32_t g_flash_regs[0x40 / sizeof(uint32_t)];
#define FLASH_MEM_ADDR 0x11000000UL
static uint8_t *g_flash_mem;
/* Snapshot of the flash at the last program-window boundary; the
* mock hal_flash_wait_complete() diffs against it. */
static uint8_t g_flash_prev[FLASH_MEM_SZ];
/* 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
@ -85,6 +91,31 @@ static uint8_t *g_flash_mem;
static uint8_t g_data[DATA_SZ + CANARY_SZ];
#define g_canary (g_data + DATA_SZ)
/* Mock hal_flash_wait_complete(): the real one (hal/stm32u5.c) only
* spins on FLASH_SR_BSY, which the host register file never sets. This
* one adds the program-window check the host model cannot see any
* other way: the bytes changed since the previous window must fit
* within one aligned 16-byte program unit. The pre-fix HAL issued its
* four word stores relative to the caller address, so an unaligned
* start split them across two units and this assertion goes red. */
static void hal_flash_wait_complete(uint8_t bank)
{
int i;
int first = -1;
int last = -1;
for (i = 0; i < FLASH_MEM_SZ; i++) {
if (g_flash_mem[i] != g_flash_prev[i]) {
if (first < 0)
first = i;
last = i;
}
}
if (first >= 0)
ck_assert_int_le(last, (first & ~0x0F) + 15);
memcpy(g_flash_prev, g_flash_mem, FLASH_MEM_SZ);
}
/* The real functions from hal/stm32u5.c (extracted by the Makefile). */
#include "stm32u5_write_extract.h"
@ -95,6 +126,7 @@ static void setup(void)
memset(g_flash_regs, 0, sizeof(g_flash_regs));
for (i = 0; i < FLASH_MEM_SZ; i++)
g_flash_mem[i] = 0x12; /* stale */
memcpy(g_flash_prev, g_flash_mem, FLASH_MEM_SZ);
for (i = 0; i < DATA_SZ; i++)
g_data[i] = (uint8_t)(0x30 + i);
for (i = 0; i < CANARY_SZ; i++)