stm32l5/u5: program the whole flash unit in one PG window

The partial-tail handling skipped the words that hold no requested
byte. Neither part can do that: U5 only starts the 128-bit program on
the fourth word (a partial quad-word leaves FLASH_SR_WDW set and
hal_flash_wait_complete() never returns), and L5 has no 32-bit program
mode, so splitting the double word into two PG cycles programs
nothing and faults the second on alignment.

Build the full unit first, taking bytes outside [i, len) from the
current flash content as hal/stm32h5.c does, then store every word.
That drops the over-read F-9763/F-9764 targeted without ever issuing
a partial unit. L5 TZ claim widened to the 8-byte boundary to match.
pull/862/head
Daniele Lacamera 2026-08-18 08:41:26 +02:00
parent 685888a8ef
commit 78ba84cc23
4 changed files with 84 additions and 86 deletions

View File

@ -77,59 +77,53 @@ void RAMFUNCTION hal_flash_clear_errors(uint8_t bank)
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
int i = 0;
uint32_t *src, *dst;
uint32_t *dst;
uint32_t dword[2];
uint8_t *dword_bytes = (uint8_t *)dword;
volatile uint32_t *sr, *cr;
cr = &FLASH_CR;
sr = &FLASH_SR;
hal_flash_clear_errors(0);
src = (uint32_t *)data;
dst = (uint32_t *)address;
#if TZ_SECURE()
if (address >= FLASH_BANK2_BASE)
/* Claim the flash span the program writes actually touch: the
* last partial word is programmed whole, up to the 4-byte
* boundary past len. */
hal_tz_claim_nonsecure_area(address, (len + 3) & ~3);
/* Claim the flash span the program touches: the last partial
* double word is read back and programmed whole, up to the
* 8-byte boundary past len. */
hal_tz_claim_nonsecure_area(address, (len + 7) & ~7);
/* Convert into secure address space */
dst = (uint32_t *)((address & (~FLASHMEM_ADDRESS_SPACE)) | FLASH_SECURE_MMAP_BASE);
#endif
while (i < len) {
uint32_t rem = (uint32_t)(len - i);
uint32_t w0, w1;
int j;
/* 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. */
w0 = src[i >> 2];
if (rem < 4)
w0 |= ~0u << (8 * rem);
/* Build the whole 64-bit unit before opening the PG window.
* The flash has no 32-bit program mode: both words of the
* double word must be stored inside one PG window or the
* operation never starts. Bytes outside [i, len) are read back
* from flash and written unchanged, so nothing past the
* requested length is modified and the source is never read
* past len (same read-modify-write shape as hal/stm32h5.c). */
for (j = 0; j < 8; j++) {
if (i + j < len)
dword_bytes[j] = data[i + j];
else
dword_bytes[j] = ((const uint8_t *)dst)[i + j];
}
*cr |= FLASH_CR_PG;
dst[i >> 2] = w0;
dst[i >> 2] = dword[0];
ISB();
dst[(i >> 2) + 1] = dword[1];
hal_flash_wait_complete(0);
if ((*sr & FLASH_SR_EOP) != 0)
*sr |= FLASH_SR_EOP;
*cr &= ~FLASH_CR_PG;
if (rem > 4) {
w1 = src[(i >> 2) + 1];
if (rem < 8)
w1 |= ~0u << (8 * (rem - 4));
*cr |= FLASH_CR_PG;
dst[(i >> 2) + 1] = w1;
ISB();
hal_flash_wait_complete(0);
if ((*sr & FLASH_SR_EOP) != 0)
*sr |= FLASH_SR_EOP;
*cr &= ~FLASH_CR_PG;
}
i+=8;
i += 8;
}
#if TZ_SECURE()
hal_tz_release_nonsecure_area();

View File

@ -65,12 +65,12 @@ void RAMFUNCTION hal_flash_clear_errors(uint8_t bank)
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
int i = 0;
uint32_t *src, *dst;
uint32_t *dst;
uint32_t qword[4];
uint8_t *qword_bytes = (uint8_t *)qword;
volatile uint32_t *sr, *cr;
hal_flash_clear_errors(0);
src = (uint32_t*)data;
dst = (uint32_t*)address;
#if (TZ_SECURE())
@ -93,39 +93,26 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
#endif
while (i < len) {
uint32_t rem = (uint32_t)(len - i);
uint32_t w0, w1, w2, w3;
int j;
/* 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);
/* Build the whole 128-bit unit before opening the PG window.
* The controller only starts the program once all four words
* have been stored -- a partial quad-word leaves FLASH_SR_WDW
* set and hal_flash_wait_complete() never returns. Bytes
* outside [i, len) are read back from flash and written
* unchanged, so nothing past the requested length is modified
* and the source is never read past len (same read-modify-write
* shape as hal/stm32h5.c). */
for (j = 0; j < 16; j++) {
if (i + j < len)
qword_bytes[j] = data[i + j];
else
qword_bytes[j] = ((const uint8_t *)dst)[i + j];
}
*cr |= FLASH_CR_PG;
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;
for (j = 0; j < 4; j++) {
dst[(i >> 2) + j] = qword[j];
ISB();
}
hal_flash_wait_complete(0);

View File

@ -14,8 +14,13 @@
* registers on a host register file and the destination flash at a
* host memory address pre-filled with stale data. The source buffer
* is followed by a canary: pre-fix, a short write copies the canary
* into the destination flash; post-fix the partial final word is
* padded with the erased value (0xFF) and nothing past len is read.
* into the destination flash; post-fix the partial final unit is
* read-modify-written -- bytes outside [i, len) come from the current
* flash content and are stored back unchanged -- so nothing past len
* is read or changed. Both words are always stored inside one PG
* window, because the flash has no 32-bit program mode; that is a
* hardware sequencing property this host model cannot observe, so it
* is asserted by construction in hal/stm32l5.c, not here.
*
* Copyright (C) 2026 wolfSSL Inc.
*
@ -128,8 +133,9 @@ START_TEST(test_write_60_no_overread)
}
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. */
/* A write of 58 bytes: the final unit is partial (bytes 58,59 are
* outside the request); they are read back from flash and rewritten
* unchanged, and nothing past len is read. */
START_TEST(test_write_58_partial_word_padded)
{
int i;
@ -138,16 +144,18 @@ START_TEST(test_write_58_partial_word_padded)
g_data, 58), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 58), 0);
/* word 14 (bytes 56..59): 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);
/* word 14 (bytes 56..59): 58,59 keep their flash content */
ck_assert_uint_eq(g_flash_mem[58], 0x12);
ck_assert_uint_eq(g_flash_mem[59], 0x12);
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 3 bytes: only one word, padded in its upper three bytes. */
/* A write of 3 bytes: the whole 8-byte unit is programmed, but only
* bytes 0..2 take the requested value; the rest is rewritten with
* what flash already held. */
START_TEST(test_write_3_single_word_padded)
{
int i;
@ -156,9 +164,8 @@ START_TEST(test_write_3_single_word_padded)
g_data, 3), 0);
ck_assert_int_eq(memcmp(g_flash_mem, g_data, 3), 0);
/* word 0 (bytes 0..3) is programmed with byte 3 padded; word 1 is
* never programmed at all and keeps its stale value */
ck_assert_uint_eq(g_flash_mem[3], 0xFF);
/* byte 3 and the whole second word are rewritten unchanged */
ck_assert_uint_eq(g_flash_mem[3], 0x12);
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);

View File

@ -9,6 +9,14 @@
* for exactly len by the NSC update path) and wrote those bytes to
* flash.
*
* The fix read-modify-writes the whole 128-bit unit: bytes outside
* [i, len) come from the current flash content and are stored back
* unchanged, so the source is never read past len and no flash byte
* past len changes value. All four words are always stored, because
* the controller only starts the program on the fourth -- that part
* is a hardware sequencing property this host model cannot observe,
* so it is asserted by construction in hal/stm32u5.c, not here.
*
* 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
@ -130,8 +138,9 @@ START_TEST(test_write_60_no_overread)
}
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. */
/* A write of 58 bytes: the final unit is partial (bytes 58,59 of the
* last word are outside the request); they are read back from flash
* and rewritten unchanged, and nothing past len is read. */
START_TEST(test_write_58_partial_word_padded)
{
int i;
@ -140,17 +149,17 @@ START_TEST(test_write_58_partial_word_padded)
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);
/* word 14 (bytes 56..59): bytes 58,59 keep their flash content */
ck_assert_uint_eq(g_flash_mem[58], 0x12);
ck_assert_uint_eq(g_flash_mem[59], 0x12);
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. */
/* A write of 18 bytes: the second unit holds only 2 requested bytes;
* the rest of that unit is read back and rewritten unchanged. */
START_TEST(test_write_18_second_word_padded)
{
int i;
@ -159,17 +168,18 @@ START_TEST(test_write_18_second_word_padded)
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);
/* word 4 (bytes 16..19): bytes 18,19 keep their flash content */
ck_assert_uint_eq(g_flash_mem[18], 0x12);
ck_assert_uint_eq(g_flash_mem[19], 0x12);
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. */
/* A write of 3 bytes: the whole first unit is programmed, but only
* bytes 0..2 take the requested value; the rest is rewritten with
* what flash already held. */
START_TEST(test_write_3_single_word_padded)
{
int i;
@ -178,7 +188,7 @@ START_TEST(test_write_3_single_word_padded)
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);
ck_assert_uint_eq(g_flash_mem[3], 0x12);
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);