Files
STM32F4-Base/docs/测试说明/专项验证/存储维护与容量恢复.md
T
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

124 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`(B9 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 的恢复构建会把它链进去——所以这条坑只在
**做恢复的时候**才存在,也正因如此平时不会有迹象。