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.
9.5 KiB
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. 操作步骤(以容量恢复为例)
-
test/board/test_config.h:取消注释TEST_SUITE_STORAGE; -
test/board/storage_test.h:取消注释ENABLE_STORAGE_RECOVERY_TESTS与STORAGE_RECOVERY_FULL_WIPE; -
/keil-build(要求 0 错误 0 警告); -
采集顺序
open→clear→ 烧录 →wait-idle(擦除 2044 块约 4 秒,静默判据给 20~30s); -
确认日志:
=== 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) === -
断电重启(
stlink_flash.py reset无参即可)——本次启动才会重建干净 journal; -
恢复产品构建(两个头文件重新注释)→
/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-testPASS(定值巡检 与配置加载都在这条链上);③ 跑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.bin32,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 的恢复构建会把它链进去——所以这条坑只在 做恢复的时候才存在,也正因如此平时不会有迹象。