Files
STM32F4-Base/test/board/storage_test_task.c
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

623 lines
22 KiB
C
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.
/******************************************************************************
* 模块名称:Storage Test Task
* 模块功能:FTL + FatFS 存储测试,从 defaultTask 迁入,统一由 testTask
* 调度运行。正常产品构建下由 defaultTask 调用,行为与原内联逻辑一致。
* 作者:王建锋
* 创建日期:2026-08-26
******************************************************************************/
#include <string.h>
#include "main.h"
#include "ff.h"
#define DBG_TAG "[STORAGE-TEST]"
#include "dbg_log.h"
#include "test_config.h" /* 破坏性用例开关 ENABLE_DESTRUCTIVE_TESTS 的唯一来源 */
#include "storage_test.h"
#include "nand_ftl.h"
#include "diskio.h"
#include "gd5f2gq5ue.h"
storage_test_stats_t g_storage_stats;
#ifndef ENABLE_STORAGE_RECOVERY_TESTS
/* 全套件共享的 FATFS/FIL(RAM 预算紧张:每个 static FATFS≈560B、FIL≈600B,
* 各测试函数独立持有会撑爆 .bss。测试在单任务 testTask 内串行执行、从不并发,
* 共用一组安全,且比"每函数一份"省下约 5KB。见产品级 RAM 溢出 L6406E) */
static FATFS fs;
static FIL fil;
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 性能缓冲 2048B = 恰好一个 NAND 页(RAM 预算:4096 时套件链接溢出 0x604,
* L6406E 实测;2048 分块仍覆盖整页写路径,128KB 总量不变) */
static uint8_t s_perf_buf[2048];
/* TC-STORAGE-01: FTL 掉电恢复(journal 持久化)
* 写文件并同步落盘,随后模拟掉电(卸载 + 复位 FTL + 重新 resume),再挂载校验内容一致。
*/
static void storage_test_powerloss(void)
{
const uint32_t len = 4096;
uint8_t wbuf[512];
uint8_t rbuf[512];
UINT bw, br;
uint32_t i;
int ok = 0;
DBG_INFO("=== Storage Test: TC-STORAGE-01 Power-loss recovery ===");
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-01 mount");
return;
}
/* Phase 1: 写已知内容文件并同步 */
for (i = 0; i < sizeof(wbuf); i++) {
wbuf[i] = (uint8_t)((i * 31 + 7) & 0xFF);
}
if (f_open(&fil, "pl.dat", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
for (i = 0; i < len; i += sizeof(wbuf)) {
f_write(&fil, wbuf, sizeof(wbuf), &bw);
}
f_sync(&fil);
f_close(&fil);
} else {
STORAGE_TEST_CHECK(0, "TC-STORAGE-01 f_open(write)");
return;
}
/* 模拟掉电:卸载 + 复位 FTL + 重新 resume */
f_mount(&fs, "", 0);
nand_ftl_deinit();
nand_ftl_init();
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-01 remount after resume");
return;
}
/* Phase 2: 重新打开校验 */
if (f_open(&fil, "pl.dat", FA_READ) == FR_OK) {
ok = 1;
for (i = 0; i < len; i += sizeof(rbuf)) {
f_read(&fil, rbuf, sizeof(rbuf), &br);
for (uint32_t j = 0; j < br; j++) {
if (rbuf[j] != (uint8_t)(((i + j) * 31 + 7) & 0xFF)) {
ok = 0;
break;
}
}
if (!ok) break;
}
f_close(&fil);
} else {
ok = 0;
}
STORAGE_TEST_CHECK(ok, "TC-STORAGE-01 content survived power-loss (resume)");
f_unlink("pl.dat");
}
/* TC-STORAGE-02: 大文件 / 多扇区 / 随机 seek
* 写 256KB 位置相关模式文件,顺序读回比对 + 随机偏移 seek 读比对。
* 栈预算:testTask 栈 4KB,局部缓冲 4096 必然溢出栈(8 月侥幸未爆是堆使用
* 低),改 2048 = 恰好一个 NAND 整页,覆盖度等价。
*/
static void storage_test_largefile(void)
{
const uint32_t total = 256 * 1024;
const uint32_t chunk = 2048;
uint8_t buf[2048];
UINT bw, br;
uint32_t off, i;
int ok = 1;
DBG_INFO("=== Storage Test: TC-STORAGE-02 Large file / random seek ===");
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-02 mount");
return;
}
/* 写:位置相关模式 */
if (f_open(&fil, "big.dat", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-02 f_open(write)");
return;
}
for (off = 0; off < total; off += chunk) {
for (i = 0; i < chunk; i++) {
buf[i] = (uint8_t)(((off + i) * 31 + 7) & 0xFF);
}
f_write(&fil, buf, chunk, &bw);
}
f_close(&fil);
/* 顺序读回 */
if (f_open(&fil, "big.dat", FA_READ) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-02 f_open(read)");
return;
}
for (off = 0; off < total; off += chunk) {
f_read(&fil, buf, chunk, &br);
if (br != chunk) { ok = 0; break; }
for (i = 0; i < chunk; i++) {
if (buf[i] != (uint8_t)(((off + i) * 31 + 7) & 0xFF)) { ok = 0; break; }
}
if (!ok) break;
}
STORAGE_TEST_CHECK(ok, "TC-STORAGE-02 sequential 256KB read consistent");
/* 随机 seek 读 */
if (ok) {
uint32_t seek_offs[] = { 0, 33333, 131072, 200000, total - 200 };
int seek_ok = 1;
for (int s = 0; s < 5; s++) {
uint32_t so = seek_offs[s];
uint32_t sc = 200;
if (f_lseek(&fil, so) != FR_OK) { seek_ok = 0; break; }
f_read(&fil, buf, sc, &br);
for (i = 0; i < sc; i++) {
if (buf[i] != (uint8_t)(((so + i) * 31 + 7) & 0xFF)) { seek_ok = 0; break; }
}
if (!seek_ok) break;
}
STORAGE_TEST_CHECK(seek_ok, "TC-STORAGE-02 random seek read consistent");
}
f_close(&fil);
f_unlink("big.dat");
}
/* TC-STORAGE-03: 坏块注入下存储写入
* 清空旧 map → 注入若干好块为坏(持久化) → 重建文件系统 → 正常写读 → 校验坏块仍被报告。
*/
static void storage_test_badblock(void)
{
uint8_t wbuf[512], rbuf[512];
UINT bw, br;
uint32_t bad[3];
int nbad = 0;
int ok = 1;
DBG_INFO("=== Storage Test: TC-STORAGE-03 Bad-block injection during storage ===");
/* 清空旧 dhara map,避免后续注入的坏块被旧映射引用 */
nand_ftl_format();
/* 注入:挑出厂好块标记为坏(更新并持久化 BBT) */
for (uint32_t b = 100; b < gd5f_get_usable_blocks() && nbad < 3; b++) {
if (gd5f2gq5ue_is_block_bad(b) == 0) {
gd5f2gq5ue_mark_block_bad(b);
bad[nbad++] = b;
}
}
if (nbad < 3) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-03 could not find 3 good blocks (got %d)", nbad);
return;
}
STORAGE_TEST_CHECK(nbad == 3, "TC-STORAGE-03 injected %d bad blocks", nbad);
/* 复位 FTL 使 dhara 经 is_block_bad 看到新坏块 */
f_mount(&fs, "", 0);
nand_ftl_deinit();
nand_ftl_init();
/* 重建文件系统(dhara 仍会跳过坏块) */
{
MKFS_PARM opts = { FM_FAT32, 0, 0, 0, 0 };
uint8_t work[FF_MAX_SS];
if (f_mkfs("", &opts, work, sizeof(work)) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-03 f_mkfs");
return;
}
}
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-03 mount");
return;
}
/* 正常文件写读 */
for (uint32_t i = 0; i < sizeof(wbuf); i++) {
wbuf[i] = (uint8_t)((i * 17 + 3) & 0xFF);
}
if (f_open(&fil, "bbt.dat", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
f_write(&fil, wbuf, sizeof(wbuf), &bw);
f_close(&fil);
} else {
STORAGE_TEST_CHECK(0, "TC-STORAGE-03 f_open(write)");
return;
}
if (f_open(&fil, "bbt.dat", FA_READ) == FR_OK) {
f_read(&fil, rbuf, sizeof(rbuf), &br);
f_close(&fil);
if (br == sizeof(wbuf)) {
for (uint32_t i = 0; i < sizeof(rbuf); i++) {
if (rbuf[i] != wbuf[i]) { ok = 0; break; }
}
} else {
ok = 0;
}
} else {
ok = 0;
}
STORAGE_TEST_CHECK(ok, "TC-STORAGE-03 file intact with injected bad blocks");
/* 校验注入坏块仍被报告为坏(未被 dhara 占用) */
int badok = 1;
for (int k = 0; k < nbad; k++) {
if (gd5f2gq5ue_is_block_bad(bad[k]) != 1) { badok = 0; }
}
STORAGE_TEST_CHECK(badok, "TC-STORAGE-03 injected blocks still reported bad");
f_unlink("bbt.dat");
/* 注意:注入的坏块已持久化,测试设备专用;如需恢复需重建 BBT / 重新格式化
* (配套 TC-STORAGE-04 恢复测试:storage_test.h 启用 ENABLE_STORAGE_RECOVERY_TESTS
* 后跑一次套件,即可清除运行时注入坏块 + 清 dhara map 回出厂态) */
}
#endif /* ENABLE_DESTRUCTIVE_TESTS: s_perf_buf + TC-STORAGE-01/02/03(写、擦、坏块注入) */
/* TC-STORAGE-05: 文件名/路径合法性校验(守护 ".fschk 无限重格式化" 真根因)
* 本移植 FF_USE_LFN=0,只支持严格 8.3 短名(主名≤8、扩展≤3、不以点开头)。
* 非法名字 f_open 必返 FR_INVALID_NAME(纯 RAM 名字判定、不碰盘);合法但
* 不存在的名字返 FR_NO_FILE。本测试断言两类严格可区分——一旦哪天有人把
* FR_INVALID_NAME 误归入"卷损坏"自愈集合,这条测试就是最后的机械防线。
* (详见 docs/问题记录/GD5F2GQ5UE_Trap_Records.md TRAP-GD5F-05 / AGENTS.md)
*/
static void storage_test_name_validation(void)
{
uint32_t i;
int gok = 1;
int bok = 1;
/* 合法 8.3:FA_READ 打开 → FR_OK(存在) 或 FR_NO_FILE(不存在),均非 INVALID */
static const char *good[] = {
"fschk.tmp", /* 修复后的探针名(名5+扩展3) */
"config.ini", /* 名6+扩展3 */
"12345678.txt", /* 主名 8 / 扩展 3 上限 */
"A.TXT", /* 最小合法名 */
"NOEXT", /* 无扩展 */
"1.23", /* 数字名 */
"run.log"
};
/* 非法:FF_USE_LFN=0 下 create_short 必拒 → FR_INVALID_NAME */
static const char *bad[] = {
".fschk", /* 事故根因:点开头(点后当扩展域,超 3 上限) */
".hidden", /* 点开头隐藏名风格 */
"123456789", /* 主名 9 > 8 */
"toolongname.txt", /* 主名 > 8 */
"a.toolongext", /* 扩展 > 3 */
"bad?char.txt", /* 非法字符 ? */
"star*name", /* * */
"col:on", /* : */
"lt<gt>val", /* < > */
"pipe|name" /* | */
};
DBG_INFO("=== Storage Test: TC-STORAGE-05 Filename validation ===");
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-05 mount");
return;
}
for (i = 0; i < (uint32_t)(sizeof(good) / sizeof(good[0])); i++) {
FRESULT r = f_open(&fil, good[i], FA_READ);
if (r == FR_OK) {
f_close(&fil);
}
if (r == FR_INVALID_NAME) {
gok = 0;
DBG_ERROR(" valid name wrongly rejected: '%s'", good[i]);
}
}
STORAGE_TEST_CHECK(gok, "TC-STORAGE-05 valid 8.3 names accepted");
for (i = 0; i < (uint32_t)(sizeof(bad) / sizeof(bad[0])); i++) {
FRESULT r = f_open(&fil, bad[i], FA_READ);
if (r == FR_OK) {
f_close(&fil);
}
/* 含 ':' 的名字被 FatFs 先解析为盘符前缀 → FR_INVALID_DRIVE(11)
* 同样属合法拒绝(台架实测 'col:on' fr=11),与 INVALID 同等待遇 */
if ((r != FR_INVALID_NAME) &&
!((r == FR_INVALID_DRIVE) && (strchr(bad[i], ':') != NULL))) {
bok = 0;
DBG_ERROR(" invalid name NOT rejected: '%s' -> fr=%d", bad[i], (int)r);
}
}
STORAGE_TEST_CHECK(bok, "TC-STORAGE-05 invalid names rejected (FR_INVALID_NAME)");
}
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱)
* 本移植 FA_OPEN_APPEND 不含 FA_WRITE → 不带写位打开 f_write 必返 FR_DENIED;
* 追加写必须 FA_WRITE|FA_OPEN_APPEND。另 f_write 簇耗尽返 FR_OK+短写(无错误
* 码),判成败必须校验写回字节数。锁定这两条移植专有语义防回归。
*/
static void storage_test_open_modes(void)
{
UINT bw;
FRESULT r;
DBG_INFO("=== Storage Test: Open-mode semantics ===");
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "mount");
return;
}
(void)f_unlink("mode.tmp");
/* 1) 建一个含初始内容的文件 */
r = f_open(&fil, "mode.tmp", FA_CREATE_ALWAYS | FA_WRITE);
STORAGE_TEST_CHECK(r == FR_OK, "create base file");
if (r == FR_OK) {
f_write(&fil, "abc", 3, &bw);
f_close(&fil);
}
/* 2) FA_OPEN_APPEND(无 FA_WRITE)→ 写必返 FR_DENIED */
r = f_open(&fil, "mode.tmp", FA_OPEN_APPEND);
STORAGE_TEST_CHECK(r == FR_OK, "open append(no write)");
if (r == FR_OK) {
FRESULT wr = f_write(&fil, "xyz", 3, &bw);
STORAGE_TEST_CHECK(wr == FR_DENIED,
"append-no-write must DENY (fr=%d)", (int)wr);
f_close(&fil);
}
/* 3) FA_WRITE|FA_OPEN_APPEND → 追加成功且字节数正确 */
r = f_open(&fil, "mode.tmp", FA_WRITE | FA_OPEN_APPEND);
STORAGE_TEST_CHECK(r == FR_OK, "open write|append");
if (r == FR_OK) {
(void)f_write(&fil, "xyz", 3, &bw);
STORAGE_TEST_CHECK(bw == 3, "append byte-count (%u)", bw);
f_close(&fil);
}
/* 4) 只读打开后写 → 必返 FR_DENIED(读句柄不可写) */
r = f_open(&fil, "mode.tmp", FA_READ);
STORAGE_TEST_CHECK(r == FR_OK, "open read-only");
if (r == FR_OK) {
FRESULT wr = f_write(&fil, "q", 1, &bw);
STORAGE_TEST_CHECK(wr == FR_DENIED,
"read-only write must DENY (fr=%d)", (int)wr);
f_close(&fil);
}
(void)f_unlink("mode.tmp");
}
#endif /* ENABLE_DESTRUCTIVE_TESTS: 打开模式语义 */
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS: 共享缓冲 + 正常测试函数 */
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
static void storage_test_recover(void);
#endif
#ifndef ENABLE_STORAGE_RECOVERY_TESTS
/* 正常存储测试套件主体(恢复模式见文件末尾 storage_test_run 分发) */
static void storage_test_run_normal(void)
{
FRESULT res;
memset(&g_storage_stats, 0, sizeof(g_storage_stats));
#ifdef ENABLE_DESTRUCTIVE_TESTS
DBG_INFO("mode: DESTRUCTIVE (write/erase/format/badblock-inject enabled)");
#else
DBG_INFO("mode: READ-ONLY (destructive cases skipped; set "
"ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)");
#endif
/* ==================== 挂载 ==================== */
res = f_mount(&fs, "", 1);
#ifdef ENABLE_DESTRUCTIVE_TESTS
if (res == FR_NO_FILESYSTEM) {
static uint8_t work[FF_MAX_SS];
MKFS_PARM opts = { FM_FAT32, 0, 0, 0, 0 };
res = f_mkfs("", &opts, work, sizeof(work));
STORAGE_TEST_CHECK(res == FR_OK, "f_mkfs");
res = f_mount(&fs, "", 1);
}
#else
if (res == FR_NO_FILESYSTEM) {
DBG_INFO("read-only mode: no filesystem on volume, skip write cases "
"(mkfs self-heal disabled)");
}
#endif
STORAGE_TEST_CHECK(res == FR_OK, "f_mount");
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* ==================== FTL + FatFS 文件系统测试 ==================== */
{
char read_buf[64];
const char *test_str = "Hello FatFS + dhara FTL!";
if (res == FR_OK) {
UINT bw, br;
res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
STORAGE_TEST_CHECK(res == FR_OK, "f_open(write)");
if (res == FR_OK) {
f_write(&fil, test_str, (UINT)strlen(test_str), &bw);
STORAGE_TEST_CHECK(bw == (UINT)strlen(test_str), "f_write (%u bytes)", bw);
f_close(&fil);
res = f_open(&fil, "test.txt", FA_READ);
STORAGE_TEST_CHECK(res == FR_OK, "f_open(read)");
if (res == FR_OK) {
f_read(&fil, read_buf, (UINT)(sizeof(read_buf) - 1), &br);
f_close(&fil);
read_buf[br] = '\0';
STORAGE_TEST_CHECK(br == (UINT)strlen(test_str) &&
memcmp(test_str, read_buf, br) == 0,
"data verify \"%s\"", read_buf);
}
}
f_unlink("test.txt");
}
}
/* ==================== FTL + FatFS 性能测试 ==================== */
DBG_INFO("=== Storage Test: Performance ===");
{
FIL pfile;
UINT pw, pr;
uint32_t t0, tw, tr;
uint32_t i;
uint32_t total_kb = 128;
uint32_t chunk = sizeof(s_perf_buf);
uint32_t count = (total_kb * 1024) / chunk;
memset(s_perf_buf, 0xA5, sizeof(s_perf_buf));
/* 写入 */
t0 = HAL_GetTick();
if (f_open(&pfile, "perf.dat", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
for (i = 0; i < count; i++) {
f_write(&pfile, s_perf_buf, chunk, &pw);
}
f_close(&pfile);
}
tw = HAL_GetTick() - t0;
/* 读取 */
t0 = HAL_GetTick();
if (f_open(&pfile, "perf.dat", FA_READ) == FR_OK) {
for (i = 0; i < count; i++) {
f_read(&pfile, s_perf_buf, chunk, &pr);
}
f_close(&pfile);
}
tr = HAL_GetTick() - t0;
f_unlink("perf.dat");
if (tw > 0) {
DBG_INFO(" Write: %lu KB in %lu ms = %lu KB/s",
total_kb, tw, (total_kb * 1000) / tw);
}
if (tr > 0) {
DBG_INFO(" Read: %lu KB in %lu ms = %lu KB/s",
total_kb, tr, (total_kb * 1000) / tr);
}
}
#endif /* ENABLE_DESTRUCTIVE_TESTS: 文件系统写读 + 性能压测 */
/* ==================== 存储集成测试(TC-STORAGE-05 契约 + 01~03 集成) ========== */
/* 纯语义契约测试(非破坏性):文件名合法性 */
storage_test_name_validation();
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 破坏性用例:打开模式契约 + 掉电恢复 / 大文件 / 坏块注入集成 */
storage_test_open_modes();
storage_test_powerloss();
storage_test_largefile();
storage_test_badblock();
#endif
/* ==================== 汇总报告 ==================== */
STORAGE_TEST_REPORT("Storage Tests");
}
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS */
/*
* 套件总入口分发。恢复模式(storage_test.h 打开 ENABLE_STORAGE_RECOVERY_TESTS)
* = 专用维护构建:重建 BBT(清除运行时误标坏块)+ 清 dhara map 后直接返回,
* 不在受污染介质上继续跑其余测试(2026-09-20:CCM 首轮实验中 DMA 够不到栈上
* 小缓冲,随机块被误标坏并持久化,journal 引用到即静默卡死——恢复模式即为其
* 清账手段)。恢复完成后重新注释该宏、编译正常套件即可。
*/
void storage_test_run(void)
{
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
storage_test_recover();
STORAGE_TEST_REPORT("Storage Recovery");
#else
storage_test_run_normal();
#endif
}
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
#ifdef STORAGE_RECOVERY_FULL_WIPE
/*
* 函数功能:擦净 dhara 可见区全部块(容量恢复用)
* 入口参数:无
* 返回值:无
* 限定条件:SPI/驱动已初始化;调用期间无其它存储访问(测试构建下单写者)
* 函数说明:逐块擦除 [0, gd5f_get_usable_blocks()),跳过 BBT 记录的坏块,
* 保留块池(BBT 存储区)不动。擦净后 journal checkpoint 不复存在,
* 下次启动 dhara_map_resume 失败 → dhara_map_clear 全新初始化
* (bb_last = bb_current = 0)→ 容量回满。
* 注意:擦净后**不能**再调 nand_ftl_format()(会把内存中仍带旧坏块
* 计数的 journal 状态重新落盘,容量又被扣掉)。
*/
static void storage_test_wipe_visible(void)
{
uint32_t usable = gd5f_get_usable_blocks();
uint32_t done = 0;
uint32_t skipped = 0;
uint32_t b;
for (b = 0; b < usable; b++) {
if (gd5f2gq5ue_is_block_bad(b) != 0) {
skipped++;
continue;
}
if (gd5f_block_erase(b) == GD5F_OK) {
done++;
} else {
DBG_ERROR(" block %lu erase failed", (unsigned long)b);
}
if ((b % 256U) == 0U) {
DBG_INFO(" wipe progress: %lu/%lu", (unsigned long)b, (unsigned long)usable);
}
}
DBG_INFO(" wiped %lu blocks (%lu skipped as bad, %lu visible)",
(unsigned long)done, (unsigned long)skipped, (unsigned long)usable);
STORAGE_TEST_CHECK(done > 0U, "TC-STORAGE-04 full wipe (%lu blocks erased)",
(unsigned long)done);
}
#endif /* STORAGE_RECOVERY_FULL_WIPE */
/* TC-STORAGE-04: 维护模式(恢复 = 重建 BBT + 重新格式化;叠加全片擦除 = 容量恢复)
* 仅在启用 ENABLE_STORAGE_RECOVERY_TESTS 时编译运行;运行一次即可把设备
* 恢复到干净出厂态(清除运行时注入坏块并清空 dhara map)。
*/
static void storage_test_recover(void)
{
DBG_INFO("=== Storage Test: TC-STORAGE-04 maintenance (recovery / full wipe) ===");
int r = gd5f2gq5ue_bbt_rebuild();
STORAGE_TEST_CHECK(r == GD5F_OK, "TC-STORAGE-04 bbt rebuild");
#ifdef STORAGE_RECOVERY_FULL_WIPE
storage_test_wipe_visible();
#else
nand_ftl_format();
STORAGE_TEST_CHECK(1, "TC-STORAGE-04 nand_ftl_format done");
#endif
uint8_t fb[GD5F_TOTAL_BLOCKS / 8];
uint32_t ver = 0;
if (gd5f_bbt_dump_flash(fb, &ver) == GD5F_OK) {
STORAGE_TEST_CHECK(ver >= 1, "TC-STORAGE-04 persisted BBT version=%lu", ver);
} else {
STORAGE_TEST_CHECK(0, "TC-STORAGE-04 dump_flash failed");
}
#ifdef STORAGE_RECOVERY_FULL_WIPE
DBG_INFO("TC-STORAGE-04 full wipe complete; power-cycle to rebuild fresh "
"(capacity expected back to full)");
#else
DBG_INFO("TC-STORAGE-04 recovery complete; recommend power-cycle to re-init cleanly");
#endif
}
#endif