From 66fc48cd98433813a8c0ffc858ecc8d88362cd15 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 20 Sep 2026 10:48:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Emap=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E8=BD=AC=E5=82=A8=E8=AF=8A=E6=96=AD=EF=BC=88dhara=5Fmap=5Ffind?= =?UTF-8?q?+=E8=A3=B8=E8=AF=BB=E7=BB=91=E5=AE=9A=E9=A1=B5=EF=BC=89?= =?UTF-8?q?=EF=BC=9A=E8=B7=A8boot=E5=AF=B9=E6=AF=94=E5=AE=9A=E7=95=8C?= =?UTF-8?q?=E6=A0=91=E5=88=86=E6=AD=A7=E4=B8=8E=E6=95=B0=E6=8D=AE=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1?= 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 | 294 ++++++++++++++++++++++++++++++ Drivers/BSP/GD5F2GQ5UE/nand_ftl.h | 2 + Src/freertos.c | 9 + 4 files changed, 310 insertions(+) diff --git a/App/app_main.c b/App/app_main.c index 0d3adfd2..029cb488 100644 --- a/App/app_main.c +++ b/App/app_main.c @@ -20,6 +20,7 @@ #include "sd2506.h" #include "sys_clock.h" #include "gd5f2gq5ue.h" +#include "nand_ftl.h" #include "tpafe5160.h" #include "sys_log.h" @@ -72,6 +73,10 @@ 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 6a2fa097..abe42b94 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -672,3 +672,297 @@ void nand_ftl_scan_checks(void) 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 e67d6848..8ea0a74d 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h @@ -15,6 +15,8 @@ void nand_ftl_deinit(void); 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); #ifdef __cplusplus } diff --git a/Src/freertos.c b/Src/freertos.c index f2fb6477..6f1f2595 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -274,6 +274,10 @@ void StartDefaultTask(void *argument) 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) { if (hfr == FR_DENIED || hfr == FR_INVALID_OBJECT || @@ -297,6 +301,11 @@ void StartDefaultTask(void *argument) 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");