/*************************************************************************** * 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 */