Files
STM32F4-Base/docs/测试说明/存储维护与容量恢复.md
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

133 lines
11 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. `@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. 恢复产品构建(两个头文件重新注释)→ `@build` → 烧录 → `@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`;② `@test` PASS(定值巡检
与配置加载都在这条链上);③ 跑 `test/host/ftp_list_fill_test.py` 确认 FTP 面正常。
- 这属于**动作 D(清文件)**,与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复)
都不同:A/B 会丢卷内容,D 不动文件系统结构。
### 4.2 自检台账清零,重建 S7 审计基线(2026-10-22 实测)
`@test --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 动作记录)。之后再跑 `@test --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 的恢复构建会把它链进去——所以这条坑只在
**做恢复的时候**才存在,也正因如此平时不会有迹象。
## 5. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-09-21 | 新建:A/B/C 三个维护动作;容量恢复原理(dhara checkpoint 坏块计数不清零)与全片擦除恢复路径(实测 95200 → 96064) |
| 2026-10-22 | 加 §4.1「根目录残留文件清理」(动作 D,与 A/B/C 区分):台架实测 27 个探针残留文件 / 262 KB 已清;判据 = 只删显式点名、保留 `/config.ini`、`/PROT_SET.BIN`、`/log`;清后 `@test` PASS + LIST 完整性回归 PASS |
| 2026-10-22 | 加 §4.2「自检台账清零,重建 S7 审计基线」:29 条历史记录(16 × `watchdog reset` + 13 × `heap low water`,10-10 → 10-14,两案均已结案)按用户决定删除;安全判据 = `sys_log_mcu.c` 每条记录开-写-关、不持常驻句柄;`run.log`/`wdev.log`/`soe.log` 属产品数据不动;顺带删掉孤儿 `/log/flushchk.bin`(32 KB、0..255 图案的写盘校验填充、全库零引用,删前留档);清后 `this-run` 与 `all` 两条判定路径都 PASS(清零前 `all` 是 29 条 FAIL) |
| 2026-10-23 | 加 §4.3:恢复流程的 ECC 开关改为"失败即中止 + 回读 B0h 验生效"——S-5 R4 上线照出来的第一处静默降级;给出运维判据——出现 `ECC restore FAILED` / `ECC not enabled` 即本次恢复不算完成,须断电重启重走 init;并记明该函数在产品镜像中不被链接(`Removing … 604 bytes`),只有恢复构建才有这条路径。顺带修正本文结构:`## 5. 变更记录` 原先误插在 §4.1 与 §4.2 之间,已移到文末(原位置会让 §4.2 变成 §5 的子节) |