Files
STM32F4-Base/test/board/storage_test_task.c
T
wandering1 52976bdec3 refactor(编号): 批 4 陷阱号统一为 TRAP-<模块码>-<nn>
本批是 ② 形态迁移清的第一族(用户裁定顺序:② 里的 Trap 一族先做)

统一了什么(数字不变、只补模块码,§7.1)——改名工具 23 处 / 11 文件
- Trap 01~07 → TRAP-GD5F-01~07:属主文档 7 个标题行从此成为定义位
- 陷阱 1 → TRAP-RTC-01(SD2506 属 RTC 域,§4.2)、见陷阱 01 → TRAP-GD5F-01
- Trap 21 / Trap 19 → TRAP-NET-21 / TRAP-NET-19(引用的是活登记处
  CH395F_Trap_Records.md,号本身不变)
- 波及 Drivers/BSP/GD5F2GQ5UE/*(4 处注释)与 test/board/*(2 处)→ 已重编

三处判读后**刻意不改**(写进 §8.3,不让人当成漏网)
- docs/环境与部署/并行任务工作树方案.md 的 wip/trap22-fix 是 git 分支名,不是编号;
  这类非编号字面量要靠类型处理,与位号 T1、IEEE 负序 S2、gitlint 规则 ID 同一批
- docs/UI设计/菜单页面设计.md 变更记录行的「陷阱 3」按 §8.1 冻结不改写
- docs/硬件平台/V0.0、V0.1 网表报告里 6 处 Trap05 语义确指 TRAP-NET-05,但该目录
  是门禁与改名工具共同的排除语料 → 不越界动笔,盲区登记进注册表 TRAP 族 cursor_note

两处不是改名、是改错话(映射表类工具的典型失效:字面对了、意思错了)
- scripts/README.md 原写「Trap 19 从未存在」;改名后字面变成 TRAP-NET-19,而它是
  **真实条目**(SET_MAC 必须在 INIT_CH395 之前)→ 那句话成了假话,改用站得住的例子
- docs/编号规范.md 同一处事故叙述补准确说法:当年裸形不在册 ≠ 这个坑不存在
- 菜单页面设计.md 的「见 §8.2 陷阱 1」本就不是 TRAP-* 族的号(指本文小节第 1 条),
  改写成「见本文 §8.2 第 1 条(lv_text_get_width 的 length=0 陷阱)」

关闸第一次真拦人:咬的是自己家的审计表
- scripts/rename_maps/batch3-trap.json 里必然写着 Trap 21 / 陷阱 1 等旧形态,
  被当成 6 类「台账外新账」→ 提交被拦(拦得对,但表不是语料)
- 第 4 次同一类自循环(前三次:别名表、注册表、债务台账)→ 整个 rename_maps/
  目录入 excluded_paths;这条规律写进 §8.4:**登记号的表一律不得当引用语料**

实测变化
- 错误级 659 → 632(−27);② 空格/中文形 Trap 残余 2 处(都是上面刻意保留的)
- ③ 悬空错误 44 → 42:TRAP-GD5F-01/05/06 三处历史悬空引用自动落地为已定义
- 编号字面量 8277 → 8312(识别面没有缩水,这是改名最容易踩的坑);
  可判定比例 82.92% → 82.99%(基线 82.6%);⑦ 定义无引用 100 → 103(如 TRAP-GD5F-02
  只有定义位无人引用,如实挂 warn 不压掉)
- 台账 --prune-backlog 收掉已还清 42 条,剩 298 条(只减不增);--strict 通过

另修:core.autocrlf=true 把 SD2506_Trap_Records.md 检出成 CRLF(41 处)→ 还原 LF,
该文件 diff 只剩 1 行实质改动(仓库权威行尾是 LF,见 .gitattributes)

验证(全绿)
- @build:0 错误 0 警告(Program Size Code=213356 RO=18580 RW=1184 ZI=131480)
- ci_local.py:L0 + L0.5 全过(16 runner / 1104 断言)
- check_doc_links.py --all:143 篇文档 0 失效、0 待确认
- 改动文件字节卫生逐项与 HEAD 对比:BOM / CR / U+FFFD 异常 0

审计轨迹与文档
- 映射表 scripts/rename_maps/batch3-trap.json(含作用域切分理由与刻意不改清单)
- 别名 11 条全部带 scope;docs/编号规范.md §7.1 行状态、§8.3 批 4 行 + 说明段、
  §8.4 自循环第 4 条、变更记录
2026-10-21 12:32:13 +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
/* TC-STORAGE-06: 打开模式与写回字节数语义(守护 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: TC-STORAGE-06 Open-mode semantics ===");
if (f_mount(&fs, "", 1) != FR_OK) {
STORAGE_TEST_CHECK(0, "TC-STORAGE-06 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, "TC-STORAGE-06 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, "TC-STORAGE-06 open append(no write)");
if (r == FR_OK) {
FRESULT wr = f_write(&fil, "xyz", 3, &bw);
STORAGE_TEST_CHECK(wr == FR_DENIED,
"TC-STORAGE-06 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, "TC-STORAGE-06 open write|append");
if (r == FR_OK) {
(void)f_write(&fil, "xyz", 3, &bw);
STORAGE_TEST_CHECK(bw == 3, "TC-STORAGE-06 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, "TC-STORAGE-06 open read-only");
if (r == FR_OK) {
FRESULT wr = f_write(&fil, "q", 1, &bw);
STORAGE_TEST_CHECK(wr == FR_DENIED,
"TC-STORAGE-06 read-only write must DENY (fr=%d)", (int)wr);
f_close(&fil);
}
(void)f_unlink("mode.tmp");
}
#endif /* ENABLE_DESTRUCTIVE_TESTS: TC-STORAGE-06 */
#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