/* * 模块名称:NAND FTL 适配层(dhara + FatFS diskio 胶水) * 模块功能:实现 dhara NAND HAL 和 FatFS 磁盘 I/O,连接 GD5F2GQ5UE 底层驱动 * 适用平台:STM32F407ZGT6 * 作者:王建锋 * 创建日期:2026-07-20 * 修改记录: * 2026-07-20 王建锋 创建初始版本 */ /* 头文件包含区 */ #include #include "gd5f2gq5ue.h" #include "ff.h" #include "diskio.h" #include "nand.h" #include "map.h" /* FTL 分区起始地址(块 0,偏移 0,占用全部 256MB) */ #define FTL_FATFS_OFFSET 0U /* 调试输出配置 */ #define DBG_TAG "[NAND_FTL]" #include "dbg_log.h" /* 私有宏定义区 */ #define FTL_PAGE_SIZE GD5F_PAGE_SIZE #define FTL_SECTORS_PER_PAGE (FTL_PAGE_SIZE / 512) #define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE) #define FTL_NUM_BLOCKS (GD5F_TOTAL_BLOCKS - FTL_START_BLOCK) #define FTL_START_PAGE (FTL_START_BLOCK * GD5F_PAGES_PER_BLOCK) /* dhara 逻辑地址到物理地址转换 */ static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLOCK; } static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; } /* ======================== 私有变量 ======================== */ /* dhara 核心数据结构 */ static struct dhara_nand s_nand; static struct dhara_map s_map; static uint8_t s_page_buf[FTL_PAGE_SIZE]; /* dhara 内部页缓冲区 */ /* 页面缓存(FatFS 读写缓存) */ static uint8_t s_cache_buf[FTL_PAGE_SIZE]; static dhara_sector_t s_cached_lpn; static uint8_t s_cache_dirty; /* 拷贝临时缓冲区 */ static uint8_t s_copy_buf[FTL_PAGE_SIZE]; /* 初始化状态 */ static uint8_t s_initialized; /* resume 搜索探针日志窗口(诊断:逐探测记录判定输入) */ static uint8_t s_probe_log; /* L0 卷健康探针窗口(诊断:记录 FAT 读的 LPN 绑定 → 物理页路径) */ static uint8_t s_tree_probe_log; /* 写路径全量追踪窗口(诊断:mkfs 期间每个 erase/prog 逐条记录) */ static uint8_t s_prog_log; /* * 函数功能:打开/关闭写路径全量追踪窗口(erase/prog 逐条日志) * 入口参数:on - 1 打开,0 关闭 * 返回值:无 * 限定条件:无 * 函数说明:定位"绑定页内容被谁改写/擦除":记录每次 erase 的目标块与 * 每次 prog 的目标页+数据前 4 字节+校验结果。 */ void nand_ftl_set_prog_log(uint8_t on) { s_prog_log = on; } /* * 函数功能:打开/关闭 L0 卷健康探针日志窗口 * 入口参数:on - 1 打开,0 关闭 * 返回值:无 * 限定条件:FTL 已初始化 * 函数说明:窗口内的 dhara_nand_read/is_free/is_bad 调用逐条记录 * 物理页(= journal LPN),用于判定 map 绑定指向 */ void nand_ftl_set_tree_probe(uint8_t on) { s_tree_probe_log = on; } /* ======================== 私有函数声明 ======================== */ static int ftl_flush_cache(void); static int ftl_read_page(dhara_sector_t lpn); static void ftl_scan_checks_raw(void); /* SPI 原语从 gd5f2gq5ue.h 获取 */ /* ======================== 页面缓存管理 ======================== */ static int ftl_flush_cache(void) { if (!s_cache_dirty) { return GD5F_OK; } dhara_error_t err; if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) { DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err); return GD5F_ERROR; } s_cache_dirty = 0; return GD5F_OK; } static int ftl_read_page(dhara_sector_t lpn) { dhara_error_t err; if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) { DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err); return GD5F_ERROR; } s_cached_lpn = lpn; s_cache_dirty = 0; if (s_tree_probe_log) { /* 内容指纹:前 8 字节(BPB=33 52 / FAT32=F8 FF.. / 擦除=FF FF..) * 半新半旧混合页在此直接现形 */ DBG_INFO("ftl t %lu read LPN=%lu: %02X %02X %02X %02X %02X %02X %02X %02X", (unsigned long)HAL_GetTick(), (unsigned long)lpn, (unsigned int)s_cache_buf[0], (unsigned int)s_cache_buf[1], (unsigned int)s_cache_buf[2], (unsigned int)s_cache_buf[3], (unsigned int)s_cache_buf[4], (unsigned int)s_cache_buf[5], (unsigned int)s_cache_buf[6], (unsigned int)s_cache_buf[7]); } return GD5F_OK; } /* ======================== dhara NAND HAL ======================== */ int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) { (void)n; const int bad = gd5f2gq5ue_is_block_bad(ftl_phy_block(b)); if (s_probe_log || s_tree_probe_log) { DBG_INFO("probe blk %lu: bbt_bad=%u", (unsigned long)b, (unsigned int)bad); } return bad; } void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) { (void)n; gd5f2gq5ue_mark_block_bad(ftl_phy_block(b)); } int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) { (void)n; int ret = gd5f_block_erase(ftl_phy_block(b)); if (s_prog_log) { DBG_INFO("eraselog blk %lu ret %d", (unsigned long)ftl_phy_block(b), ret); } if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) { dhara_nand_mark_bad(n, b); dhara_set_error(err, DHARA_E_BAD_BLOCK); return -1; } return 0; } int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) { int ret; uint32_t phy_pg = ftl_phy_page(p); if (s_probe_log || s_tree_probe_log) { DBG_INFO("probe t %lu prog pg %lu blk %lu g%lu", (unsigned long)HAL_GetTick(), (unsigned long)phy_pg, (unsigned long)(p >> 6), (unsigned long)((p >> 4) & 3u)); } gd5f_write_enable(); ret = gd5f_program_load(0, data, FTL_PAGE_SIZE); if (ret != GD5F_OK) { dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_BAD_BLOCK); return -1; } ret = gd5f_program_exec(phy_pg); if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) { if (s_prog_log) { DBG_INFO("proglog pg %lu exec ret %d", (unsigned long)phy_pg, ret); } dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_BAD_BLOCK); return -1; } /* 编程回读校验(2026-09-20 台架实证):供电低于标称(3.1V)时 * program 可"状态成功"但 cell 未可靠 latch(ECC 不可纠或数据错位)—— * 状态寄存器发现不了,下次复位 journal resume 因 ECC 门拒绝这些 * checkpoint 页 → 新写入整体静默丢失(重格式化循环的真根因)。 * 回读比对+ECC 检查,失败则标记坏块由 dhara 迁移,宁可丢块不丢卷。 */ if (gd5f_page_read(phy_pg) != GD5F_OK) { dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_ECC); DBG_ERROR("program verify: readback fail, blk %lu", (unsigned long)(p >> n->log2_ppb)); return -1; } if (gd5f_check_ecc() != GD5F_OK) { dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_ECC); DBG_ERROR("program verify: ecc uncorrectable, blk %lu", (unsigned long)(p >> n->log2_ppb)); return -1; } gd5f_read_from_cache(0, s_copy_buf, FTL_PAGE_SIZE); if (memcmp(s_copy_buf, data, FTL_PAGE_SIZE) != 0) { if (s_prog_log) { DBG_INFO("proglog pg %lu verify mismatch d=%02X%02X%02X%02X r=%02X%02X%02X%02X", (unsigned long)phy_pg, (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3], (unsigned int)s_copy_buf[0], (unsigned int)s_copy_buf[1], (unsigned int)s_copy_buf[2], (unsigned int)s_copy_buf[3]); } dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_BAD_BLOCK); DBG_ERROR("program verify: data mismatch, blk %lu", (unsigned long)(p >> n->log2_ppb)); return -1; } if (s_prog_log) { DBG_INFO("proglog pg %lu ok d=%02X%02X%02X%02X", (unsigned long)phy_pg, (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3]); } return 0; } int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) { (void)n; uint32_t phy_pg = ftl_phy_page(p); int fre; if (gd5f_page_read(phy_pg) != GD5F_OK) { fre = 0; } else { uint8_t buf[64]; uint32_t i; gd5f_read_from_cache(0, buf, sizeof(buf)); fre = 1; for (i = 0; i < sizeof(buf); i++) { if (buf[i] != 0xFF) { fre = 0; break; } } } if (s_probe_log || s_tree_probe_log) { DBG_INFO("probe t %lu isfree pg %lu = %u", (unsigned long)HAL_GetTick(), (unsigned long)phy_pg, (unsigned int)fre); } return fre; } int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) { (void)n; uint32_t phy_pg = ftl_phy_page(p); int rret; int eret; if ((rret = gd5f_page_read(phy_pg)) != GD5F_OK) { if (s_probe_log || s_tree_probe_log) { DBG_INFO("probe pg %lu blk %lu: page_read=%d", (unsigned long)phy_pg, (unsigned long)(p >> 6), rret); } dhara_set_error(err, DHARA_E_ECC); return -1; } if ((eret = gd5f_check_ecc()) != GD5F_OK) { if (s_probe_log || s_tree_probe_log) { DBG_INFO("probe pg %lu blk %lu: ecc=%d", (unsigned long)phy_pg, (unsigned long)(p >> 6), eret); } dhara_set_error(err, DHARA_E_ECC); return -1; } gd5f_read_from_cache((uint16_t)offset, data, length); if ((s_probe_log || s_tree_probe_log) && offset == 0 && length == FTL_PAGE_SIZE) { /* 组末页(组内偏移 15:页号低 4 位 = 15)= checkpoint 页: * 记录 header 前 4 字节 + meta 区 4 字节(偏移 944 = 槽 7, * 树根 meta)+ 时间戳。header 新/meta 旧 = 半新半旧混合页 * (低 VDD 就绪信号提前竞态的直接证据) */ if ((p & 15u) == 15u) { const uint8_t *m = data; DBG_INFO("probe t %lu pg %lu blk %lu g%lu: %02X %02X %02X %02X" " meta %02X %02X %02X %02X", (unsigned long)HAL_GetTick(), (unsigned long)phy_pg, (unsigned long)(p >> 6), (unsigned long)((p >> 4) & 3u), (unsigned int)m[0], (unsigned int)m[1], (unsigned int)m[2], (unsigned int)m[3], (unsigned int)m[944], (unsigned int)m[945], (unsigned int)m[946], (unsigned int)m[947]); } } return 0; } int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) { (void)n; if (dhara_nand_read(n, src, 0, FTL_PAGE_SIZE, s_copy_buf, err) < 0) { return -1; } if (dhara_nand_prog(n, dst, s_copy_buf, err) < 0) { return -1; } return 0; } /* ======================== FatFS 磁盘 I/O ======================== */ DSTATUS disk_initialize(BYTE pdrv) { if (pdrv != 0) { return STA_NOINIT; } if (s_initialized) { return 0; } DBG_INFO("Initializing NAND FTL..."); s_nand.log2_page_size = 11; s_nand.log2_ppb = 6; /* dhara 可见块数 = 总块数 - 保留块池(BBT 存储区不交给 dhara,避免被擦写) */ s_nand.num_blocks = gd5f_get_usable_blocks(); dhara_map_init(&s_map, &s_nand, s_page_buf, 4); /* 诊断:resume 前的裸 NAND checkpoint 扫描(复位后地面真值, * 不经 FTL 缓存)——判定上一开机的 checkpoint 页是否真正写入芯片 */ DBG_INFO("Pre-resume raw check scan start: blocks=%lu", (unsigned long)s_nand.num_blocks); ftl_scan_checks_raw(); memset(s_cache_buf, 0, FTL_PAGE_SIZE); s_cached_lpn = (dhara_sector_t)-1; /* -1 表示缓存未加载;LPN 0 合法,不能用 0 作哨兵 */ s_cache_dirty = 0; { dhara_error_t err; s_probe_log = 1; if (dhara_map_resume(&s_map, &err) < 0) { DBG_INFO("No valid map found, creating fresh (err=%d)", err); dhara_map_clear(&s_map); } else { DBG_INFO("Map resumed: %lu/%lu sectors used", dhara_map_size(&s_map), dhara_map_capacity(&s_map)); } s_probe_log = 0; } s_initialized = 1; DBG_INFO("NAND FTL ready (capacity: %lu pages = %lu MB)", dhara_map_capacity(&s_map), (dhara_map_capacity(&s_map) * FTL_PAGE_SIZE) / (1024 * 1024)); return 0; } int nand_ftl_init(void) { DSTATUS sta = disk_initialize(0); return (sta == 0) ? 0 : -1; } void nand_ftl_deinit(void) { /* 复位 FTL 状态,使下次 nand_ftl_init() 真正重新走 dhara_map_resume, 用于测试"掉电恢复"与"坏块注入后重建"场景(模拟设备重启) */ s_initialized = 0; s_cached_lpn = (dhara_sector_t)-1; s_cache_dirty = 0; } DSTATUS disk_status(BYTE pdrv) { if (pdrv != 0) { return STA_NOINIT; } if (!s_initialized) { return STA_NOINIT; } return 0; } DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { if (pdrv != 0 || !buff) { return RES_PARERR; } if (!s_initialized && disk_initialize(pdrv) != 0) { return RES_NOTRDY; } while (count > 0) { dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE); uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512; uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE); if (batch > count) { batch = count; } if (lpn != s_cached_lpn) { if (ftl_flush_cache() != GD5F_OK) { return RES_ERROR; } if (ftl_read_page(lpn) != GD5F_OK) { return RES_ERROR; } } memcpy(buff, s_cache_buf + offset, batch * 512); buff += batch * 512; sector += batch; count -= (UINT)batch; } return RES_OK; } DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { if (pdrv != 0 || !buff) { return RES_PARERR; } if (!s_initialized && disk_initialize(pdrv) != 0) { return RES_NOTRDY; } while (count > 0) { dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE); uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512; uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE); if (batch > count) { batch = count; } if (lpn != s_cached_lpn) { if (ftl_flush_cache() != GD5F_OK) { return RES_ERROR; } if (ftl_read_page(lpn) != GD5F_OK) { return RES_ERROR; } } memcpy(s_cache_buf + offset, buff, batch * 512); s_cache_dirty = 1; if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) { if (ftl_flush_cache() != GD5F_OK) { return RES_ERROR; } } buff += batch * 512; sector += batch; count -= (UINT)batch; } return RES_OK; } DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) { if (pdrv != 0) { return RES_PARERR; } if (!s_initialized) { return RES_NOTRDY; } switch (cmd) { case CTRL_SYNC: if (ftl_flush_cache() != GD5F_OK) { return RES_ERROR; } { dhara_error_t err; if (dhara_map_sync(&s_map, &err) < 0) { DBG_ERROR("Map sync failed: %d", err); return RES_ERROR; } } return RES_OK; case GET_SECTOR_COUNT: { LBA_t *p_sectors = (LBA_t *)buff; dhara_sector_t pages = dhara_map_capacity(&s_map); *p_sectors = pages * FTL_SECTORS_PER_PAGE; return RES_OK; } case GET_SECTOR_SIZE: *(WORD *)buff = 512; return RES_OK; case GET_BLOCK_SIZE: *(DWORD *)buff = 1; return RES_OK; default: return RES_PARERR; } } /* ======================== 公共函数定义 ======================== */ int nand_ftl_format(void) { dhara_error_t err; if (!s_initialized) { if (disk_initialize(0) != 0) { return GD5F_ERROR; } } if (ftl_flush_cache() != GD5F_OK) { return GD5F_ERROR; } dhara_map_clear(&s_map); if (dhara_map_sync(&s_map, &err) < 0) { DBG_ERROR("Map sync after clear failed: %d", err); return GD5F_ERROR; } s_cache_dirty = 0; s_cached_lpn = (dhara_sector_t)-1; DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map)); return GD5F_OK; } /* ======================== 诊断(跨复位日志持久性分析) ======================== */ /* 打印当前 journal/map 状态(epoch/head/tail/root/容量) */ void nand_ftl_log_state(const char *tag) { const struct dhara_journal *j = &s_map.journal; if (!s_initialized) { return; } DBG_INFO("FTL state %s: epoch=%u head=%lu tail=%lu root=%lu jsize=%lu map=%lu", tag, (unsigned int)j->epoch, (unsigned long)j->head, (unsigned long)j->tail, (unsigned long)j->root, (unsigned long)dhara_journal_size(j), (unsigned long)dhara_map_size(&s_map)); } /* 打印某块 checkpoint 页的 16 字节 header(magic/epoch/tail/bb), * 用于区分"未落盘(全FF)"/"编程失败(花)"/"完整" 三种状态 */ static void ftl_dump_checkheader(uint32_t blk) { uint8_t hdr[16]; const uint32_t pg = ftl_phy_page((blk << s_nand.log2_ppb) | ((1u << s_map.journal.log2_ppc) - 1u)); if (gd5f_page_read(pg) != GD5F_OK) { DBG_INFO("check hdr blk %lu: read fail", (unsigned long)blk); return; } gd5f_read_from_cache(0, hdr, sizeof(hdr)); DBG_INFO("check hdr blk %lu: %02X %02X %02X %02X %02X %02X %02X %02X " "%02X %02X %02X %02X %02X %02X %02X %02X", (unsigned long)blk, (unsigned int)hdr[0], (unsigned int)hdr[1], (unsigned int)hdr[2], (unsigned int)hdr[3], (unsigned int)hdr[4], (unsigned int)hdr[5], (unsigned int)hdr[6], (unsigned int)hdr[7], (unsigned int)hdr[8], (unsigned int)hdr[9], (unsigned int)hdr[10], (unsigned int)hdr[11], (unsigned int)hdr[12], (unsigned int)hdr[13], (unsigned int)hdr[14], (unsigned int)hdr[15]); } /* 全扫每块首组 checkpoint 页(magic+epoch),打印连续区段分布, * 用于识别 journal 环上的 epoch 分界与空洞。 * 裸读(gd5f 页读),不受 FTL 缓存影响 = NAND 真实内容。 */ static void ftl_scan_checks_raw(void) { uint32_t i; uint8_t buf[8]; uint32_t total = 0; uint32_t run_start = 0; uint32_t run_prev = 0; uint32_t run_len = 0; uint32_t run_ecc_fail = 0; uint32_t run_ecc_first = 0; uint32_t run_ecc_last = 0; uint32_t run_bbt_bad = 0; uint32_t run_bbt_first = 0; uint32_t run_bbt_last = 0; uint8_t run_epoch = 0; uint8_t run_open = 0; for (i = 0; i < s_nand.num_blocks; i++) { const uint32_t pg = ftl_phy_page((i << s_nand.log2_ppb) | ((1u << s_map.journal.log2_ppc) - 1u)); if (gd5f_page_read(pg) != GD5F_OK) { continue; } /* ECC 状态在 0Bh 之前检查(与 dhara_nand_read 的读取顺序一致, * 排除 read-from-cache 对状态的干扰) */ int ecc = gd5f_check_ecc(); gd5f_read_from_cache(0, buf, sizeof(buf)); if (buf[0] == 'D' && buf[1] == 'h' && buf[2] == 'a') { const uint8_t ep = buf[3]; total++; if (ecc != GD5F_OK) { run_ecc_fail++; if (!run_ecc_fail) { run_ecc_first = i; } run_ecc_last = i; } if (gd5f2gq5ue_is_block_bad(ftl_phy_block(i))) { run_bbt_bad++; if (!run_bbt_bad) { run_bbt_first = i; } run_bbt_last = i; } if (!run_open || (i != run_prev + 1) || (ep != run_epoch)) { if (run_open) { DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)", (unsigned long)run_start, (unsigned long)run_prev, (unsigned int)run_epoch, (unsigned long)run_len); if (run_ecc_fail) { DBG_INFO(" run ecc-fail: %lu blocks (blk %lu..%lu)", (unsigned long)run_ecc_fail, (unsigned long)run_ecc_first, (unsigned long)run_ecc_last); } if (run_bbt_bad) { DBG_INFO(" run bbt-bad: %lu blocks (blk %lu..%lu)", (unsigned long)run_bbt_bad, (unsigned long)run_bbt_first, (unsigned long)run_bbt_last); } ftl_dump_checkheader(run_start); } run_start = i; run_epoch = ep; run_len = 0; run_ecc_fail = 0; run_bbt_bad = 0; run_open = 1; } run_prev = i; run_len++; } } if (run_open) { DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)", (unsigned long)run_start, (unsigned long)run_prev, (unsigned int)run_epoch, (unsigned long)run_len); if (run_ecc_fail) { DBG_INFO(" run ecc-fail: %lu blocks (blk %lu..%lu)", (unsigned long)run_ecc_fail, (unsigned long)run_ecc_first, (unsigned long)run_ecc_last); } if (run_bbt_bad) { DBG_INFO(" run bbt-bad: %lu blocks (blk %lu..%lu)", (unsigned long)run_bbt_bad, (unsigned long)run_bbt_first, (unsigned long)run_bbt_last); } ftl_dump_checkheader(run_start); } DBG_INFO("check scan done: %lu checkpoint blocks", (unsigned long)total); } void nand_ftl_scan_checks(void) { if (!s_initialized) { return; } DBG_INFO("check scan start: blocks=%lu", (unsigned long)s_nand.num_blocks); ftl_scan_checks_raw(); } /* ============ 裸片读写擦自检(2026-09-20 诊断:驱动层 / FTL 层 / 电源定界) ============ */ /* * 测试块选择:2040/2041(芯片顶部区,远离 journal 环头,数千次开机内 * 不会被环覆盖;不在 BBT 保留池 2044..2047)。使用块内页 1..4, * 避开页 0(出厂坏块标记位 page0 spare[0],防 BBT 扫描误判)。 */ #define RAW_T_BLK_A 2040u /* 数据块:先回读上一boot图案(持久性),再擦除写新图案 */ #define RAW_T_BLK_B 2041u /* 纯擦块:每次擦除后校验全 FF */ #define RAW_T_PAGE0 1u /* 块内起始使用页(跳过页 0) */ #define RAW_T_NPG 4u /* 测试页数 */ /* * 函数功能:计算测试页第 i 字节的期望值(seq+page 的确定性函数) * 入口参数:seq - 启动序号 uint32_t * page - 逻辑页号 0..3 uint32_t * i - 页内偏移 uint32_t * 返回值:期望字节值 uint8_t * 限定条件:无 * 函数说明:页头 10 字节 = magic "RAW1" + seq(LE32) + page(LE16), * 其余 2038 字节 = (seq*31 + page*7 + i) 的伪随机填充。 */ static uint8_t raw_t_byte(uint32_t seq, uint32_t page, uint32_t i) { switch (i) { case 0u: return (uint8_t)'R'; case 1u: return (uint8_t)'A'; case 2u: return (uint8_t)'W'; case 3u: return (uint8_t)'1'; case 4u: return (uint8_t)(seq & 0xFFu); case 5u: return (uint8_t)((seq >> 8) & 0xFFu); case 6u: return (uint8_t)((seq >> 16) & 0xFFu); case 7u: return (uint8_t)((seq >> 24) & 0xFFu); case 8u: return (uint8_t)(page & 0xFFu); case 9u: return (uint8_t)((page >> 8) & 0xFFu); default: return (uint8_t)(seq * 31u + page * 7u + i); } } /* * 函数功能:填充测试图案到缓冲区 * 入口参数:p_buf - 目标缓冲区 uint8_t* 长度 FTL_PAGE_SIZE * seq - 启动序号 uint32_t * page - 逻辑页号 0..3 uint32_t * 返回值:无 * 限定条件:无 * 函数说明:图案内容见 raw_t_byte。 */ static void raw_t_fill(uint8_t *p_buf, uint32_t seq, uint32_t page) { uint32_t i; for (i = 0; i < FTL_PAGE_SIZE; i++) { p_buf[i] = raw_t_byte(seq, page, i); } } /* * 函数功能:缓冲区与期望图案逐字节比对 * 入口参数:p_buf - 待比对缓冲 const uint8_t* 长度 FTL_PAGE_SIZE * seq - 期望启动序号 uint32_t * page - 期望逻辑页号 uint32_t * p_off - 差异偏移输出 uint32_t*(仅失配时有效) * 返回值:0 - 全部一致,-1 - 存在差异 * 限定条件:无 * 函数说明:期望内容见 raw_t_byte。 */ static int raw_t_cmp(const uint8_t *p_buf, uint32_t seq, uint32_t page, uint32_t *p_off) { uint32_t i; for (i = 0; i < FTL_PAGE_SIZE; i++) { if (p_buf[i] != raw_t_byte(seq, page, i)) { *p_off = i; return -1; } } return 0; } /* * 函数功能:读一个物理整页到 s_copy_buf * 入口参数:page_addr - 物理页地址 uint32_t * 返回值:0 - 成功,其它 - 错误码 * 限定条件:ECC 已使能;s_copy_buf 空闲 * 函数说明:13h 装载(驱动内含二次发令+沉降兜底)→ 0Bh 整页流出。 */ static int raw_t_read(uint32_t page_addr) { int ret = gd5f_page_read(page_addr); if (ret != GD5F_OK) { return ret; } return gd5f_read_from_cache(0, s_copy_buf, FTL_PAGE_SIZE); } /* * 函数功能:裸 NAND 读 / 擦 / 写 / 掉电持久性自检(诊断用) * 入口参数:无 * 返回值:无 * 限定条件:gd5f2gq5ue_init() 成功后、FTL 初始化前调用(内核前, * 无并发;s_copy_buf 此时未被 FTL 占用,复用安全) * 函数说明:每次开机顺序执行三步,三条日志合起来定界故障域: * 1) persist — 块 A 回读"上一 boot"写入的 seq 图案(擦除前!) * → 数据跨复位是否保得住; * 2) erase — 擦块 A/B(返回码含 E_FAIL)+ 读回全 FF 校验 * → 擦除是否真实落盘; * 3) write — 块 A 写新 seq 图案 + 立即回读逐字节比对(wv) * → 编程当下是否落盘。 * 判读:wv 全过而 persist 丢失 ⇒ 编程未写入非易失单元(掉电丢); * erase 返回 OK 而 ff 不满 ⇒ 擦除未落盘;wv 丢 ⇒ 驱动/命令通路问题。 */ void nand_ftl_raw_persist_test(void) { uint32_t k; uint32_t pg; uint32_t seq_prev = 0u; uint8_t magic_ok = 0u; uint32_t off = 0u; uint32_t seq_new; uint32_t wv = 0u; int erase_a; int erase_b; uint32_t ff_a = 0u; uint32_t ff_b = 0u; /* ---- 1) 持久性检查:回读上一 boot 写入的图案(必须先于擦除) ---- */ pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0; if (raw_t_read(pg) == GD5F_OK) { if (s_copy_buf[0] == (uint8_t)'R' && s_copy_buf[1] == (uint8_t)'A' && s_copy_buf[2] == (uint8_t)'W' && s_copy_buf[3] == (uint8_t)'1') { magic_ok = 1u; seq_prev = (uint32_t)s_copy_buf[4] | ((uint32_t)s_copy_buf[5] << 8) | ((uint32_t)s_copy_buf[6] << 16) | ((uint32_t)s_copy_buf[7] << 24); } } if (magic_ok != 0u) { uint32_t persist_ok = 0u; for (k = 0u; k < RAW_T_NPG; k++) { pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k; if (raw_t_read(pg) != GD5F_OK) { break; } if (raw_t_cmp(s_copy_buf, seq_prev, k, &off) != 0) { DBG_ERROR("[RAW] persist corrupt pg %lu off %lu exp %02X got %02X", (unsigned long)(RAW_T_PAGE0 + k), (unsigned long)off, (unsigned int)raw_t_byte(seq_prev, k, off), (unsigned int)s_copy_buf[off]); break; } persist_ok++; } DBG_INFO("[RAW] persist: seq=%lu ok=%u/%lu (prev-boot data after reset)", (unsigned long)seq_prev, (unsigned int)persist_ok, (unsigned long)RAW_T_NPG); } else { DBG_INFO("[RAW] persist: no magic (first boot or lost), pg1 head: " "%02X %02X %02X %02X %02X %02X %02X %02X", (unsigned int)s_copy_buf[0], (unsigned int)s_copy_buf[1], (unsigned int)s_copy_buf[2], (unsigned int)s_copy_buf[3], (unsigned int)s_copy_buf[4], (unsigned int)s_copy_buf[5], (unsigned int)s_copy_buf[6], (unsigned int)s_copy_buf[7]); } /* ---- 2) 擦除试验:擦 A/B 块 + 读回全 FF 校验 ---- */ erase_a = gd5f_block_erase(RAW_T_BLK_A); erase_b = gd5f_block_erase(RAW_T_BLK_B); for (k = 0u; k < RAW_T_NPG; k++) { uint32_t i; uint8_t all_ff = 1u; pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k; if (raw_t_read(pg) != GD5F_OK) { break; } for (i = 0; i < FTL_PAGE_SIZE; i++) { if (s_copy_buf[i] != 0xFFu) { all_ff = 0u; break; } } if (all_ff != 0u) { ff_a++; } } for (k = 0u; k < RAW_T_NPG; k++) { uint32_t i; uint8_t all_ff = 1u; pg = (RAW_T_BLK_B * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k; if (raw_t_read(pg) != GD5F_OK) { break; } for (i = 0; i < FTL_PAGE_SIZE; i++) { if (s_copy_buf[i] != 0xFFu) { all_ff = 0u; break; } } if (all_ff != 0u) { ff_b++; } } /* ---- 3) 编程试验:写新 seq 图案 + 立即回读比对 ---- */ seq_new = (magic_ok != 0u) ? (seq_prev + 1u) : 0u; for (k = 0u; k < RAW_T_NPG; k++) { int ret; pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k; raw_t_fill(s_copy_buf, seq_new, k); if (gd5f_write_enable() != GD5F_OK) { break; } ret = gd5f_program_load(0, s_copy_buf, FTL_PAGE_SIZE); if (ret != GD5F_OK) { break; } ret = gd5f_program_exec(pg); if (ret != GD5F_OK) { DBG_INFO("[RAW] prog exec pg %lu rc %d", (unsigned long)pg, ret); break; } if (raw_t_read(pg) != GD5F_OK) { break; } if (raw_t_cmp(s_copy_buf, seq_new, k, &off) == 0) { wv++; } else { DBG_ERROR("[RAW] write verify mismatch pg %lu off %lu exp %02X got %02X", (unsigned long)(RAW_T_PAGE0 + k), (unsigned long)off, (unsigned int)raw_t_byte(seq_new, k, off), (unsigned int)s_copy_buf[off]); } } /* ---- 4) 汇总(单行速读) ---- */ DBG_INFO("[RAW] erase A=%d B=%d; ffA=%u/%lu ffB=%u/%lu; write seq=%lu wv=%u/%lu", erase_a, erase_b, (unsigned int)ff_a, (unsigned long)RAW_T_NPG, (unsigned int)ff_b, (unsigned long)RAW_T_NPG, (unsigned long)seq_new, (unsigned int)wv, (unsigned long)RAW_T_NPG); } /* * 函数功能:转储某 LPN 的 map 绑定与绑定页裸读内容(诊断:跨 boot 对比 * 绑定地址是否分歧、绑定页内容是否丢失/被擦) * 入口参数:lpn - 逻辑页号(journal LPN = FatFs 页号)uint32_t * tag - 场景标记(如 "fresh" / "postreset")const char* * 返回值:无 * 限定条件:FTL 已初始化 * 函数说明:dhara_map_find 取当前树绑定 → 物理页号 → 绕开缓存的裸读 * 前 16 字节。两 boot 的同一 LPN:地址不同 = 树分歧;地址同 * 内容不同 = 数据页未落盘或被擦。 */ void nand_ftl_dump_binding(uint32_t lpn, const char *tag) { dhara_page_t loc = DHARA_PAGE_NONE; dhara_error_t err; uint8_t b[16]; uint32_t phy; if (!s_initialized) { return; } if (dhara_map_find(&s_map, lpn, &loc, &err) < 0) { DBG_INFO("bind %s LPN=%lu: not found (err %d)", tag, (unsigned long)lpn, (int)err); return; } phy = ftl_phy_page(loc); if (gd5f_page_read(phy) != GD5F_OK) { DBG_INFO("bind %s LPN=%lu -> jpage %lu: raw read err", tag, (unsigned long)lpn, (unsigned long)loc); return; } gd5f_read_from_cache(0, b, sizeof(b)); DBG_INFO("bind %s LPN=%lu -> jpage %lu (phy %lu): " "%02X %02X %02X %02X %02X %02X %02X %02X", tag, (unsigned long)lpn, (unsigned long)loc, (unsigned long)phy, (unsigned int)b[0], (unsigned int)b[1], (unsigned int)b[2], (unsigned int)b[3], (unsigned int)b[4], (unsigned int)b[5], (unsigned int)b[6], (unsigned int)b[7]); }