From d77747ec43f1f303baef63083a216197e6379e5e Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 20 Sep 2026 13:31:15 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4FTL=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=E8=AF=8A=E6=96=AD=E4=BB=A3=E7=A0=81=EF=BC=88?= =?UTF-8?q?raw=E6=8C=81=E4=B9=85=E6=80=A7=E6=B5=8B=E8=AF=95/bind=20dump/pr?= =?UTF-8?q?oglog/=E6=8E=A2=E9=92=88=E7=AA=97=E5=8F=A3/=E6=8C=87=E7=BA=B9/?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=EF=BC=89=E4=BF=9D=E7=95=99=E7=A1=AC=E5=8C=96?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/app_main.c | 5 - Drivers/BSP/GD5F2GQ5UE/nand_ftl.c | 586 +----------------------------- Drivers/BSP/GD5F2GQ5UE/nand_ftl.h | 8 - Src/freertos.c | 29 +- 4 files changed, 14 insertions(+), 614 deletions(-) diff --git a/App/app_main.c b/App/app_main.c index 029cb488..0d3adfd2 100644 --- a/App/app_main.c +++ b/App/app_main.c @@ -20,7 +20,6 @@ #include "sd2506.h" #include "sys_clock.h" #include "gd5f2gq5ue.h" -#include "nand_ftl.h" #include "tpafe5160.h" #include "sys_log.h" @@ -73,10 +72,6 @@ int app_main_init(void) { (ret == GD5F_ID_MISMATCH) ? "ID MISMATCH" : "UNKNOWN"); if (ret != GD5F_OK) { syslog_post(SYSLOG_SELFTEST, "NAND fault"); - } else { - /* 裸片读写擦持久性自检(2026-09-20 诊断,可整块移除): - * 定界"驱动层读写擦 / FTL 层 / 掉电持久性"三个故障域 */ - nand_ftl_raw_persist_test(); } /* 4) TPAFE5160 ADC 测试 */ diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c index f88c23ff..49e6bd23 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -52,42 +52,10 @@ 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 获取 */ @@ -118,16 +86,6 @@ static int ftl_read_page(dhara_sector_t lpn) { 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; } @@ -135,12 +93,7 @@ static int ftl_read_page(dhara_sector_t lpn) { 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; + return gd5f2gq5ue_is_block_bad(ftl_phy_block(b)); } void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) { @@ -151,10 +104,6 @@ void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t 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); @@ -167,12 +116,6 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d 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) { @@ -183,20 +126,18 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d 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 迁移,宁可丢块不丢卷。 */ + /* 编程回读校验(硬化措施,2026-09-20):拦截"状态寄存器报成功但 + * cell 未可靠 latch"的静默编程失败——此类页若流入 journal,复位后 + * resume 会因 ECC/魔数门整体拒收 checkpoint,造成新写入丢失。 + * 回读比对+ECC 检查,失败即标记坏块交由 dhara 迁移(宁可丢块)。 + * 注:本校验曾被认为是重格式化循环根因(低电压假设),后经验证 + * 该假设证伪——循环真根因是 FatFs 探针名非法(见 freertos.c 探针 + * 注释与 Trap Records);本校验作为数据完整性硬化长期保留。 */ if (gd5f_page_read(phy_pg) != GD5F_OK) { dhara_nand_mark_bad(n, p >> n->log2_ppb); dhara_set_error(err, DHARA_E_ECC); @@ -215,14 +156,6 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d 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", @@ -230,13 +163,6 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d 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; } @@ -260,61 +186,24 @@ int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) { } } } - 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); - } + if (gd5f_page_read(phy_pg) != GD5F_OK) { 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); - } + if (gd5f_check_ecc() != GD5F_OK) { 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; } @@ -352,19 +241,12 @@ DSTATUS disk_initialize(BYTE pdrv) { 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); @@ -372,7 +254,6 @@ DSTATUS disk_initialize(BYTE pdrv) { 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; @@ -556,450 +437,3 @@ int nand_ftl_format(void) { 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]); -} diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h index 33d0446a..183c07ae 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h @@ -11,14 +11,6 @@ int nand_ftl_init(void); int nand_ftl_format(void); void nand_ftl_deinit(void); -/* 诊断接口:打印 journal 状态 / 全扫 checkpoint 分布(跨复位持久性分析) */ -void nand_ftl_log_state(const char *tag); -void nand_ftl_scan_checks(void); -void nand_ftl_set_tree_probe(uint8_t on); -void nand_ftl_raw_persist_test(void); -void nand_ftl_dump_binding(uint32_t lpn, const char *tag); -void nand_ftl_set_prog_log(uint8_t on); - #ifdef __cplusplus } #endif diff --git a/Src/freertos.c b/Src/freertos.c index 69e995a6..8888b4ab 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -226,16 +226,14 @@ void StartDefaultTask(void *argument) * (/config.ini 丢失 → 配置引擎首启路径回落工厂默认并自动重建模板)—— * 仅当卷已不可用时触发,可用设备 > 一份配置文件。 */ static FATFS default_fatfs; - nand_ftl_set_tree_probe(1); /* 诊断窗口:挂载+探针的读路径全记录 */ FRESULT fr = f_mount(&default_fatfs, "", 1); if (fr == FR_NO_FILESYSTEM) { - nand_ftl_set_tree_probe(0); /* mkfs 路径不追踪(I/O 洪泛) */ DBG_INFO("[FS] no FAT volume, formatting NAND..."); static uint8_t fs_work[FF_MAX_SS]; MKFS_PARM opts = { FM_FAT32, 0, 0, 0, 0 }; - /* 格式化前清空 FTL map 树(2026-09-20 台架实证:不清则 mkfs 经旧树 - * 写新卷 → on-disk 树残留旧节点/旧绑定 → 复位后 resume 混用新旧 → - * FAT 探针失败 → 无限重格式化) */ + /* 格式化前清空 FTL map 树:新卷从空树开始写,journal + * 不与旧卷残留共存(结构卫生)。注:这不是"无限重格式化"的根因—— + * 真根因是 FatFs 探针文件名非法(见下方探针注释)。 */ (void)nand_ftl_format(); fr = f_mkfs("", &opts, fs_work, sizeof(fs_work)); if (fr == FR_OK) { @@ -271,19 +269,10 @@ void StartDefaultTask(void *argument) } \ } while (0) FRESULT hfr; - nand_ftl_set_tree_probe(1); FS_VOL_PROBE(hfr); if (hfr != FR_OK) { FS_VOL_PROBE(hfr); /* 两击确认 */ } - nand_ftl_set_tree_probe(0); - if (hfr != FR_OK) { - nand_ftl_scan_checks(); /* 诊断:环上 checkpoint 的 epoch 分布 */ - /* 诊断:本 boot(复位后)树绑定 + 绑定页裸读内容 */ - nand_ftl_dump_binding(0, "postreset"); - nand_ftl_dump_binding(15, "postreset"); - nand_ftl_dump_binding(16, "postreset"); - } if (hfr != FR_OK) { /* ⚠ FR_INVALID_NAME 绝不可入本集合:它是路径名语法错误(纯 RAM 判定、 * 不碰盘),与卷健康无关(曾误入 → "无限重格式化"事故) */ @@ -294,8 +283,7 @@ void StartDefaultTask(void *argument) DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...", (int)hfr); f_mount(NULL, "", 0); - nand_ftl_set_prog_log(1); - /* 格式化前清空 FTL map 树(同首启路径:防新旧树混合) */ + /* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */ (void)nand_ftl_format(); { static uint8_t fs_work2[FF_MAX_SS]; @@ -305,16 +293,8 @@ void StartDefaultTask(void *argument) fr = f_mount(&default_fatfs, "", 1); } } - nand_ftl_set_prog_log(0); - nand_ftl_log_state("[after-mkfs]"); - nand_ftl_log_state("[after-mkfs]"); if (fr == FR_OK) { DBG_INFO("[FS] volume reformatted OK"); - /* 诊断:重格式化后树绑定 + 绑定页裸读内容(与下次 - * boot 的 postreset 对比:地址分歧=树问题,内容变化=数据丢失) */ - nand_ftl_dump_binding(0, "fresh"); - nand_ftl_dump_binding(15, "fresh"); - nand_ftl_dump_binding(16, "fresh"); } else { DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr); syslog_post(SYSLOG_SELFTEST, "storage fault"); @@ -330,7 +310,6 @@ void StartDefaultTask(void *argument) #undef FS_VOL_PROBE if (fr == FR_OK) { DBG_INFO("[FS] FATFS mounted"); - nand_ftl_log_state("[probe-ok]"); /* 日志引擎挂载后处理:建 /log 目录 + 写"上电/重启"分类记录(唯一写者) */ syslog_mcu_mounted(); /* 加载网络配置进 RAM 副本(配置引擎属主 = 本任务;首启含旧档迁移;