新增map绑定转储诊断(dhara_map_find+裸读绑定页):跨boot对比定界树分歧与数据丢失
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user