From bfb39676df9c8e5b839411d4141d2e52aca90baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 2 Jun 2026 11:25:12 -0400 Subject: [PATCH 1/4] =?UTF-8?q?Added=20opt-in=20lazy=20sector=20release=20?= =?UTF-8?q?to=20reduce=20O(N=C2=B2)=20deletion=20(issue=20#70)=20(#71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WARNING: This feature is brand new and has not been tested beyond the existing regression suite. It should be thoroughly tested — including stress, power-loss, and wear-levelling scenarios — before being used in any production system. When LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE is defined and free blocks exceed LX_NAND_FLASH_SECTOR_RELEASE_LAZY_THRESHOLD (default 10), releasing a sector from a full block defers the copy+erase. A tombstone page is written to a freshly-allocated block; the old full block is flagged COMPACTION_PENDING in its block status and recorded in a new per-LG compaction table. Compaction (merge + erase) is triggered lazily: * sector_write: before writing to a full block with a pending source * block_data_move: intercepts wear-levelling moves for pending LGs * defragment: iterates all pending LGs (now actually implemented) * block_allocate: emergency compaction when the free list is empty Reads transparently fall back to the compaction-pending block when the primary block does not contain the requested sector. Crash recovery is handled in open_extended: - Phase 1: detects COMPACTION_PENDING blocks and either clears the flag (crash before mapping update — abort) or rebuilds compaction_table (crash after mapping update — resume deferred compaction). - Phase 2: erases orphaned ALLOCATED blocks not present in any mapping. Without the flag, all paths remain unchanged and defragment returns LX_NOT_SUPPORTED as before. Fixes: https://github.com/eclipse-threadx/levelx/issues/70 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- common/CMakeLists.txt | 1 + common/inc/lx_api.h | 25 + common/src/lx_nand_flash_block_allocate.c | 38 ++ common/src/lx_nand_flash_block_data_move.c | 28 ++ common/src/lx_nand_flash_defragment.c | 29 ++ .../src/lx_nand_flash_logical_group_compact.c | 447 ++++++++++++++++++ common/src/lx_nand_flash_memory_initialize.c | 43 ++ common/src/lx_nand_flash_open_extended.c | 156 ++++++ common/src/lx_nand_flash_sector_read.c | 71 +++ common/src/lx_nand_flash_sector_release.c | 115 +++++ common/src/lx_nand_flash_sector_write.c | 37 ++ 11 files changed, 990 insertions(+) create mode 100644 common/src/lx_nand_flash_logical_group_compact.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f4cfbfe..5f4a9ac 100755 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_format_extended.c ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_free_block_list_add.c ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_initialize.c + ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_logical_group_compact.c ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_mapped_block_list_add.c ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_mapped_block_list_get.c ${CMAKE_CURRENT_LIST_DIR}/src/lx_nand_flash_mapped_block_list_remove.c diff --git a/common/inc/lx_api.h b/common/inc/lx_api.h index d128902..308cafa 100644 --- a/common/inc/lx_api.h +++ b/common/inc/lx_api.h @@ -274,6 +274,17 @@ typedef unsigned long long ULONG64; #define LX_NAND_FLASH_MAX_METADATA_BLOCKS 4 #endif +/* When LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE is defined, sector releases from full blocks + are deferred when free block count exceeds the threshold below (opt-in, default: off). + Requires pages_per_block <= 4095 (bit 12 is reserved for COMPACTION_PENDING flag). */ +/* #define LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE +#ifndef LX_NAND_FLASH_SECTOR_RELEASE_LAZY_THRESHOLD +#define LX_NAND_FLASH_SECTOR_RELEASE_LAZY_THRESHOLD 10u +#endif +#endif + #ifndef LX_UTILITY_SHORT_SET #define LX_UTILITY_SHORT_SET(address, value) *((USHORT*)(address)) = (USHORT)(value) #endif @@ -328,6 +339,11 @@ typedef unsigned long long ULONG64; #define LX_NAND_BLOCK_STATUS_FULL 0x4000u #define LX_NAND_BLOCK_STATUS_NON_SEQUENTIAL 0x2000u #define LX_NAND_BLOCK_STATUS_MAPPING_PRESENT 0x1000u +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE +/* Repurpose the otherwise-unused MAPPING_PRESENT bit to signal a block whose compaction + has been deferred. Only valid when pages_per_block <= 4095. */ +#define LX_NAND_BLOCK_STATUS_COMPACTION_PENDING LX_NAND_BLOCK_STATUS_MAPPING_PRESENT +#endif #define LX_NAND_BLOCK_STATUS_PAGE_NUMBER_MASK 0x0FFFu #define LX_NAND_BLOCK_STATUS_FREE 0xFFFFu #define LX_NAND_BLOCK_STATUS_BAD 0xFF00u @@ -402,6 +418,12 @@ typedef struct LX_NAND_FLASH_STRUCT USHORT *lx_nand_flash_block_list; ULONG lx_nand_flash_block_list_size; ULONG lx_nand_flash_free_block_list_tail; +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + /* Parallel to block_mapping_table: physical block with pending compaction for a group, + or LX_NAND_BLOCK_UNMAPPED when no compaction is pending. */ + USHORT *lx_nand_flash_block_compaction_table; + ULONG lx_nand_flash_block_compaction_table_size; +#endif ULONG lx_nand_flash_mapped_block_list_head; ULONG lx_nand_flash_metadata_block_number; @@ -771,6 +793,9 @@ UINT _lx_nand_flash_metadata_write(LX_NAND_FLASH *nand_flash, UCHAR* main_buf VOID _lx_nand_flash_system_error(LX_NAND_FLASH *nand_flash, UINT error_code, ULONG block, ULONG page); UINT _lx_nand_flash_256byte_ecc_check(UCHAR *page_buffer, UCHAR *ecc_buffer); UINT _lx_nand_flash_256byte_ecc_compute(UCHAR *page_buffer, UCHAR *ecc_buffer); +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE +UINT _lx_nand_flash_logical_group_compact(LX_NAND_FLASH *nand_flash, ULONG logical_group); +#endif UINT _lx_nor_flash_block_reclaim(LX_NOR_FLASH *nor_flash); UINT _lx_nor_flash_driver_block_erase(LX_NOR_FLASH *nor_flash, ULONG block, ULONG erase_count); diff --git a/common/src/lx_nand_flash_block_allocate.c b/common/src/lx_nand_flash_block_allocate.c index 3652d59..ff3407d 100644 --- a/common/src/lx_nand_flash_block_allocate.c +++ b/common/src/lx_nand_flash_block_allocate.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -75,6 +76,43 @@ UINT _lx_nand_flash_block_allocate(LX_NAND_FLASH* nand_flash, ULONG* block) if (nand_flash -> lx_nand_flash_free_block_list_tail == 0) { +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + { + + ULONG lg; + UINT compact_status; + + /* Emergency compaction: find any pending compaction and execute it to + reclaim the old full block. */ + for (lg = 0; lg < nand_flash -> lx_nand_flash_total_blocks; lg++) + { + + if (nand_flash -> lx_nand_flash_block_compaction_table[lg] != (USHORT)LX_NAND_BLOCK_UNMAPPED) + { + + compact_status = _lx_nand_flash_logical_group_compact(nand_flash, lg); + + if (compact_status == LX_SUCCESS) + { + + /* Retry the allocation after compaction freed a block. */ + if (nand_flash -> lx_nand_flash_free_block_list_tail > 0) + { + + nand_flash -> lx_nand_flash_free_block_list_tail--; + *block = nand_flash -> lx_nand_flash_block_list[nand_flash -> lx_nand_flash_free_block_list_tail]; + return(LX_SUCCESS); + } + } + + /* Attempt only one compaction per allocate call. */ + break; + } + } + + } +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + /* Empty list, return error. */ return(LX_NO_BLOCKS); } diff --git a/common/src/lx_nand_flash_block_data_move.c b/common/src/lx_nand_flash_block_data_move.c index 6585003..e265243 100644 --- a/common/src/lx_nand_flash_block_data_move.c +++ b/common/src/lx_nand_flash_block_data_move.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -111,6 +112,33 @@ ULONG block_mapping_index; return(LX_ERROR); } +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + /* If this logical group has a pending compaction source, redirect the + data move to a full logical-group compact to correctly merge data from + both source blocks. */ + if (nand_flash -> lx_nand_flash_block_compaction_table[block_mapping_index] != (USHORT)LX_NAND_BLOCK_UNMAPPED) + { + + /* Return the pre-allocated new_block to the free list. */ + status = _lx_nand_flash_block_status_set(nand_flash, new_block, LX_NAND_BLOCK_STATUS_FREE); + + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, new_block, 0); + return(LX_ERROR); + } + + _lx_nand_flash_free_block_list_add(nand_flash, new_block); + + /* Restore block_mapping_index to the mapped list; mapped_block_list_get + already removed it before this function was called. */ + _lx_nand_flash_mapped_block_list_add(nand_flash, block_mapping_index); + + /* Perform full compaction merge of both source blocks. */ + return(_lx_nand_flash_logical_group_compact(nand_flash, block_mapping_index)); + } +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + /* Set new block status to allocated for now. */ new_block_status = LX_NAND_BLOCK_STATUS_ALLOCATED; diff --git a/common/src/lx_nand_flash_defragment.c b/common/src/lx_nand_flash_defragment.c index b2fa684..8afced7 100644 --- a/common/src/lx_nand_flash_defragment.c +++ b/common/src/lx_nand_flash_defragment.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -72,10 +73,38 @@ UINT _lx_nand_flash_defragment(LX_NAND_FLASH *nand_flash) { +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + +UINT status; +ULONG lg; + + /* Compact every logical group that has a deferred compaction pending. */ + for (lg = 0; lg < nand_flash -> lx_nand_flash_total_blocks; lg++) + { + + if (nand_flash -> lx_nand_flash_block_compaction_table[lg] != (USHORT)LX_NAND_BLOCK_UNMAPPED) + { + + status = _lx_nand_flash_logical_group_compact(nand_flash, lg); + + /* On error, continue to compact remaining groups. */ + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, lg, 0); + } + } + } + + return(LX_SUCCESS); + +#else + LX_PARAMETER_NOT_USED(nand_flash); /* Return not supported. */ return(LX_NOT_SUPPORTED); + +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ } diff --git a/common/src/lx_nand_flash_logical_group_compact.c b/common/src/lx_nand_flash_logical_group_compact.c new file mode 100644 index 0000000..1e71221 --- /dev/null +++ b/common/src/lx_nand_flash_logical_group_compact.c @@ -0,0 +1,447 @@ +/*************************************************************************** + * Copyright (c) 2026-present 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. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ + +// Some portions generated by Copilot (Sonnet 4.6). + +/**************************************************************************/ +/**************************************************************************/ +/** */ +/** LevelX Component */ +/** */ +/** NAND Flash */ +/** */ +/**************************************************************************/ +/**************************************************************************/ + +#define LX_SOURCE_CODE + + +/* Disable ThreadX error checking. */ + +#ifndef LX_DISABLE_ERROR_CHECKING +#define LX_DISABLE_ERROR_CHECKING +#endif + + +/* Include necessary system files. */ + +#include "lx_api.h" + + +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _lx_nand_sector_find_in_block PORTABLE C */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This helper scans a block for a specific logical sector and returns */ +/* the page number and kind (data / released / not found). */ +/* */ +/* INPUT */ +/* */ +/* nand_flash NAND flash instance */ +/* block Physical block number */ +/* block_status Block status word */ +/* logical_sector Logical sector to search for */ +/* found_page Destination: page index found */ +/* sector_kind Destination: kind (see below) */ +/* */ +/* OUTPUT */ +/* */ +/* LX_SUCCESS Sector found; outputs set */ +/* LX_NO_BLOCKS Sector not present in block */ +/* LX_ERROR Driver error */ +/* */ +/* SECTOR KIND VALUES (written to *sector_kind) */ +/* */ +/* LX_NAND_PAGE_TYPE_USER_DATA Sector contains live data */ +/* LX_NAND_PAGE_TYPE_USER_DATA_RELEASED Sector is tombstoned */ +/* */ +/**************************************************************************/ +static UINT _lx_nand_sector_find_in_block(LX_NAND_FLASH *nand_flash, + ULONG block, + USHORT block_status, + ULONG logical_sector, + LONG *found_page, + ULONG *sector_kind) +{ + +ULONG available_pages; +LONG page; +ULONG spare_data1; +UINT status; +UCHAR *spare_buffer_ptr; + + + /* Use page buffer as scratch for spare-only reads (per existing LevelX convention). */ + spare_buffer_ptr = nand_flash -> lx_nand_flash_page_buffer; + + /* Determine page count. */ + available_pages = (block_status & LX_NAND_BLOCK_STATUS_FULL) ? + nand_flash -> lx_nand_flash_pages_per_block : + (block_status & LX_NAND_BLOCK_STATUS_PAGE_NUMBER_MASK); + + if (block_status & LX_NAND_BLOCK_STATUS_NON_SEQUENTIAL) + { + + /* Scan newest-to-oldest so the first match is the authoritative version. */ + for (page = (LONG)available_pages - 1; page >= 0; page--) + { + +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_read)(nand_flash, block, (ULONG)page, LX_NULL, spare_buffer_ptr, 1); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_read)(block, (ULONG)page, LX_NULL, spare_buffer_ptr, 1); +#endif + + /* Check for an error from flash driver. */ + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, block, 0); + return(LX_ERROR); + } + + spare_data1 = LX_UTILITY_LONG_GET(&spare_buffer_ptr[nand_flash -> lx_nand_flash_spare_data1_offset]); + + if ((spare_data1 & LX_NAND_PAGE_TYPE_USER_DATA_MASK) == logical_sector) + { + + /* Sector found; return its page index and kind. */ + *found_page = page; + *sector_kind = spare_data1 & (~LX_NAND_PAGE_TYPE_USER_DATA_MASK); + return(LX_SUCCESS); + } + } + } + else + { + + /* Sequential block: page index == sector offset within group. */ + page = (LONG)(logical_sector % nand_flash -> lx_nand_flash_pages_per_block); + if ((ULONG)page < available_pages) + { + *found_page = page; + *sector_kind = LX_NAND_PAGE_TYPE_USER_DATA; + return(LX_SUCCESS); + } + } + + *found_page = -1L; + return(LX_NO_BLOCKS); +} + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _lx_nand_flash_logical_group_compact PORTABLE C */ +/* 6.5.0 */ +/* AUTHOR */ +/* */ +/* Eclipse ThreadX contributors */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function completes a deferred full-block compaction created by */ +/* the lazy sector-release path. It merges the tombstone block */ +/* (current mapping) and the compaction-pending old block into a fresh */ +/* destination block, then erases both source blocks. */ +/* */ +/* The tombstone block takes priority: if a sector appears in both */ +/* blocks the tombstone block's version is used (newer). */ +/* */ +/* INPUT */ +/* */ +/* nand_flash NAND flash instance */ +/* logical_group Logical group index */ +/* */ +/* OUTPUT */ +/* */ +/* LX_SUCCESS Compaction completed */ +/* LX_ERROR / LX_NO_BLOCKS Error during compaction */ +/* */ +/* CALLS */ +/* */ +/* _lx_nand_sector_find_in_block Find sector in a block */ +/* _lx_nand_flash_block_allocate Allocate a free block */ +/* _lx_nand_flash_block_status_set Set block status */ +/* _lx_nand_flash_block_mapping_set Update logical-to-physical map*/ +/* _lx_nand_flash_mapped_block_list_remove Remove from mapped list */ +/* _lx_nand_flash_mapped_block_list_add Add to mapped list */ +/* _lx_nand_flash_free_block_list_add Return block to free list */ +/* _lx_nand_flash_driver_block_erase Erase a block */ +/* _lx_nand_flash_erase_count_set Record new erase count */ +/* _lx_nand_flash_block_data_move Wear-level an over-erased blk */ +/* _lx_nand_flash_system_error Internal system error handler */ +/* lx_nand_flash_driver_pages_copy Copy a page between blocks */ +/* */ +/* CALLED BY */ +/* */ +/* _lx_nand_flash_sector_release Lazy release pre-compact */ +/* _lx_nand_flash_sector_write Pre-compact before block copy */ +/* _lx_nand_flash_block_data_move Wear-level with pending compact*/ +/* _lx_nand_flash_defragment Explicit defragment pass */ +/* _lx_nand_flash_block_allocate Emergency compaction */ +/* */ +/**************************************************************************/ +UINT _lx_nand_flash_logical_group_compact(LX_NAND_FLASH *nand_flash, ULONG logical_group) +{ + +UINT status; +ULONG tombstone_block; +ULONG compaction_block; +ULONG dest_block; +USHORT tombstone_block_status; +USHORT compaction_block_status; +USHORT dest_block_status; +ULONG sector; +ULONG base_sector; +LONG source_page; +ULONG dest_page; +ULONG sector_kind; +UINT find_status; + + + /* Retrieve the two source blocks. */ + tombstone_block = nand_flash -> lx_nand_flash_block_mapping_table[logical_group]; + compaction_block = nand_flash -> lx_nand_flash_block_compaction_table[logical_group]; + tombstone_block_status = nand_flash -> lx_nand_flash_block_status_table[tombstone_block]; + compaction_block_status = nand_flash -> lx_nand_flash_block_status_table[compaction_block]; + + /* Allocate a fresh destination block. */ + status = _lx_nand_flash_block_allocate(nand_flash, &dest_block); + if (status != LX_SUCCESS) + { + return(status); + } + + dest_block_status = LX_NAND_BLOCK_STATUS_ALLOCATED; + dest_page = 0; + base_sector = logical_group * nand_flash -> lx_nand_flash_pages_per_block; + + /* Merge loop: iterate every sector in the logical group. + The tombstone block (current mapping) supersedes the compaction block. */ + for (sector = base_sector; + sector < base_sector + nand_flash -> lx_nand_flash_pages_per_block; + sector++) + { + + source_page = -1L; + sector_kind = 0; + + /* Search the tombstone block first (newer data). */ + find_status = _lx_nand_sector_find_in_block(nand_flash, tombstone_block, + tombstone_block_status, sector, + &source_page, §or_kind); + if (find_status == LX_ERROR) + { + return(LX_ERROR); + } + + if (find_status == LX_NO_BLOCKS) + { + + /* Not in tombstone block; try the compaction block (older data). */ + find_status = _lx_nand_sector_find_in_block(nand_flash, compaction_block, + compaction_block_status, sector, + &source_page, §or_kind); + if (find_status == LX_ERROR) + { + return(LX_ERROR); + } + + if ((find_status == LX_SUCCESS) && (sector_kind == LX_NAND_PAGE_TYPE_USER_DATA)) + { + + /* Copy live data from compaction block to destination. */ +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_copy)(nand_flash, compaction_block, (ULONG)source_page, dest_block, dest_page, 1, nand_flash -> lx_nand_flash_page_buffer); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_copy)(compaction_block, (ULONG)source_page, dest_block, dest_page, 1, nand_flash -> lx_nand_flash_page_buffer); +#endif + + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, dest_block, 0); + return(LX_ERROR); + } + + if (dest_page != (sector % nand_flash -> lx_nand_flash_pages_per_block)) + { + dest_block_status |= LX_NAND_BLOCK_STATUS_NON_SEQUENTIAL; + } + + dest_page++; + } + /* RELEASED or NOT_FOUND in compaction block: sector no longer exists, skip. */ + } + else if ((find_status == LX_SUCCESS) && (sector_kind == LX_NAND_PAGE_TYPE_USER_DATA)) + { + + /* Copy live data from tombstone block to destination. */ +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_copy)(nand_flash, tombstone_block, (ULONG)source_page, dest_block, dest_page, 1, nand_flash -> lx_nand_flash_page_buffer); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_copy)(tombstone_block, (ULONG)source_page, dest_block, dest_page, 1, nand_flash -> lx_nand_flash_page_buffer); +#endif + + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, dest_block, 0); + return(LX_ERROR); + } + + if (dest_page != (sector % nand_flash -> lx_nand_flash_pages_per_block)) + { + dest_block_status |= LX_NAND_BLOCK_STATUS_NON_SEQUENTIAL; + } + + dest_page++; + } + /* RELEASED (tombstone found in tombstone block): sector is gone, skip. */ + } + + /* Encode page count into dest_block_status. */ + dest_block_status = (USHORT)((dest_block_status & ~(USHORT)LX_NAND_BLOCK_STATUS_PAGE_NUMBER_MASK) | + (dest_page & LX_NAND_BLOCK_STATUS_PAGE_NUMBER_MASK)); + + /* Commit destination block or discard it if the entire group was released. */ + if (dest_page == 0) + { + + /* All sectors released; destination block is unused. */ + status = _lx_nand_flash_block_status_set(nand_flash, dest_block, LX_NAND_BLOCK_STATUS_FREE); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, dest_block, 0); + return(LX_ERROR); + } + _lx_nand_flash_free_block_list_add(nand_flash, dest_block); + dest_block = (ULONG)LX_NAND_BLOCK_UNMAPPED; + } + else + { + status = _lx_nand_flash_block_status_set(nand_flash, dest_block, dest_block_status); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, dest_block, 0); + return(LX_ERROR); + } + } + + /* Remove tombstone block from the mapped list. */ + _lx_nand_flash_mapped_block_list_remove(nand_flash, logical_group); + + /* Persist the new mapping (or UNMAPPED if all sectors were released). */ + _lx_nand_flash_block_mapping_set(nand_flash, logical_group * nand_flash -> lx_nand_flash_pages_per_block, dest_block); + + /* Clear the in-RAM compaction table entry. */ + nand_flash -> lx_nand_flash_block_compaction_table[logical_group] = (USHORT)LX_NAND_BLOCK_UNMAPPED; + + /* Add destination block to the mapped list if it holds data. */ + if (dest_block != (ULONG)LX_NAND_BLOCK_UNMAPPED) + { + _lx_nand_flash_mapped_block_list_add(nand_flash, logical_group); + } + + /* ---- Erase tombstone block ---- */ + status = _lx_nand_flash_driver_block_erase(nand_flash, tombstone_block, + nand_flash -> lx_nand_flash_base_erase_count + + nand_flash -> lx_nand_flash_erase_count_table[tombstone_block] + 1); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, tombstone_block, 0); + return(LX_ERROR); + } + + status = _lx_nand_flash_erase_count_set(nand_flash, tombstone_block, + (UCHAR)(nand_flash -> lx_nand_flash_erase_count_table[tombstone_block] + 1)); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, tombstone_block, 0); + return(LX_ERROR); + } + + if (nand_flash -> lx_nand_flash_erase_count_table[tombstone_block] > LX_NAND_FLASH_MAX_ERASE_COUNT_DELTA) + { + status = _lx_nand_flash_block_data_move(nand_flash, tombstone_block); + } + else + { + status = _lx_nand_flash_block_status_set(nand_flash, tombstone_block, LX_NAND_BLOCK_STATUS_FREE); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, tombstone_block, 0); + return(LX_ERROR); + } + status = _lx_nand_flash_free_block_list_add(nand_flash, tombstone_block); + } + + if (status) + { + return(LX_ERROR); + } + + /* ---- Clear COMPACTION_PENDING flag on the old block, then erase it ---- */ + status = _lx_nand_flash_block_status_set(nand_flash, compaction_block, + (USHORT)(nand_flash -> lx_nand_flash_block_status_table[compaction_block] & + ~(USHORT)LX_NAND_BLOCK_STATUS_COMPACTION_PENDING)); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, compaction_block, 0); + return(LX_ERROR); + } + + status = _lx_nand_flash_driver_block_erase(nand_flash, compaction_block, + nand_flash -> lx_nand_flash_base_erase_count + + nand_flash -> lx_nand_flash_erase_count_table[compaction_block] + 1); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, compaction_block, 0); + return(LX_ERROR); + } + + status = _lx_nand_flash_erase_count_set(nand_flash, compaction_block, + (UCHAR)(nand_flash -> lx_nand_flash_erase_count_table[compaction_block] + 1)); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, compaction_block, 0); + return(LX_ERROR); + } + + if (nand_flash -> lx_nand_flash_erase_count_table[compaction_block] > LX_NAND_FLASH_MAX_ERASE_COUNT_DELTA) + { + status = _lx_nand_flash_block_data_move(nand_flash, compaction_block); + } + else + { + status = _lx_nand_flash_block_status_set(nand_flash, compaction_block, LX_NAND_BLOCK_STATUS_FREE); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, compaction_block, 0); + return(LX_ERROR); + } + status = _lx_nand_flash_free_block_list_add(nand_flash, compaction_block); + } + + return(status); +} + +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ diff --git a/common/src/lx_nand_flash_memory_initialize.c b/common/src/lx_nand_flash_memory_initialize.c index 5975868..92143d4 100644 --- a/common/src/lx_nand_flash_memory_initialize.c +++ b/common/src/lx_nand_flash_memory_initialize.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -181,6 +182,48 @@ UINT buffer_size; return(LX_NO_MEMORY); } +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + { + + ULONG i; + ULONG compaction_table_entries; + + /* Set memory size for block compaction table. Same element type as block_mapping_table. */ + buffer_size = nand_flash -> lx_nand_flash_total_blocks * sizeof(*nand_flash -> lx_nand_flash_block_compaction_table); + + /* Make sure the size is at least one page size. */ + if (buffer_size < nand_flash -> lx_nand_flash_bytes_per_page) + { + buffer_size = nand_flash -> lx_nand_flash_bytes_per_page; + } + + /* Assign memory for block compaction table. */ + nand_flash -> lx_nand_flash_block_compaction_table = (USHORT*)(((UCHAR*)memory_ptr) + memory_offset); + + /* Update block compaction table size. */ + nand_flash -> lx_nand_flash_block_compaction_table_size = buffer_size; + + /* Update memory offset. */ + memory_offset += buffer_size; + + /* Check if there is enough memory. */ + if (memory_offset > memory_size) + { + + /* No enough memory, return error. */ + return(LX_NO_MEMORY); + } + + /* Initialize all compaction table entries to unmapped. (LX_MEMSET zero-fills the buffer + above, but LX_NAND_BLOCK_UNMAPPED is 0xFFFF, so explicit initialisation is required.) */ + compaction_table_entries = buffer_size / sizeof(*nand_flash -> lx_nand_flash_block_compaction_table); + for (i = 0; i < compaction_table_entries; i++) + { + nand_flash -> lx_nand_flash_block_compaction_table[i] = (USHORT)LX_NAND_BLOCK_UNMAPPED; + } + } +#endif + /* Assign memory for page buffer. */ nand_flash -> lx_nand_flash_page_buffer = ((UCHAR*)memory_ptr) + memory_offset; diff --git a/common/src/lx_nand_flash_open_extended.c b/common/src/lx_nand_flash_open_extended.c index 617041f..69f97d5 100644 --- a/common/src/lx_nand_flash_open_extended.c +++ b/common/src/lx_nand_flash_open_extended.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -460,6 +461,161 @@ LX_INTERRUPT_SAVE_AREA } +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + { + + ULONG blk; + ULONG lg; + USHORT blk_status; + + /* Phase 1: Rebuild compaction table and recover from mid-release crashes. + Scan all blocks for COMPACTION_PENDING. */ + for (blk = 0; blk < nand_flash -> lx_nand_flash_total_blocks; blk++) + { + + blk_status = nand_flash -> lx_nand_flash_block_status_table[blk]; + + /* FREE (0xFFFF) and BAD (0xFF00) both have COMPACTION_PENDING bit set; skip them. */ + if ((blk_status == LX_NAND_BLOCK_STATUS_FREE) || (blk_status == LX_NAND_BLOCK_STATUS_BAD)) + { + continue; + } + + if (blk_status & LX_NAND_BLOCK_STATUS_COMPACTION_PENDING) + { + + /* Check if any logical group still maps to this physical block. + If so, the mapping update didn't happen — abort the lazy release. */ + lg = nand_flash -> lx_nand_flash_total_blocks; + { + ULONG search_lg; + for (search_lg = 0; search_lg < nand_flash -> lx_nand_flash_total_blocks; search_lg++) + { + if (nand_flash -> lx_nand_flash_block_mapping_table[search_lg] == (USHORT)blk) + { + lg = search_lg; + break; + } + } + } + + if (lg != nand_flash -> lx_nand_flash_total_blocks) + { + + /* Scenario A: crash after COMPACTION_PENDING set, before mapping updated. + Clear COMPACTION_PENDING to restore the block to its normal state. */ + blk_status = (USHORT)(blk_status & (USHORT)(~LX_NAND_BLOCK_STATUS_COMPACTION_PENDING)); + (void)_lx_nand_flash_block_status_set(nand_flash, blk, blk_status); + } + else + { + + /* Scenario B: deferred compaction — scan block pages to find the LG. */ + ULONG page; + ULONG spare_word; + ULONG found_lg; + UCHAR *spare_ptr; + + spare_ptr = (UCHAR*)nand_flash -> lx_nand_flash_page_buffer; + found_lg = nand_flash -> lx_nand_flash_total_blocks; + + for (page = 0; page < nand_flash -> lx_nand_flash_pages_per_block; page++) + { + +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_read)(nand_flash, blk, page, (UCHAR*)NULL, spare_ptr, 1); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_read)(blk, page, (UCHAR*)NULL, spare_ptr, 1); +#endif + + if (status) + { + break; + } + + spare_word = LX_UTILITY_LONG_GET(&spare_ptr[nand_flash -> lx_nand_flash_spare_data1_offset]); + + if ((spare_word & (~LX_NAND_PAGE_TYPE_USER_DATA_MASK)) == LX_NAND_PAGE_TYPE_USER_DATA) + { + found_lg = (spare_word & LX_NAND_PAGE_TYPE_USER_DATA_MASK) / + nand_flash -> lx_nand_flash_pages_per_block; + break; + } + } + + if ((found_lg != nand_flash -> lx_nand_flash_total_blocks) && + (found_lg < nand_flash -> lx_nand_flash_total_blocks)) + { + nand_flash -> lx_nand_flash_block_compaction_table[found_lg] = (USHORT)blk; + } + } + } + } + + /* Phase 2: Orphan block recovery — erase ALLOCATED blocks not referenced + by any mapping entry and not marked COMPACTION_PENDING. */ + for (blk = 0; blk < nand_flash -> lx_nand_flash_total_blocks; blk++) + { + + ULONG found; + + blk_status = nand_flash -> lx_nand_flash_block_status_table[blk]; + + if ((blk_status == LX_NAND_BLOCK_STATUS_FREE) || (blk_status == LX_NAND_BLOCK_STATUS_BAD)) + { + continue; + } + + if (blk_status & LX_NAND_BLOCK_STATUS_COMPACTION_PENDING) + { + continue; + } + + if (!(blk_status & LX_NAND_BLOCK_STATUS_ALLOCATED)) + { + continue; + } + + /* Search for this block in the mapping table. */ + found = LX_FALSE; + for (lg = 0; lg < nand_flash -> lx_nand_flash_total_blocks; lg++) + { + if (nand_flash -> lx_nand_flash_block_mapping_table[lg] == (USHORT)blk) + { + found = LX_TRUE; + break; + } + } + + if (!found) + { + + /* Orphan block: erase and reclaim. */ + status = _lx_nand_flash_driver_block_erase(nand_flash, blk, + nand_flash -> lx_nand_flash_base_erase_count + + nand_flash -> lx_nand_flash_erase_count_table[blk] + 1); + + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, blk, 0); + continue; + } + + status = _lx_nand_flash_erase_count_set(nand_flash, blk, + (UCHAR)(nand_flash -> lx_nand_flash_erase_count_table[blk] + 1)); + + if (!status) + { + (void)_lx_nand_flash_block_status_set(nand_flash, blk, LX_NAND_BLOCK_STATUS_FREE); + _lx_nand_flash_free_block_list_add(nand_flash, blk); + } + } + } + + } +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + + #ifdef LX_THREAD_SAFE_ENABLE /* If the thread safe option is enabled, create a ThreadX mutex that will be used in all external APIs diff --git a/common/src/lx_nand_flash_sector_read.c b/common/src/lx_nand_flash_sector_read.c index f469f37..aa537dc 100644 --- a/common/src/lx_nand_flash_sector_read.c +++ b/common/src/lx_nand_flash_sector_read.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -231,6 +232,76 @@ LONG page; } } +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + { + + ULONG logical_group; + ULONG old_block; + + /* Check the compaction table for a deferred source block that might hold + the requested sector. The primary block (tombstone block) was already + scanned above; fall back to the old full block here. */ + logical_group = logical_sector / nand_flash -> lx_nand_flash_pages_per_block; + + if (nand_flash -> lx_nand_flash_block_compaction_table[logical_group] != (USHORT)LX_NAND_BLOCK_UNMAPPED) + { + + old_block = nand_flash -> lx_nand_flash_block_compaction_table[logical_group]; + + /* Set spare buffer pointer. */ + spare_buffer_ptr = (UCHAR*)nand_flash -> lx_nand_flash_page_buffer; + + /* The compaction-pending block is always FULL; scan all pages in reverse. */ + for (page = (LONG)nand_flash -> lx_nand_flash_pages_per_block - 1; page >= 0; page--) + { + +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_read)(nand_flash, old_block, (ULONG)page, (UCHAR*)NULL, spare_buffer_ptr, 1); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_read)(old_block, (ULONG)page, (UCHAR*)NULL, spare_buffer_ptr, 1); +#endif + + /* Check for an error from flash driver. */ + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, old_block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + + /* Check if this page matches the requested sector. */ + if ((LX_UTILITY_LONG_GET(&spare_buffer_ptr[nand_flash -> lx_nand_flash_spare_data1_offset]) & LX_NAND_PAGE_TYPE_USER_DATA_MASK) == logical_sector) + { + +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_read)(nand_flash, old_block, (ULONG)page, (UCHAR*)buffer, NULL, 1); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_read)(old_block, (ULONG)page, (UCHAR*)buffer, NULL, 1); +#endif + + /* Check for an error from flash driver. */ + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, old_block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_SUCCESS); + } + } + } + + } +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + /* Sector hasn't been written. Simply fill the destination buffer with ones and return success. */ /* Setup pointer to users buffer. */ diff --git a/common/src/lx_nand_flash_sector_release.c b/common/src/lx_nand_flash_sector_release.c index 4732ea0..9783442 100644 --- a/common/src/lx_nand_flash_sector_release.c +++ b/common/src/lx_nand_flash_sector_release.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -205,6 +206,119 @@ USHORT new_block_status; if (block_status & LX_NAND_BLOCK_STATUS_FULL) { +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + /* Lazy path: defer the copy+erase when enough free blocks are available. + The old block is kept as a compaction source; a new block holds only + the tombstone for the released sector. */ + if (nand_flash -> lx_nand_flash_free_block_list_tail > + (ULONG)LX_NAND_FLASH_SECTOR_RELEASE_LAZY_THRESHOLD) + { + + /* Allocate a new block for the tombstone. */ + status = _lx_nand_flash_block_allocate(nand_flash, &new_block); + + /* Check return status. */ + if (status != LX_SUCCESS) + { + + /* Call system error handler. */ + _lx_nand_flash_system_error(nand_flash, status, new_block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + + /* Release the thread safe mutex. */ + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + /* Return an error. */ + return(LX_ERROR); + } + + /* Set page buffer to all 0xFF (data = erased, sector is released). */ + LX_MEMSET(nand_flash -> lx_nand_flash_page_buffer, 0xFF, + nand_flash -> lx_nand_flash_bytes_per_page + + nand_flash -> lx_nand_flash_spare_total_length); + + /* Setup spare buffer pointer. */ + spare_buffer_ptr = nand_flash -> lx_nand_flash_page_buffer + + nand_flash -> lx_nand_flash_bytes_per_page; + + /* Save metadata block number in spare bytes if space allows. */ + if (nand_flash -> lx_nand_flash_spare_data2_length >= sizeof(USHORT)) + { + LX_UTILITY_SHORT_SET(&spare_buffer_ptr[nand_flash -> lx_nand_flash_spare_data2_offset], + nand_flash -> lx_nand_flash_metadata_block_number); + } + + /* Set page type to USER_DATA_RELEASED. */ + LX_UTILITY_LONG_SET(&spare_buffer_ptr[nand_flash -> lx_nand_flash_spare_data1_offset], + LX_NAND_PAGE_TYPE_USER_DATA_RELEASED | logical_sector); + + /* Write the tombstone page to page 0 of the new block. */ +#ifdef LX_NAND_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = (nand_flash -> lx_nand_flash_driver_pages_write)(nand_flash, new_block, 0, + (UCHAR*)nand_flash -> lx_nand_flash_page_buffer, spare_buffer_ptr, 1); +#else + status = (nand_flash -> lx_nand_flash_driver_pages_write)(new_block, 0, + (UCHAR*)nand_flash -> lx_nand_flash_page_buffer, spare_buffer_ptr, 1); +#endif + + /* Check for an error from flash driver. */ + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, new_block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + + /* Set new block status: allocated, non-sequential, 1 page written. */ + new_block_status = (USHORT)(LX_NAND_BLOCK_STATUS_ALLOCATED | + LX_NAND_BLOCK_STATUS_NON_SEQUENTIAL | 1u); + status = _lx_nand_flash_block_status_set(nand_flash, new_block, new_block_status); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, new_block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + + /* Mark old block as compaction-pending (persisted to flash). + Write order: tombstone written → new_block status set → + COMPACTION_PENDING set → mapping updated. + This ordering ensures safe crash recovery at open time. */ + status = _lx_nand_flash_block_status_set(nand_flash, block, + (USHORT)(block_status | LX_NAND_BLOCK_STATUS_COMPACTION_PENDING)); + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + + /* Remove old mapping before updating to new block. */ + _lx_nand_flash_mapped_block_list_remove(nand_flash, + logical_sector / nand_flash -> lx_nand_flash_pages_per_block); + + /* Update logical-to-physical mapping to the new tombstone block. */ + _lx_nand_flash_block_mapping_set(nand_flash, logical_sector, new_block); + + /* Record the old block as the compaction source (RAM only; rebuilt at open). */ + nand_flash -> lx_nand_flash_block_compaction_table[logical_sector / nand_flash -> lx_nand_flash_pages_per_block] = (USHORT)block; + + /* Add new tombstone block to the mapped list. */ + _lx_nand_flash_mapped_block_list_add(nand_flash, + logical_sector / nand_flash -> lx_nand_flash_pages_per_block); + } + else +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + { + + /* Eager path (existing): allocate a new block and copy + erase immediately. */ + /* Allocate a new block. */ status = _lx_nand_flash_block_allocate(nand_flash, &new_block); @@ -387,6 +501,7 @@ USHORT new_block_status; /* Add the new block to mapped block list. */ _lx_nand_flash_mapped_block_list_add(nand_flash, logical_sector / nand_flash -> lx_nand_flash_pages_per_block); } + } /* end eager path else block */ } else { diff --git a/common/src/lx_nand_flash_sector_write.c b/common/src/lx_nand_flash_sector_write.c index 220dcd7..a625b53 100644 --- a/common/src/lx_nand_flash_sector_write.c +++ b/common/src/lx_nand_flash_sector_write.c @@ -9,6 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Sonnet 4.6). /**************************************************************************/ /**************************************************************************/ @@ -131,6 +132,42 @@ UINT copy_block = LX_FALSE; } } +#ifdef LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE + /* If the block is full and has a pending compaction source, compact it now + before writing so the new data merges correctly with a single source block. */ + if ((block != LX_NAND_BLOCK_UNMAPPED) && (block_status & LX_NAND_BLOCK_STATUS_FULL) && + (nand_flash -> lx_nand_flash_block_compaction_table[logical_sector / nand_flash -> lx_nand_flash_pages_per_block] != (USHORT)LX_NAND_BLOCK_UNMAPPED)) + { + + status = _lx_nand_flash_logical_group_compact(nand_flash, + logical_sector / nand_flash -> lx_nand_flash_pages_per_block); + + if (status) + { + _lx_nand_flash_system_error(nand_flash, status, block, 0); +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + + /* Refresh block and block_status after compaction. */ + status = _lx_nand_flash_block_find(nand_flash, logical_sector, &block, &block_status); + + if (status != LX_SUCCESS) + { + + if (status != LX_NAND_ERROR_CORRECTED) + { +#ifdef LX_THREAD_SAFE_ENABLE + tx_mutex_put(&nand_flash -> lx_nand_flash_mutex); +#endif + return(LX_ERROR); + } + } + } +#endif /* LX_NAND_FLASH_ENABLE_LAZY_SECTOR_RELEASE */ + /* Check if block is unmapped or block is full. */ if (block == LX_NAND_BLOCK_UNMAPPED || block_status & LX_NAND_BLOCK_STATUS_FULL) { From 2e061ba31921db1e37fdf6b6f170d7282e54a949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 4 Jun 2026 17:11:35 -0400 Subject: [PATCH 2/4] Added a release preparation script (#73) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/prepare_release.sh | 132 +++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 scripts/prepare_release.sh diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh new file mode 100755 index 0000000..14b7556 --- /dev/null +++ b/scripts/prepare_release.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# prepare_release.sh +# Prepares a LevelX release by updating version constants. +# +# Usage: prepare_release.sh +# Example: prepare_release.sh 6.5.1.202602 +# Hotfix: prepare_release.sh 6.5.1.202602a +# +# This script: +# 1. Creates branch release--preparation from dev +# 2. Updates version constants in common/inc/lx_api.h +# (commit: "Updated version number constants") +# (No port-specific version strings in this repository) +# +# Copyright (C) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT + +set -eu + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +API_HEADER="${REPO_ROOT}/common/inc/lx_api.h" + +# -------------------------------------------------------------------------- +# Argument validation +# -------------------------------------------------------------------------- +if [ "$#" -ne 1 ]; then + printf "Usage: %s \n" "$(basename "$0")" >&2 + printf "Example: %s 6.5.1.202602\n" "$(basename "$0")" >&2 + exit 1 +fi + +VERSION="$1" + +if ! printf "%s" "${VERSION}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[a-z]?$'; then + printf "Error: Invalid version format '%s'.\n" "${VERSION}" >&2 + printf "Expected: MAJOR.MINOR.PATCH.BUILD[hotfix_letter]\n" >&2 + exit 1 +fi + +# -------------------------------------------------------------------------- +# Parse version components +# -------------------------------------------------------------------------- +MAJOR=$(printf "%s" "${VERSION}" | cut -d. -f1) +MINOR=$(printf "%s" "${VERSION}" | cut -d. -f2) +PATCH=$(printf "%s" "${VERSION}" | cut -d. -f3) +BUILD_AND_HOTFIX=$(printf "%s" "${VERSION}" | cut -d. -f4) +BUILD=$(printf "%s" "${BUILD_AND_HOTFIX}" | sed -E 's/[a-z]+$//') +HOTFIX=$(printf "%s" "${BUILD_AND_HOTFIX}" | sed -E 's/^[0-9]+//') + +if [ -z "${HOTFIX}" ]; then + HOTFIX_DEFINE="' '" +else + HOTFIX_DEFINE="'${HOTFIX}'" +fi + +# -------------------------------------------------------------------------- +# Read and display current version +# -------------------------------------------------------------------------- +CURR_MAJOR=$(grep -E "^#define LEVELX_MAJOR_VERSION" "${API_HEADER}" | awk '{print $NF}') +CURR_MINOR=$(grep -E "^#define LEVELX_MINOR_VERSION" "${API_HEADER}" | awk '{print $NF}') +CURR_PATCH=$(grep -E "^#define LEVELX_PATCH_VERSION" "${API_HEADER}" | awk '{print $NF}') +CURR_BUILD=$(grep -E "^#define LEVELX_BUILD_VERSION" "${API_HEADER}" | awk '{print $NF}') +CURR_HOTFIX=$(grep -E "^#define LEVELX_HOTFIX_VERSION" "${API_HEADER}" | \ + sed -E "s/.*'([^']*)'.*/\1/" | tr -d ' ') + +if [ -z "${CURR_HOTFIX}" ]; then + CURR_VER="${CURR_MAJOR}.${CURR_MINOR}.${CURR_PATCH}.${CURR_BUILD}" +else + CURR_VER="${CURR_MAJOR}.${CURR_MINOR}.${CURR_PATCH}.${CURR_BUILD}${CURR_HOTFIX}" +fi + +printf "\nLevelX release preparation\n" +printf " Repository : %s\n" "${REPO_ROOT}" +printf " Current version : %s\n" "${CURR_VER}" +printf " Target version : %s\n\n" "${VERSION}" +printf "Proceed with update? [y/N] " +read -r CONFIRM +case "${CONFIRM}" in + y|Y) ;; + *) + printf "Aborted.\n" + exit 0 + ;; +esac + +# -------------------------------------------------------------------------- +# Pre-flight checks +# -------------------------------------------------------------------------- +if ! git -C "${REPO_ROOT}" diff --quiet HEAD 2>/dev/null; then + printf "Error: Working tree has uncommitted changes. Commit or stash first.\n" >&2 + exit 1 +fi + +# -------------------------------------------------------------------------- +# Create feature branch from dev +# -------------------------------------------------------------------------- +BRANCH_NAME="release-${VERSION}-preparation" +printf "\nChecking out dev and pulling latest changes...\n" +git -C "${REPO_ROOT}" checkout dev +git -C "${REPO_ROOT}" pull origin dev +printf "Creating branch '%s'...\n" "${BRANCH_NAME}" +git -C "${REPO_ROOT}" checkout -b "${BRANCH_NAME}" + +# -------------------------------------------------------------------------- +# Update version constants +# -------------------------------------------------------------------------- +printf "\nUpdating version constants in %s...\n" "${API_HEADER}" + +sed -i -E "s|(#define LEVELX_MAJOR_VERSION[[:space:]]+)[0-9]+|\1${MAJOR}|" "${API_HEADER}" +sed -i -E "s|(#define LEVELX_MINOR_VERSION[[:space:]]+)[0-9]+|\1${MINOR}|" "${API_HEADER}" +sed -i -E "s|(#define LEVELX_PATCH_VERSION[[:space:]]+)[0-9]+|\1${PATCH}|" "${API_HEADER}" +sed -i -E "s|(#define LEVELX_BUILD_VERSION[[:space:]]+)[0-9]+|\1${BUILD}|" "${API_HEADER}" +sed -i -E "s|(#define LEVELX_HOTFIX_VERSION[[:space:]]+)'[^']*'|\1${HOTFIX_DEFINE}|" "${API_HEADER}" + +git -C "${REPO_ROOT}" add "${API_HEADER}" +git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF' +Updated version number constants + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> +COMMIT_EOF + +printf "Committed version constant updates.\n" + +# -------------------------------------------------------------------------- +# Port version strings +# -------------------------------------------------------------------------- +printf "\nNo port-specific version strings in this repository. Skipping port commit.\n" + +printf "\nRelease preparation complete.\n" +printf "Branch '%s' is ready for review.\n" "${BRANCH_NAME}" From b9f54b87bb6c4e8f5409f97d6dd2c843ea096dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 4 Jun 2026 17:12:31 -0400 Subject: [PATCH 3/4] Updated version number constants (#74) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- common/inc/lx_api.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/inc/lx_api.h b/common/inc/lx_api.h index 308cafa..4d5cf11 100644 --- a/common/inc/lx_api.h +++ b/common/inc/lx_api.h @@ -149,8 +149,8 @@ typedef unsigned long long ULONG64; #define AZURE_RTOS_LEVELX #define LEVELX_MAJOR_VERSION 6 #define LEVELX_MINOR_VERSION 5 -#define LEVELX_PATCH_VERSION 0 -#define LEVELX_BUILD_VERSION 202601 +#define LEVELX_PATCH_VERSION 1 +#define LEVELX_BUILD_VERSION 202602 #define LEVELX_HOTFIX_VERSION ' ' From 123218cc51a55adc9b0c3a57e862206a6b8b4432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Sat, 6 Jun 2026 21:42:47 +0200 Subject: [PATCH 4/4] Added copyright headers to files missing them Applied the standard MIT license header to all project-owned C, header, assembly, shell, and Python files that were missing a copyright notice. Third-party, toolchain startup, and auto-generated files were excluded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- samples/demo_filex_nand_flash.c | 11 +++++++++++ samples/demo_filex_nor_flash.c | 11 +++++++++++ scripts/build.sh | 11 +++++++++++ scripts/install.sh | 11 +++++++++++ scripts/test.sh | 11 +++++++++++ test/cmake/coverage.sh | 11 +++++++++++ test/cmake/run.sh | 11 +++++++++++ test/regression/levelx_nand_flash_test.c | 11 +++++++++++ test/regression/levelx_nor_flash_test.c | 11 +++++++++++ test/regression/levelx_nor_flash_test_cache.c | 11 +++++++++++ 10 files changed, 110 insertions(+) diff --git a/samples/demo_filex_nand_flash.c b/samples/demo_filex_nand_flash.c index 685187b..67f25d4 100644 --- a/samples/demo_filex_nand_flash.c +++ b/samples/demo_filex_nand_flash.c @@ -1,3 +1,14 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + /* This is a small demo of the high-performance FileX FAT file system with LevelX and the NAND simulated driver. */ diff --git a/samples/demo_filex_nor_flash.c b/samples/demo_filex_nor_flash.c index 31ab631..9bf2838 100644 --- a/samples/demo_filex_nor_flash.c +++ b/samples/demo_filex_nor_flash.c @@ -1,3 +1,14 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + /* This is a small demo of the high-performance FileX FAT file system with LevelX and the NOR simulated driver. */ diff --git a/scripts/build.sh b/scripts/build.sh index 94d702b..0ec3d05 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,2 +1,13 @@ #!/bin/bash +############################################################################## +# Copyright (c) 2024 Microsoft Corporation +# 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. +# +# SPDX-License-Identifier: MIT +############################################################################## + $(dirname `realpath $0`)/../test/cmake/run.sh build all \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index d38a477..bcf4a0c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,4 +1,15 @@ #!/bin/bash +############################################################################## +# Copyright (c) 2024 Microsoft Corporation +# 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. +# +# SPDX-License-Identifier: MIT +############################################################################## + # # Remove large folder diff --git a/scripts/test.sh b/scripts/test.sh index af8d0df..1c4be22 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,2 +1,13 @@ #!/bin/bash +############################################################################## +# Copyright (c) 2024 Microsoft Corporation +# 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. +# +# SPDX-License-Identifier: MIT +############################################################################## + $(dirname `realpath $0`)/../test/cmake/run.sh test all \ No newline at end of file diff --git a/test/cmake/coverage.sh b/test/cmake/coverage.sh index 394d732..817b62f 100755 --- a/test/cmake/coverage.sh +++ b/test/cmake/coverage.sh @@ -1,4 +1,15 @@ #!/bin/bash +############################################################################## +# Copyright (c) 2024 Microsoft Corporation +# 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. +# +# SPDX-License-Identifier: MIT +############################################################################## + set -e diff --git a/test/cmake/run.sh b/test/cmake/run.sh index 7feb463..3f8159d 100755 --- a/test/cmake/run.sh +++ b/test/cmake/run.sh @@ -1,4 +1,15 @@ #!/bin/bash +############################################################################## +# Copyright (c) 2024 Microsoft Corporation +# 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. +# +# SPDX-License-Identifier: MIT +############################################################################## + cd $(dirname $0) diff --git a/test/regression/levelx_nand_flash_test.c b/test/regression/levelx_nand_flash_test.c index e7dcc86..d9c1e4c 100644 --- a/test/regression/levelx_nand_flash_test.c +++ b/test/regression/levelx_nand_flash_test.c @@ -1,3 +1,14 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + /* Basic NOR flash tests... */ #include diff --git a/test/regression/levelx_nor_flash_test.c b/test/regression/levelx_nor_flash_test.c index 143371a..6ad2e95 100644 --- a/test/regression/levelx_nor_flash_test.c +++ b/test/regression/levelx_nor_flash_test.c @@ -1,3 +1,14 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + /* Basic NOR flash tests... */ #include diff --git a/test/regression/levelx_nor_flash_test_cache.c b/test/regression/levelx_nor_flash_test_cache.c index fb65102..d0b24d0 100644 --- a/test/regression/levelx_nor_flash_test_cache.c +++ b/test/regression/levelx_nor_flash_test_cache.c @@ -1,3 +1,14 @@ +/***************************************************************************/ +/* Copyright (c) 2024 Microsoft Corporation */ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + /* Basic NOR flash tests... */ #include