Files
STM32F4-Base/docs/测试说明/专项验证/存储维护与容量恢复.md
T
super_agent_bot 72b4b0943c docs: clear task and layer shorthand IDs
Replace task/batch shorthand IDs and residual layer labels in
human-facing docs with semantic phrases. Add the new cleanup
policy to docs/编号规范.md and update the entry docs.

Machine keys (TC/CODE-RULE/MENU-STATE/TRAP/FR), hardware
designators/pins/MAC, protocol/version numbers and frozen
historical evidence are intentionally kept.

Docs only; no code behavior change. Verified with
check_doc_links.py --all and git diff --check.
2026-11-02 03:33:51 +08:00

9.5 KiB
Raw Blame History

STM32F4-Base 存储维护与容量恢复

版本:v1.0 日期:2026-09-21 适用工程:STM32F4-Base(STM32F407ZGTx + GD5F2GQ5UE SPI NAND + dhara FTL + FatFs) 关联:docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md(破坏性用例清单)、docs/内存管理/内存内容分配策略.md(DMA 可达性守卫)

1. 三个维护动作,别混用

目标 开关组合 做了什么 代价
A. 卷/BBT 恢复 TEST_SUITE_STORAGE + ENABLE_STORAGE_RECOVERY_TESTS 重建 BBT(重扫出厂坏块并持久化)+ nand_ftl_format() 清 dhara map 卷内容全失;不恢复容量
B. 容量恢复 A 再叠加 STORAGE_RECOVERY_FULL_WIPE 擦净 dhara 可见区 2044 块(连 journal checkpoint 一起)+ 重建 BBT;随后断电重启 卷内容全失;2044 次块擦除(实测约 4 秒)
C. 日常回归 只开 TEST_SUITE_*,不开 ENABLE_DESTRUCTIVE_TESTS 只跑只读/参数校验用例,不写不擦不改坏块表 无

三个开关都在 test/board/test_config.h(A/B 的根开关在 test/board/storage_test.h),默认全关;维护完请重新注释并编译产品构建。

2. 为什么"容量"需要单独恢复(根因)

  • 健康满容量 = 96064 pages(187 MB)(No valid map found, creating fresh 分支下的满值)。
  • dhara 的坏块计数 bb_current / bb_last 持久化写在 journal checkpoint 头里 (Lib/dhara/journal.c 的 hdr_set_bb_current/hdr_set_bb_last,随 checkpoint 一起落盘)。 容量按 good_blocks = num_blocks − max(bb_last, bb_current) − 1 递减。
  • dhara_journal_clear()(即 nand_ftl_format() 走的路)只做 bb_last = bb_current,不归零—— 所以动作 A 清不掉被扣掉的容量,断电重扫也一样(计数从 checkpoint 读回)。
  • 只有让下次启动 dhara_map_resume 失败、走 dhara_map_clear 的全新初始化分支 (此时 bb_last = bb_current = 0)才能回满 → 这就是动作 B 的原理。
  • 记账的触发:一次 program verify 失败(日志 [ERR][NAND_FTL] program verify: data mismatch, blk N) 就让 dhara 多记一个坏块;破坏性套件(写完擦、坏块注入、格式化)会累积这个数字。

3. 操作步骤(以容量恢复为例)

  1. test/board/test_config.h:取消注释 TEST_SUITE_STORAGE;

  2. test/board/storage_test.h:取消注释 ENABLE_STORAGE_RECOVERY_TESTS 与 STORAGE_RECOVERY_FULL_WIPE;

  3. /keil-build(要求 0 错误 0 警告);

  4. 采集顺序 open → clear → 烧录 → wait-idle(擦除 2044 块约 4 秒,静默判据给 20~30s);

  5. 确认日志:

    === Storage Test: TC-STORAGE-04 maintenance (recovery / full wipe) ===
      [PASS] TC-STORAGE-04 bbt rebuild
      wipe progress: ... 2044/2044
      wiped 2044 blocks (N skipped as bad, 2044 visible)
      [PASS] TC-STORAGE-04 full wipe (2044 blocks erased)
      [PASS] TC-STORAGE-04 persisted BBT version=N
    === Storage Recovery: 3/3 PASSED (failed=0) ===
    
  6. 断电重启(stlink_flash.py reset 无参即可)——本次启动才会重建干净 journal;

  7. 恢复产品构建(两个头文件重新注释)→ /keil-build → 烧录 → /serial-boot-test,确认:

    [NAND_FTL] No valid map found, creating fresh (err=3)
    [NAND_FTL] NAND FTL ready (capacity: 96064 pages = 187 MB)
    [FS] no FAT volume, formatting NAND...      ← 首次会重建 FAT
    [FS] FATFS mounted
    

4. 注意事项

  • 单写者:套件运行期间存储由 testTask 独占(defaultTask 打印 mount/cfg skipped (STORAGE suite owns storage)),勿并发访问。
  • 卷内容全失:A/B 都会丢掉 /log 历史与 /config.ini(配置回落工厂默认,FAT 由启动流程自动重建)。
  • 坏块跳过:全片擦除会自动跳过 BBT 中已记录的坏块(skipped as bad),保留块池(BBT 存储区)不动。
  • 别用 A 期待容量回来:A 只重建 BBT + 格式化,容量仍是被扣掉的值——这是 2026-09-21 实测确认过的。
  • 别为了"省一次擦除"而手工擦单块:容量是否恢复取决于 checkpoint 是否全部失效,只擦单块无法保证(resume 会锚定到更早的 checkpoint)。
  • 恢复后建议跑一次只读套件确认(GD5F 只读 16/16 或存储只读 3/3)。

4.1 根目录残留文件清理(2026-10-22 实测,动作 C 之外的日常维护)

台架 / 根目录常年堆着 FTP 探针留下的文件——不占坏块、不影响挂载,但会把"目录列表类" 问题的现场搞混,也让容量读数失真。本次实测残留 27 个文件 / 262 KB(AB000x、A000x、 R000x、SZ2K~64K、E12A、DIAG_A、PP0~PP4,全是历史一次性探针留下的,那些探针脚本本身 已于同日删除,见 MDK-ARM/README.md)。

  • 判据:只删显式点名的文件,绝不 glob。清理脚本里写死 27 个文件名逐个 DELE;保留清单 必须含 /config.ini(网口与 FTP 鉴权配置)、/PROT_SET.BIN(保护定值 blob, PROT_SET_BLOB_SZ = 176 B,删了等于把定值清零)、/log 目录(运行与自检记录)。
  • 三步验收:① 清完 LIST 只剩 LOG + CONFIG.INI + PROT_SET.BIN;② /serial-boot-test PASS(定值巡检 与配置加载都在这条链上);③ 跑 test/host/ftp_list_fill_test.py 确认 FTP 面正常。
  • 这属于动作 D(清文件),与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复) 都不同:A/B 会丢卷内容,D 不动文件系统结构。

4.2 自检台账清零,重建联合判定审计基线(2026-10-22 实测)

run_boot_test.py --require-selftest-pass --selftest-scope all(累计口径)在台架上永远 FAIL: /log/selftest.log 里躺着 29 条历史记录,2026-10-10 09:24:12 → 2026-10-14 06:55:02, 组成为 16 × watchdog reset + 13 × heap low water——都是看门狗引入期与堆迁移期 留下的,两件事后来都已结案。新判据(只看本次新增)已不受影响,但审计口径要能用,就得 有一块干净的基线,所以按用户决定清掉台账。

  • 安全性判据:sys_log_mcu.c 是每条记录开-写-关(f_open(FA_WRITE|FA_OPEN_APPEND) → f_write(64) → f_close),不持有常驻句柄 → 从 FTP 侧 DELE 不会撕裂,下次有 故障时自动重建文件。清理前先把导出副本留档(1856 B = 29×64)。
  • 产品数据一条不动:run.log(运行记录)、wdev.log(掉电取证,带 8 KB 护栏)、 soe.log(SOE 记录 SOE 动作记录)——这三条是固件在写的活文件,容量护栏由固件自己管,别手删。
  • 顺带清掉的孤儿:/log/flushchk.bin 32,768 B,全库零引用——已退役的 flush 诱导实验 留下的(sys_log_mcu.c 注释里那句"旧称 flush 诱导"就是它)。删前先 RETR 出来验过内容: 256 种字节值全部出现、前 64 字节全 FF → 就是一份 0..255 图案的写盘校验填充,没有任何 产品语义;留档后再 DELE(判据同 §4.1:先知道删的是什么)。
  • 清理后 /log 只剩三条、全是产品数据:run.log(运行记录)、wdev.log(掉电取证)、 soe.log(SOE 动作记录)。之后再跑 run_boot_test.py --require-selftest-pass → PASS (699 ms、SELFTEST_BASELINE: 既有记录 0 条)。
  • 验收(两条判定路径都要过):--require-selftest-pass → SELFTEST_BASELINE: 既有记录 0 条 (status=absent) → PASS;--selftest-scope all → selftest: no fault record → PASS (清零前这条是 29 fault record(s) FAIL)。设备侧无此文件时 lftpd 回 450,被按"台账干净" 处理,这条映射见 ../../架构设计/STM32F4-Base存储架构说明.md §10.3 与 S7 判定表。

4.3 恢复流程里的 ECC 开关:不能只看"跑完了"(2026-10-23)

动作 A/B 的第一步是 gd5f2gq5ue_bbt_rebuild():临时关掉 ECC 才能读原始出厂坏块标记,扫完 必须重新打开并验证真的生效。这一"关—开"以前是两条裸调用(返回值全丢),于是存在一条 静默路径:SPI 抖一下 → ECC 没能重新打开 → 设备以为恢复完成,之后所有 NAND 读写都在无位翻转 保护下运行,而且 gd5f_check_ecc() 在 ECC 关闭时恒读"无 ECC 错误",整条链不会再有任何信号。

  • 现在(2026-10-23 改)关 ECC 失败会立即中止(此刻 ECC 仍开着,是安全侧;带着 ECC 去扫 spare 反而会把 厂标好块误判成坏块,"重建 BBT"变成制造坏块),重开失败则复用 init 的做法回读 B0h 校验 ECC_EN 位,"写了但没生效"与写失败同罪,一律返回错误。
  • 运维判据:串口/run.log 里出现 BBT rebuild: ECC restore FAILED 或 ECC not enabled (B0h=…) → 本次恢复不算完成,设备处于无 ECC 保护态,必须断电重启让 init 重走一遍并使回读校验通过, 不要接着跑 nand_ftl_format() 当作已修好。反之 BBT rebuilt and persisted 才代表扫描 + 使能 + 持久化三件事都成了。
  • 另记一条定位事实:gd5f2gq5ue_bbt_rebuild() 在产品镜像里不被链接(map 明写 Removing … (604 bytes),产品构建无调用者),只有本动作 A/B 的恢复构建会把它链进去——所以这条坑只在 做恢复的时候才存在,也正因如此平时不会有迹象。