diff --git a/hal/stm32l5.c b/hal/stm32l5.c index f96fe277..7619dd20 100644 --- a/hal/stm32l5.c +++ b/hal/stm32l5.c @@ -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(); diff --git a/hal/stm32u5.c b/hal/stm32u5.c index 6a6fc82a..fe8e918e 100644 --- a/hal/stm32u5.c +++ b/hal/stm32u5.c @@ -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); diff --git a/tools/unit-tests/unit-stm32l5-write.c b/tools/unit-tests/unit-stm32l5-write.c index 9b541dc8..49ed621d 100644 --- a/tools/unit-tests/unit-stm32l5-write.c +++ b/tools/unit-tests/unit-stm32l5-write.c @@ -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); diff --git a/tools/unit-tests/unit-stm32u5-write.c b/tools/unit-tests/unit-stm32u5-write.c index da84e16b..fdbd29f9 100644 --- a/tools/unit-tests/unit-stm32u5-write.c +++ b/tools/unit-tests/unit-stm32u5-write.c @@ -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);