fix(nand-ftl): 传输失败不再误标坏块 + format 不再被脏缓存挡死(#79)

根因(真机定案,问题 #79):旧 dhara_nand_erase/prog() 把一切失败一律
mark_bad,而 mark_block_bad 每次**立即持久化**板级 BBT(不可撤销)——
一次传输层/等忙抖动就把好块永久记成坏块。台架 129 个好块(1016–1142
连续段)被固化,journal prepare_head() 连续 8 次撞 is_bad → TOO_BAD →
Cache flush FAIL → reformat 又被同一页挡住 → 整卷禁用、重启不恢复。

- 修复①:只有芯片明确回 E_FAIL/P_FAIL(状态位证据)才标坏;
  传输/超时失败只报 DHARA_E_BAD_BLOCK 让 dhara 重试,不标坏
- 修复②:nand_ftl_format() 尽力刷一次脏缓存,失败即丢弃并继续
  dhara_map_clear()(格式化语义本就是销毁映射;旧实现第一步返回,
  恢复动作在同一页死锁,f_mkfs 全在旧映射上必失败)
- C4 还债:新增跨调用失败类快照 nand_ftl_fault_get/clear(20 B,
  resume/读/刷/同步四点位记账);defaultTask 探针按类分流,
  BSP_ERR_NO_SPACE 跳过重格式化(容量问题不该用 mkfs 解)
- 哨兵与诊断:init 解锁块保护(A0h)后补回读校验(与 B0h ECC 同族
  缺口,实测本板 A0h=0,非本次根因,留作哨兵);gd5f_bbt_load()
  合并后坏块数 > 出厂扫描时打「BBT after merge」,现场可区分
  真坏与误标固化
- 测试:主机单测 149→178 全绿;新增 format 死锁(TC-NANDPORT-800..
  813b)、传输失败不标坏(207 语义翻转 + 208/505);牙齿检查过——
  把修复回退即见红
- 真机:恢复动作 A 重建 BBT 清除已固化误标(3/3 PASSED,BBT
  version=71);产品镜像复烧 [FS] FATFS mounted、配置出厂模板
  落盘、0 条 flush FAIL——#79 真机闭环(旧卷内容随 map clear
  作废,台架卷为可弃数据)
- 文档:存储架构说明 §10.4 口径修正 + 新增 §10.4.1/§10.4.2(页级
  失败升级链与误标持久化机理),存储维护与容量恢复 §2 补板级 BBT
  独立持久化注记 + §2.1(动作 A 此前静默失效),登记簿 C4 销项
- Keil 0 错 0 警(Code=255552,与烧录验证镜像一致);doc-links 213
  文档通过
This commit is contained in:
2026-11-04 18:16:36 +08:00
parent 2b848c4a0b
commit f979b39474
11 changed files with 664 additions and 56 deletions
+44 -17
View File
@@ -48,6 +48,7 @@
#include "main.h"
#include "ff.h"
#include "nand_ftl.h"
#include "bsp_err.h" /* BSP_ERR_NO_SPACE / BSP_ERR_CRC:卷健康探针按失败类分流(C4) */
#include "net_cfg.h"
#include "cfg_file.h"
#include "run_par.h" /* 运行参数段落盘(「数据可信」这一项,配置引擎属主 = 本任务) */
@@ -523,30 +524,56 @@ static FRESULT storage_bring_up(FATFS *fs) {
FS_VOL_PROBE(hfr); /* 两击确认 */
}
if (hfr != FR_OK) {
/* C4 消费点(2026-11-04,问题 #79):探针只拿到 FRESULT,失败类在
* disk_read/disk_write 返回时就被 FatFs 契约抹掉了。这里把 FTL 留下的
* 跨调用快照读出来,**先判"重格式化有没有意义"再决定动作**——
* 这正是 存储架构说明 §10.4 那条一直只写在文档里、没有消费者的判据。
*
* 为什么必须先判:BSP_ERR_NO_SPACE 是 journal/映射层块池见底(介质健康),
* 重格式化只会把问题推回去(文档原文);而 BSP_ERR_CRC 才是"页级太坏,
* 换页/reformat 可能救"。两类都走 reformat 的话,块池见底会被反复擦盘。 */
nand_ftl_fault_t fault;
nand_ftl_fault_get(&fault);
if (fault.active != 0u) {
DBG_ERROR("[FS] probe failed (fr=%d), FTL last fault: op=%u class=%ld lpn=%lu count=%lu",
(int)hfr, (unsigned)fault.op, (long)fault.cls,
(unsigned long)fault.lpn, (unsigned long)fault.count);
}
/* ⚠ FR_INVALID_NAME 绝不可入本集合:它是路径名语法错误(纯 RAM 判定、
* 不碰盘),与卷健康无关(曾误入 → "无限重格式化"事故) */
if (hfr == FR_DENIED || hfr == FR_INVALID_OBJECT ||
hfr == FR_INT_ERR || hfr == FR_DISK_ERR ||
hfr == FR_NO_FILE || hfr == FR_NO_PATH ||
hfr == FR_NO_FILESYSTEM) {
DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...",
(int)hfr);
f_mount(NULL, "", 0);
/* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */
(void)nand_ftl_format();
{
static uint8_t fs_work2[FF_MAX_SS];
MKFS_PARM opts2 = { FM_FAT32, 0, 0, 0, 0 };
fr = f_mkfs("", &opts2, fs_work2, sizeof(fs_work2));
if (fr == FR_OK) {
fr = f_mount(fs, "", 1);
}
}
if (fr == FR_OK) {
DBG_INFO("[FS] volume reformatted OK");
/* 按失败类分流:块池见底 ≠ 介质坏。前者擦卷救不回来,只上报
* (后续应走容量恢复,见 存储维护与容量恢复.md),后者才 reformat。 */
if ((fault.active != 0u) && (fault.cls == (int32_t)BSP_ERR_NO_SPACE)) {
DBG_ERROR("[FS] volume probe failed: FTL out of space, "
"reformat skipped (needs capacity recovery, not mkfs)");
syslog_post(SYSLOG_SELFTEST, "storage full");
fr = FR_INT_ERR;
} else {
DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr);
syslog_post(SYSLOG_SELFTEST, "storage fault");
DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...",
(int)hfr);
f_mount(NULL, "", 0);
/* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */
(void)nand_ftl_format();
{
static uint8_t fs_work2[FF_MAX_SS];
MKFS_PARM opts2 = { FM_FAT32, 0, 0, 0, 0 };
fr = f_mkfs("", &opts2, fs_work2, sizeof(fs_work2));
if (fr == FR_OK) {
fr = f_mount(fs, "", 1);
}
}
if (fr == FR_OK) {
DBG_INFO("[FS] volume reformatted OK");
} else {
DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr);
syslog_post(SYSLOG_SELFTEST, "storage fault");
}
}
} else {
/* 其余错误(写保护/卷锁/参数类)不重格式化——只上报,