diff --git a/hal/imx_rt.c b/hal/imx_rt.c index dc5a62fd..15b6e1bb 100644 --- a/hal/imx_rt.c +++ b/hal/imx_rt.c @@ -26,6 +26,7 @@ #include #include "image.h" #include "printf.h" +#include "imx_rt.h" #include "fsl_cache.h" #include "fsl_common.h" #include "fsl_iomuxc.h" @@ -966,8 +967,8 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) * (see definition of DCACHE_InvalidateByRange). * To ensure all data is included we align the address downwards, and the length upwards. */ - uint32_t aligned_address = address - (address % 32); - uint32_t aligned_len = len + (32 - (len % 32)); + uint32_t aligned_address, aligned_len; + hal_flash_cache_align_range(address, (uint32_t)len, &aligned_address, &aligned_len); DCACHE_InvalidateByRange(aligned_address, aligned_len); /* Re-enable interrupts */ asm volatile("cpsie i"); @@ -1010,8 +1011,8 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) * (see definition of DCACHE_InvalidateByRange). * To ensure all data is included we align the address downwards, and the length upwards. */ - uint32_t aligned_address = address - (address % 32); - uint32_t aligned_len = len + (32 - (len % 32)); + uint32_t aligned_address, aligned_len; + hal_flash_cache_align_range(address, (uint32_t)len, &aligned_address, &aligned_len); DCACHE_InvalidateByRange(aligned_address, aligned_len); /* Re-enable interrupts */ asm volatile("cpsie i"); diff --git a/hal/imx_rt.h b/hal/imx_rt.h new file mode 100644 index 00000000..a1da88a6 --- /dev/null +++ b/hal/imx_rt.h @@ -0,0 +1,46 @@ +/* imx_rt.h + * + * Support routines for the i.MX RT HAL, kept free of NXP MCUXpresso SDK + * dependencies so they can be exercised directly in the host unit tests. + * + * 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 + */ +#ifndef IMX_RT_H +#define IMX_RT_H + +#include + +/* Flash is memory mapped, so after a program/erase, the affected range must + * be invalidated in the data cache to ensure coherency. The cache line size + * is 32 bytes, so both the address and length passed to + * DCACHE_InvalidateByRange() must be 32-byte aligned: the address is rounded + * down, and the length is rounded up by the same down-alignment offset plus + * "len", so that the invalidated range always fully covers + * [address, address + len). */ +static inline void hal_flash_cache_align_range(uint32_t address, uint32_t len, + uint32_t *aligned_address, uint32_t *aligned_len) +{ + uint32_t start = address - (address % 32); + uint32_t unaligned_len = len + (address - start); + + *aligned_address = start; + *aligned_len = unaligned_len + ((32 - (unaligned_len % 32)) % 32); +} + +#endif /* IMX_RT_H */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 39f01ef3..6db5f5c2 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -83,6 +83,7 @@ TESTS+=unit-elf-bss-guard TESTS+=unit-arm-tee-psa-ipc TESTS+=unit-va416x0-fram TESTS+=unit-flash-write-mcxa +TESTS+=unit-imx-rt-cache-align # linux_loader.c is x86 32-bit only, so its unit tests need a working 32-bit # (multilib) toolchain. Probe whether "gcc -m32" can link, and only add the @@ -590,6 +591,12 @@ unit-ata-security-passphrase-zeroize: ../../include/target.h unit-ata-security-p unit-flash-write-mcxa: unit-flash-write-mcxa.c ../../hal/mcxa.c gcc -o $@ unit-flash-write-mcxa.c -Imcxa_fsl_stub $(CFLAGS) $(LDFLAGS) +# unit-imx-rt-cache-align only pulls in hal/imx_rt.h, which is dependency-free +# by design, avoiding the (not vendored) NXP MCUXpresso SDK headers that +# hal/imx_rt.c itself requires. +unit-imx-rt-cache-align: unit-imx-rt-cache-align.c ../../hal/imx_rt.h + gcc -o $@ unit-imx-rt-cache-align.c $(CFLAGS) $(LDFLAGS) + unit-elf-bss-guard: unit-elf-bss-guard.c gcc -o $@ $< -I../../include -DWOLFBOOT_ELF -DARCH_FLASH_OFFSET=0 \ -DWOLFBOOT_NO_PRINTF -g $(LDFLAGS) diff --git a/tools/unit-tests/unit-imx-rt-cache-align.c b/tools/unit-tests/unit-imx-rt-cache-align.c new file mode 100644 index 00000000..3a6311a6 --- /dev/null +++ b/tools/unit-tests/unit-imx-rt-cache-align.c @@ -0,0 +1,133 @@ +/* unit-imx-rt-cache-align.c + * + * Regression test for F-6399: in hal/imx_rt.c, hal_flash_write() and + * hal_flash_erase() invalidate the data cache over a range computed by + * rounding "address" down and "len" up to 32-byte cache-line boundaries. + * The length was rounded up from "len" alone, omitting the down-alignment + * offset (address - aligned_address). Whenever + * (address % 32) + (len % 32) > 32, the resulting range's end fell short of + * the real end of the write/erase (address + len), leaving the last cache + * line stale after the flash operation. + * + * hal_flash_cache_align_range() (hal/imx_rt.h) is the exact routine used by + * both hal_flash_write() and hal_flash_erase() to compute that range; this + * test drives it directly and checks that [aligned_address, aligned_address + * + aligned_len) always fully covers [address, address + len). + * + * 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 +#include + +#include "../../hal/imx_rt.h" + +static void check_covers(uint32_t address, uint32_t len) +{ + uint32_t aligned_address, aligned_len; + + hal_flash_cache_align_range(address, len, &aligned_address, &aligned_len); + + /* Both must be 32-byte aligned, as required by DCACHE_InvalidateByRange. */ + ck_assert_uint_eq(aligned_address % 32, 0); + ck_assert_uint_eq(aligned_len % 32, 0); + + /* The invalidated range must start at or before the write/erase, and end + * at or after it: [aligned_address, aligned_address + aligned_len) + * must contain [address, address + len). */ + ck_assert(aligned_address <= address); + ck_assert(aligned_address + aligned_len >= address + len); +} + +/* address % 32 = 4, len % 32 = 29: 4 + 29 = 33 > 32, the case the buggy + * "len + (32 - (len % 32))" formula under-covered by 8 bytes. */ +START_TEST(test_straddles_cache_line) +{ + check_covers(0x60000000u + 4u, 29u); +} +END_TEST + +/* Same straddling condition, at the boundary: address % 32 = 31, + * len % 32 = 1 (31 + 1 = 32, not > 32) must still be covered exactly. */ +START_TEST(test_boundary_not_straddling) +{ + check_covers(0x60000000u + 31u, 1u); +} +END_TEST + +/* len an exact multiple of 32: must not require a spurious extra line. */ +START_TEST(test_len_multiple_of_32) +{ + check_covers(0x60000000u, 64u); + check_covers(0x60000000u + 32u, 32u); +} +END_TEST + +/* Sector-aligned erase (address % 32 == 0): always covered regardless of + * len % 32. */ +START_TEST(test_aligned_address) +{ + check_covers(0x60000000u, 1u); + check_covers(0x60000000u, 4096u); + check_covers(0x60000000u, 33u); +} +END_TEST + +/* Sweep every (address % 32, len) combination for a representative address + * base and a range of lengths, to catch any other under-coverage case. */ +START_TEST(test_sweep) +{ + uint32_t off; + uint32_t len; + + for (off = 0; off < 32; off++) { + for (len = 1; len <= 96; len++) { + check_covers(0x60000000u + off, len); + } + } +} +END_TEST + +Suite *imx_rt_cache_align_suite(void) +{ + Suite *s = suite_create("imx-rt-cache-align"); + TCase *tc = tcase_create("imx-rt-cache-align"); + + tcase_add_test(tc, test_straddles_cache_line); + tcase_add_test(tc, test_boundary_not_straddling); + tcase_add_test(tc, test_len_multiple_of_32); + tcase_add_test(tc, test_aligned_address); + tcase_add_test(tc, test_sweep); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = imx_rt_cache_align_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +}