0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(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%。
623 lines
22 KiB
C
623 lines
22 KiB
C
/******************************************************************************
|
||
* 模块名称: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
|