Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions common/src/lx_nand_flash_memory_initialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 6 additions & 0 deletions test/cmake/regression/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
277 changes: 277 additions & 0 deletions test/regression/levelx_nand_metadata_geometry_test.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#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
}