From 82af97b2849a5c79ba1679ccd6c2248f842d9a5e Mon Sep 17 00:00:00 2001 From: Dirk Petrautzki Date: Mon, 10 Aug 2026 15:04:29 +0200 Subject: [PATCH 1/2] reclaim: fix uninitialized status when LX_DIRECT_READ is used --- common/src/lx_nor_flash_block_reclaim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lx_nor_flash_block_reclaim.c b/common/src/lx_nor_flash_block_reclaim.c index e2951f1..a47d13b 100644 --- a/common/src/lx_nor_flash_block_reclaim.c +++ b/common/src/lx_nor_flash_block_reclaim.c @@ -453,7 +453,7 @@ UINT status; _lx_nor_flash_system_error(nor_flash, LX_SYSTEM_ALLOCATION_FAILED); /* Return the error. */ - return(status); + return(LX_SYSTEM_ALLOCATION_FAILED); } /* Decrement the number of mapped sectors. */ From 987a5f035e01fd5b1dc7709b4b463f6b11c0b983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 25 Sep 2026 16:36:52 -0400 Subject: [PATCH 2/2] Guarded NOR reclaim against null sector addresses Physical-sector allocation clears both output pointers on failure. Reclaim compared the null sector address with block pointers before reporting the error, which has undefined behavior in C99. Check the mapping output before both address range tests. Add a regression that fails the first allocation or its retry and verifies the returned error, diagnostic, and absence of writes or erases. GCC 14 CMake/Ninja: 4/4 tests passed in standalone buffered, standalone direct read, and ThreadX linked direct read builds. The coverage build passed its focused test; gcov recorded two allocation failure returns. Assisted-by: Codex (GPT-6) --- common/src/lx_nor_flash_block_reclaim.c | 8 +- test/cmake/regression/CMakeLists.txt | 6 + .../levelx_nor_reclaim_failure_test.c | 160 ++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 test/regression/levelx_nor_reclaim_failure_test.c diff --git a/common/src/lx_nor_flash_block_reclaim.c b/common/src/lx_nor_flash_block_reclaim.c index a47d13b..096be34 100644 --- a/common/src/lx_nor_flash_block_reclaim.c +++ b/common/src/lx_nor_flash_block_reclaim.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +/* Portions of this file were generated with AI assistance. */ + /**************************************************************************/ /**************************************************************************/ @@ -294,7 +296,8 @@ UINT status; _lx_nor_flash_physical_sector_allocate(nor_flash, logical_sector, &new_mapping_address, &new_sector_address); /* Check to see if the new sector is also in the erase block. */ - if ((new_sector_address >= block_word_ptr) && (new_sector_address < (block_word_ptr + nor_flash -> lx_nor_flash_words_per_block))) + if ((new_mapping_address != LX_NULL) && (new_sector_address >= block_word_ptr) && + (new_sector_address < (block_word_ptr + nor_flash -> lx_nor_flash_words_per_block))) { /* Yes, the new sector was found in the block to be erased. Simply move the search pointer @@ -311,7 +314,8 @@ UINT status; /* Check again for the new sector inside of the block to erase. This should be impossible, since we check previously if there are enough free sectors outside of this block needed to reclaim this block. */ - if ((new_sector_address >= block_word_ptr) && (new_sector_address < (block_word_ptr + LX_NOR_SECTOR_SIZE))) + if ((new_mapping_address != LX_NULL) && (new_sector_address >= block_word_ptr) && + (new_sector_address < (block_word_ptr + LX_NOR_SECTOR_SIZE))) { /* System error, a new sector is not available outside of the erase block. diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index f92c71a..2db15fd 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -18,3 +18,9 @@ foreach(test_case ${regression_test_cases} ${regression_test_cases_exfat}) target_compile_definitions(${test_name} PRIVATE BATCH_TEST) add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) endforeach() + +add_executable(levelx_nor_reclaim_failure_test + ${SOURCE_DIR}/levelx_nor_reclaim_failure_test.c) +target_link_libraries(levelx_nor_reclaim_failure_test PRIVATE azrtos::levelx) +add_test(${CMAKE_BUILD_TYPE}::levelx_nor_reclaim_failure_test + levelx_nor_reclaim_failure_test) diff --git a/test/regression/levelx_nor_reclaim_failure_test.c b/test/regression/levelx_nor_reclaim_failure_test.c new file mode 100644 index 0000000..035d95a --- /dev/null +++ b/test/regression/levelx_nor_reclaim_failure_test.c @@ -0,0 +1,160 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include +#include "lx_api.h" + +static LX_NOR_FLASH nor_flash; +static ULONG flash_words[256]; +static UINT allocation_calls; +static UINT invalidation_calls; +static UINT unexpected_driver_calls; +static UINT fail_on_retry; + + +#ifndef LX_STANDALONE_ENABLE +/* Provide the ThreadX application entry point required by the host library. */ +VOID tx_application_define(VOID *first_unused_memory) +{ + LX_PARAMETER_NOT_USED(first_unused_memory); +} +#endif + + +/* Select a block with one mapped sector for reclamation. */ +UINT _lx_nor_flash_next_block_to_erase_find(LX_NOR_FLASH *flash, ULONG *erase_block, + ULONG *erase_count, ULONG *mapped_sectors, + ULONG *obsolete_sectors) +{ + LX_PARAMETER_NOT_USED(flash); + *erase_block = 0; + *erase_count = 0; + *mapped_sectors = 1; + *obsolete_sectors = 0; + return(LX_SUCCESS); +} + + +/* Fail either the first allocation or the retry after selecting the erase block. */ +UINT _lx_nor_flash_physical_sector_allocate(LX_NOR_FLASH *flash, ULONG logical_sector, + ULONG **mapping_address, ULONG **sector_address) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(logical_sector); + allocation_calls++; + + if ((fail_on_retry != 0U) && (allocation_calls == 1U)) + { + *mapping_address = &flash_words[2]; + *sector_address = &flash_words[8]; + return(LX_SUCCESS); + } + + *mapping_address = LX_NULL; + *sector_address = LX_NULL; + return(LX_NO_SECTORS); +} + + +/* Count mapping-cache invalidations before allocation. */ +VOID _lx_nor_flash_sector_mapping_cache_invalidate(LX_NOR_FLASH *flash, ULONG logical_sector) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(logical_sector); + invalidation_calls++; +} + + +/* Supply mapped-list words when direct reading is disabled. */ +UINT _lx_nor_flash_driver_read(LX_NOR_FLASH *flash, ULONG *address, ULONG *destination, ULONG words) +{ + ULONG i; + + LX_PARAMETER_NOT_USED(flash); + for (i = 0; i < words; i++) + { + destination[i] = address[i]; + } + return(LX_SUCCESS); +} + + +/* Record writes that must not occur after allocation failure. */ +UINT _lx_nor_flash_driver_write(LX_NOR_FLASH *flash, ULONG *address, ULONG *source, ULONG words) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(address); + LX_PARAMETER_NOT_USED(source); + LX_PARAMETER_NOT_USED(words); + unexpected_driver_calls++; + return(LX_SUCCESS); +} + + +/* Record erases that must not occur after allocation failure. */ +UINT _lx_nor_flash_driver_block_erase(LX_NOR_FLASH *flash, ULONG block, ULONG erase_count) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(block); + LX_PARAMETER_NOT_USED(erase_count); + unexpected_driver_calls++; + return(LX_SUCCESS); +} + + +/* Check both allocation-failure points in block reclaim. */ +static UINT test_reclaim_allocation_failure(UINT retry) +{ + UINT status; + + (void) memset(&nor_flash, 0, sizeof(nor_flash)); + (void) memset(flash_words, 0, sizeof(flash_words)); + allocation_calls = 0; + invalidation_calls = 0; + unexpected_driver_calls = 0; + fail_on_retry = retry; + + nor_flash.lx_nor_flash_base_address = flash_words; + nor_flash.lx_nor_flash_words_per_block = 128; + nor_flash.lx_nor_flash_total_blocks = 2; + nor_flash.lx_nor_flash_physical_sectors_per_block = 2; + nor_flash.lx_nor_flash_free_physical_sectors = 2; + nor_flash.lx_nor_flash_block_physical_sector_mapping_offset = 1; + flash_words[1] = LX_NOR_PHYSICAL_SECTOR_VALID | 7U; + + status = _lx_nor_flash_block_reclaim(&nor_flash); + if ((status != LX_SYSTEM_ALLOCATION_FAILED) || + (nor_flash.lx_nor_flash_diagnostic_system_error != LX_SYSTEM_ALLOCATION_FAILED) || + (nor_flash.lx_nor_flash_diagnostic_system_errors != 1U) || + (allocation_calls != (retry + 1U)) || (invalidation_calls != 1U) || + (unexpected_driver_calls != 0U)) + { + return(LX_ERROR); + } + return(LX_SUCCESS); +} + + +/* Run failure checks for the first allocation and its retry. */ +int main(void) +{ + if ((test_reclaim_allocation_failure(0U) != LX_SUCCESS) || + (test_reclaim_allocation_failure(1U) != LX_SUCCESS)) + { + return(1); + } + + return(0); +}