From aebe6c23dbef07d37c243fa71397b30de7f1d2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 30 Sep 2026 16:07:39 -0400 Subject: [PATCH] Rejected NAND geometries too small for metadata rebuilds Fixes #81 A NAND geometry with too few pages can format successfully, then enter another metadata allocation while rebuilding a snapshot later. Format and open now reject geometries unless the snapshot, a block-link page, and one later update fit in one block. The check returns LX_NOT_SUPPORTED before LevelX flash I/O. The host test uses printf and exit to report to CTest; these are test-only exceptions to MISRA C:2004 Rules 20.9 and 20.11 and MISRA C:2012/2023 Rules 21.6 and 21.8. GCC 14 CMake/Ninja: 4/4 focused configurations and the existing NAND regression passed. New validation lines and branches: 14/14 each. No hardware test. Assisted-by: Codex (GPT-6-Sol) --- common/src/lx_nand_flash_memory_initialize.c | 34 +++ test/cmake/regression/CMakeLists.txt | 6 + .../levelx_nand_metadata_geometry_test.c | 277 ++++++++++++++++++ 3 files changed, 317 insertions(+) create mode 100644 test/regression/levelx_nand_metadata_geometry_test.c diff --git a/common/src/lx_nand_flash_memory_initialize.c b/common/src/lx_nand_flash_memory_initialize.c index af8f444..3ad4da3 100644 --- a/common/src/lx_nand_flash_memory_initialize.c +++ b/common/src/lx_nand_flash_memory_initialize.c @@ -75,8 +75,42 @@ UINT _lx_nand_flash_memory_initialize(LX_NAND_FLASH *nand_flash, ULONG* memory UINT memory_offset; UINT buffer_size; +ULONG page_size; +ULONG erase_count_bytes; +ULONG block_table_bytes; +ULONG erase_count_pages; +ULONG block_table_pages; +ULONG snapshot_pages; + /* Reserve room for a metadata snapshot, its link, and one later update. */ + page_size = nand_flash -> lx_nand_flash_bytes_per_page; + if ((page_size == 0u) || (nand_flash -> lx_nand_flash_pages_per_block < 2u)) + { + return(LX_NOT_SUPPORTED); + } + + erase_count_bytes = nand_flash -> lx_nand_flash_total_blocks * sizeof(UCHAR); + block_table_bytes = nand_flash -> lx_nand_flash_total_blocks * sizeof(USHORT); + + erase_count_pages = erase_count_bytes / page_size; + if (((erase_count_bytes % page_size) != 0u) || (erase_count_pages == 0u)) + { + erase_count_pages++; + } + + block_table_pages = block_table_bytes / page_size; + if (((block_table_bytes % page_size) != 0u) || (block_table_pages == 0u)) + { + block_table_pages++; + } + + snapshot_pages = 1u + erase_count_pages + (2u * block_table_pages); + if (snapshot_pages > (nand_flash -> lx_nand_flash_pages_per_block - 2u)) + { + return(LX_NOT_SUPPORTED); + } + /* Clear the memory buffer. */ LX_MEMSET(memory_ptr, 0, memory_size); diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index 10bf3d8..632b197 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -25,3 +25,9 @@ add_executable(levelx_nor_reclaim_failure_test 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) + +add_executable(levelx_nand_metadata_geometry_test + ${SOURCE_DIR}/levelx_nand_metadata_geometry_test.c) +target_link_libraries(levelx_nand_metadata_geometry_test PRIVATE azrtos::levelx) +add_test(${CMAKE_BUILD_TYPE}::levelx_nand_metadata_geometry_test + levelx_nand_metadata_geometry_test) diff --git a/test/regression/levelx_nand_metadata_geometry_test.c b/test/regression/levelx_nand_metadata_geometry_test.c new file mode 100644 index 0000000..e454ab4 --- /dev/null +++ b/test/regression/levelx_nand_metadata_geometry_test.c @@ -0,0 +1,277 @@ +/*************************************************************************** + * 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-Sol). + * 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 +#include "lx_api.h" + +#define TEST_MEMORY_WORDS 10000u +#define TEST_REBUILD_WRITES 40u + +static LX_NAND_FLASH test_flash; +static ULONG test_memory[TEST_MEMORY_WORDS]; +static ULONG test_blocks; +static ULONG test_pages_per_block; +static ULONG test_bytes_per_page; +static UINT test_reject_io; +static UINT unexpected_io; +static UINT invalid_page_writes; +#ifndef LX_STANDALONE_ENABLE +static TX_THREAD test_thread; +static UCHAR test_thread_stack[4096]; +#endif + +UINT _lx_nand_flash_simulator_initialize(LX_NAND_FLASH *nand_flash); +UINT _lx_nand_flash_simulator_erase_all(VOID); +static INT run_cases(VOID); + +#ifndef LX_STANDALONE_ENABLE +/* Run the NAND checks from a ThreadX thread. */ +static VOID test_thread_entry(ULONG input) +{ + LX_PARAMETER_NOT_USED(input); + exit(run_cases()); +} + +/* 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); + if (tx_thread_create(&test_thread, "NAND geometry", test_thread_entry, 0u, + test_thread_stack, sizeof(test_thread_stack), + 1u, 1u, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) + { + exit(1); + } +} +#endif + +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT (*simulator_pages_write)(LX_NAND_FLASH *, ULONG, ULONG, UCHAR *, UCHAR *, ULONG); + +/* Count any metadata page write beyond the configured virtual block. */ +static UINT guarded_pages_write(LX_NAND_FLASH *nand_flash, ULONG block, ULONG page, + UCHAR *main_buffer, UCHAR *spare_buffer, ULONG pages) +{ + if ((page >= test_pages_per_block) || (pages > (test_pages_per_block - page))) + { + invalid_page_writes++; + return(LX_ERROR); + } + + return(simulator_pages_write(nand_flash, block, page, main_buffer, spare_buffer, pages)); +} + +/* Detect flash access before an invalid geometry is rejected. */ +static UINT unexpected_block_status_get(LX_NAND_FLASH *nand_flash, ULONG block, UCHAR *status) +{ + LX_PARAMETER_NOT_USED(nand_flash); + LX_PARAMETER_NOT_USED(block); + LX_PARAMETER_NOT_USED(status); + unexpected_io++; + return(LX_ERROR); +} +#else +static UINT (*simulator_pages_write)(ULONG, ULONG, UCHAR *, UCHAR *, ULONG); + +/* Count any metadata page write beyond the configured virtual block. */ +static UINT guarded_pages_write(ULONG block, ULONG page, UCHAR *main_buffer, + UCHAR *spare_buffer, ULONG pages) +{ + if ((page >= test_pages_per_block) || (pages > (test_pages_per_block - page))) + { + invalid_page_writes++; + return(LX_ERROR); + } + + return(simulator_pages_write(block, page, main_buffer, spare_buffer, pages)); +} + +/* Detect flash access before an invalid geometry is rejected. */ +static UINT unexpected_block_status_get(ULONG block, UCHAR *status) +{ + LX_PARAMETER_NOT_USED(block); + LX_PARAMETER_NOT_USED(status); + unexpected_io++; + return(LX_ERROR); +} +#endif + +/* Expose each test geometry through the existing NAND simulator. */ +static UINT geometry_initialize(LX_NAND_FLASH *nand_flash) +{ + UINT status; + + status = _lx_nand_flash_simulator_initialize(nand_flash); + if (status != LX_SUCCESS) + { + return(status); + } + + nand_flash -> lx_nand_flash_total_blocks = test_blocks; + nand_flash -> lx_nand_flash_pages_per_block = test_pages_per_block; + nand_flash -> lx_nand_flash_bytes_per_page = test_bytes_per_page; + + if (test_reject_io != 0u) + { + nand_flash -> lx_nand_flash_driver_block_status_get = unexpected_block_status_get; + } + else + { + simulator_pages_write = nand_flash -> lx_nand_flash_driver_pages_write; + nand_flash -> lx_nand_flash_driver_pages_write = guarded_pages_write; + } + + return(LX_SUCCESS); +} + +/* Reject unsupported geometry in both entry points before flash access. */ +static UINT invalid_geometry_check(ULONG blocks, ULONG pages_per_block, ULONG bytes_per_page) +{ + UINT status; + + test_blocks = blocks; + test_pages_per_block = pages_per_block; + test_bytes_per_page = bytes_per_page; + test_reject_io = 1u; + unexpected_io = 0u; + + status = lx_nand_flash_format_extended(&test_flash, "geometry", geometry_initialize, + LX_NULL, test_memory, sizeof(test_memory)); + if ((status != LX_NOT_SUPPORTED) || (unexpected_io != 0u)) + { + return(LX_ERROR); + } + + status = lx_nand_flash_open_extended(&test_flash, "geometry", geometry_initialize, + LX_NULL, test_memory, sizeof(test_memory)); + if ((status != LX_NOT_SUPPORTED) || (unexpected_io != 0u)) + { + return(LX_ERROR); + } + + return(LX_SUCCESS); +} + +/* Format, reopen, and rebuild a snapshot at the supported boundary. */ +static UINT boundary_geometry_check(ULONG blocks, ULONG pages_per_block) +{ + ULONG initial_metadata_block; + UINT status; + UINT write_index; + + test_blocks = blocks; + test_pages_per_block = pages_per_block; + test_bytes_per_page = 512u; + test_reject_io = 0u; + invalid_page_writes = 0u; + + status = _lx_nand_flash_simulator_erase_all(); + if (status != LX_SUCCESS) + { + return(status); + } + + status = lx_nand_flash_format_extended(&test_flash, "geometry", geometry_initialize, + LX_NULL, test_memory, sizeof(test_memory)); + if (status != LX_SUCCESS) + { + printf("Format failed for %lu blocks: %u\n", blocks, status); + return(status); + } + + status = lx_nand_flash_open_extended(&test_flash, "geometry", geometry_initialize, + LX_NULL, test_memory, sizeof(test_memory)); + if (status != LX_SUCCESS) + { + printf("Open failed for %lu blocks: %u\n", blocks, status); + return(status); + } + + initial_metadata_block = test_flash.lx_nand_flash_metadata_block_number; + for (write_index = 0u; write_index < TEST_REBUILD_WRITES; write_index++) + { + status = _lx_nand_flash_erase_count_set(&test_flash, 7u, (UCHAR)write_index); + if ((status != LX_SUCCESS) || (invalid_page_writes != 0u)) + { + printf("Rebuild failed for %lu blocks at update %u: %u, invalid writes %u\n", + blocks, write_index, status, invalid_page_writes); + return(LX_ERROR); + } + } + + if (test_flash.lx_nand_flash_metadata_block_number == initial_metadata_block) + { + printf("No rebuild for %lu blocks\n", blocks); + return(LX_ERROR); + } + + return(lx_nand_flash_close(&test_flash)); +} + +/* Exercise rejected geometries and all snapshot page-rounding cases. */ +static INT run_cases(VOID) +{ + UINT status; + + status = lx_nand_flash_initialize(); + if (status == LX_SUCCESS) + { + status = invalid_geometry_check(4096u, 8u, 256u); + } + if (status == LX_SUCCESS) + { + status = invalid_geometry_check(64u, 1u, 512u); + } + if (status == LX_SUCCESS) + { + status = invalid_geometry_check(64u, 8u, 0u); + } + if (status == LX_SUCCESS) + { + status = invalid_geometry_check(0u, 5u, 512u); + } + if (status == LX_SUCCESS) + { + status = boundary_geometry_check(64u, 6u); + } + if (status == LX_SUCCESS) + { + status = invalid_geometry_check(513u, 8u, 512u); + } + if (status == LX_SUCCESS) + { + status = boundary_geometry_check(512u, 8u); + } + if (status != LX_SUCCESS) + { + printf("NAND metadata geometry test failed: %u\n", status); + return(1); + } + + printf("NAND metadata geometry test passed\n"); + return(0); +} + +/* Start the regression in the configured LevelX execution mode. */ +int main(void) +{ +#ifndef LX_STANDALONE_ENABLE + tx_kernel_enter(); + return(1); +#else + return(run_cases()); +#endif +}