mirror of https://github.com/wolfSSL/wolfBoot.git
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
parent
685888a8ef
commit
78ba84cc23
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue