diff --git a/common/src/lx_nor_flash_block_reclaim.c b/common/src/lx_nor_flash_block_reclaim.c index e2951f1..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. @@ -453,7 +457,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. */ diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index 9ac1b61..10bf3d8 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -19,3 +19,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); +}