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%。
1030 lines
45 KiB
C
1030 lines
45 KiB
C
/*
|
||
* 模块名称:dhara NAND 移植层(nand_ftl)主机侧单元测试
|
||
* 被测对象:真实源文件 Drivers/BSP/GD5F2GQ5UE/nand_ftl.c(以 #include 原样纳入,
|
||
* 保证测的是生产代码而非副本)。底层 SPI 原语由本文件提供的
|
||
* **RAM 假芯片**(stub/gd5f2gq5ue.h 声明的同名 API)实现。
|
||
*
|
||
* 为什么要主机侧单测(而不是只用板端套件):
|
||
* 移植层(dhara HAL 六个入口)是**纯地址算术 + 边界判定**,与 SPI 时序无关;
|
||
* 而它要防的两类缺陷恰恰是真机**极难复现**的:
|
||
* 1. is_free 只看页首 64 B → "页首 0xFF、后段有数据"的页被误判空闲
|
||
* → dhara 往已编程页再编程 = 静默数据丢失。真机上要恰好撞上那种页形态
|
||
* 才发作,且发作后只表现为"某次复位后丢数据",无从定位。
|
||
* 2. 越界页/块号(含 DHARA_PAGE_NONE 哨兵)被地址位截断成真实页
|
||
* → 静默读写/搬走无辜数据。真机上没有任何一条路径会主动喂进越界号,
|
||
* 只有被污染的 meta 或将来改动才会触发——主机侧一次构造即钉死。
|
||
* 假芯片把"双编程"做成**可数事件**(is_free 精度回归探针),并记录全部访问次数,
|
||
* 于是"越界不产生任何芯片访问""越界标坏块次数为 0"这类判据可机械断言
|
||
* ——这是板端永远拿不到的观测面。
|
||
*
|
||
* 编码:字符串字面量**纯 ASCII**(中文只进注释),与 cfg_file_test.c 同口径;
|
||
* 主机侧由 harness 的 /utf-8 保证注释里的中文被正确解码。
|
||
*
|
||
* 用例前缀:TC-NP-NNN(NP = NAND Port);分组:
|
||
* TC-NANDPORT-000 假芯片自洽(几何 / 上电态)
|
||
* TC-NANDPORT-100..108 dhara_nand_is_free 整页扫描
|
||
* TC-NANDPORT-110..112 dhara_nand_is_free 越界守卫
|
||
* TC-NANDPORT-200..207 dhara_nand_prog(越界 / NULL / 往返 / 双编程探针 / 失败注入)
|
||
* TC-NANDPORT-300..307 dhara_nand_read(越界 / 越窗 offset+length / 部分读 / ECC / 读失败保守判据)
|
||
* TC-NANDPORT-400..405 dhara_nand_copy(src 与 dst 双端守卫 / 正常搬页)
|
||
* TC-NANDPORT-500..504 dhara_nand_erase(越界块零标坏块 / 整块回 0xFF / 失败注入)
|
||
* TC-NANDPORT-600..605 dhara_nand_is_bad / dhara_nand_mark_bad(越界保守判坏、BBT 不变)
|
||
* TC-NANDPORT-700 dhara map 级场景(写入 -> 重新 resume,双编程计数为 0)
|
||
*
|
||
* 通过:全部 CHECK 通过(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-15
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
|
||
#include "dbg_log.h"
|
||
#include "gd5f2gq5ue.h"
|
||
|
||
/* DBG_* 计数(stub/dbg_log.h 声明) */
|
||
int g_nandtest_err_count;
|
||
int g_nandtest_info_count;
|
||
int g_nandtest_debug_count;
|
||
|
||
/* ======================== RAM 假芯片 ========================
|
||
*
|
||
* 形态:一块 32 块 x 64 页 x 2048 B = 4 MB 的字节数组,上电全 0xFF。
|
||
* 语义保真点:
|
||
* - 读:gd5f_page_read(pg) 把该页**锁进内部缓存**,之后必须由
|
||
* gd5f_read_from_cache(column, buf, len) 按列取数;未锁页就取数、
|
||
* 或列区间越过页尾 → 返回失败(真实芯片同样是"先 13h 后 0Bh"两段式);
|
||
* - 写:gd5f_program_load 只写"数据寄存器",gd5f_program_exec 才落到阵列,
|
||
* 且落盘是 **NOR 叠加**(new = old & written)——真实 NAND 的位语义;
|
||
* - 严格探针:exec 时若目标页已有非 0xFF 字节,记一次**双编程违规**
|
||
* (g_fake_double_program_cnt)。这是抓 is_free 整页精度回归的核心:任何
|
||
* "把已编程页判成空闲"的实现都会让它立刻非 0;
|
||
* - 擦除:整块回 0xFF;坏块表是 RAM 位图,mark_bad 记录次数与块号序列。
|
||
*/
|
||
|
||
#define FAKE_BLOCKS ((uint32_t)GD5F_TOTAL_BLOCKS) /* 32 */
|
||
#define FAKE_PAGES_PER_BLOCK ((uint32_t)GD5F_PAGES_PER_BLOCK) /* 64 */
|
||
#define FAKE_PAGE_SIZE ((uint32_t)GD5F_PAGE_SIZE) /* 2048 */
|
||
#define FAKE_TOTAL_PAGES (FAKE_BLOCKS * FAKE_PAGES_PER_BLOCK) /* 2048 */
|
||
#define FAKE_USABLE_BLOCKS (FAKE_BLOCKS - (uint32_t)GD5F_BBT_POOL_COUNT) /* 28 */
|
||
|
||
/* 阵列 + 数据寄存器 + 内部读缓存锁 */
|
||
static uint8_t s_chip[FAKE_TOTAL_PAGES][FAKE_PAGE_SIZE];
|
||
static uint8_t s_load_reg[FAKE_PAGE_SIZE];
|
||
static int32_t s_cache_page = -1; /* gd5f_page_read 锁进的页;-1 = 无 */
|
||
static int s_load_valid;
|
||
|
||
/* 坏块表(RAM) */
|
||
static uint8_t s_bad[FAKE_BLOCKS];
|
||
|
||
/* 访问计数(供"越界不产生任何芯片访问"断言) */
|
||
static unsigned long g_fake_write_enable_cnt;
|
||
static unsigned long g_fake_page_read_cnt;
|
||
static unsigned long g_fake_read_cache_cnt;
|
||
static unsigned long g_fake_check_ecc_cnt;
|
||
static unsigned long g_fake_program_load_cnt;
|
||
static unsigned long g_fake_program_exec_cnt;
|
||
static unsigned long g_fake_block_erase_cnt;
|
||
static unsigned long g_fake_is_bad_cnt;
|
||
static unsigned long g_fake_mark_bad_cnt;
|
||
static unsigned long g_fake_isfree_scan_cnt; /* 64 B 列读次数(is_free 指纹) */
|
||
|
||
/* 双编程探针:往已编程页再编程的次数与页号 */
|
||
static unsigned long g_fake_double_program_cnt;
|
||
static uint32_t g_fake_last_double_program_page;
|
||
|
||
/* mark_bad 记账(次数 + 块号序列) */
|
||
#define FAKE_MARKBAD_LOG 16u
|
||
static uint32_t g_fake_mark_bad_log[FAKE_MARKBAD_LOG];
|
||
static unsigned long g_fake_mark_bad_log_n;
|
||
|
||
/* 故障注入(复现真机极难构造的 I/O 失败) */
|
||
static int s_fail_page_read;
|
||
static int s_fail_read_cache;
|
||
static int s_fail_check_ecc;
|
||
static int s_fail_program_load;
|
||
static int s_fail_program_exec;
|
||
static int s_fail_block_erase;
|
||
|
||
/* ---- SPI 原语(stub/gd5f2gq5ue.h 声明,签名与真实驱动逐字一致) ---- */
|
||
|
||
int32_t gd5f_write_enable(void) {
|
||
g_fake_write_enable_cnt++;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_page_read(uint32_t page_addr) {
|
||
g_fake_page_read_cnt++;
|
||
if (page_addr >= FAKE_TOTAL_PAGES) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (s_fail_page_read != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
s_cache_page = (int32_t)page_addr;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, uint32_t size) {
|
||
g_fake_read_cache_cnt++;
|
||
/* 64 B 列读 = dhara_nand_is_free 分块扫描的形状指纹。
|
||
* dhara 其它读路径的尺寸是 16(header)/ 132(meta)/ 2048(整页),
|
||
* 故本计数可用来证明场景用例确实走到了 is_free(而不是"没跑到也算过")。 */
|
||
if (size == 64u) {
|
||
g_fake_isfree_scan_cnt++;
|
||
}
|
||
if (p_buf == NULL) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (s_cache_page < 0) {
|
||
return GD5F_ERROR; /* 未先 page_read 就取缓存 = 无数据 */
|
||
}
|
||
if (s_fail_read_cache != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (((uint32_t)column + size) > FAKE_PAGE_SIZE) {
|
||
return GD5F_ERROR; /* 列越界 */
|
||
}
|
||
(void)memcpy(p_buf, &s_chip[(uint32_t)s_cache_page][column], size);
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_check_ecc(void) {
|
||
g_fake_check_ecc_cnt++;
|
||
return (s_fail_check_ecc != 0) ? GD5F_ECC_ERROR : GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_program_load(uint16_t column, const uint8_t *p_buf, uint32_t size) {
|
||
g_fake_program_load_cnt++;
|
||
if (p_buf == NULL) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (s_fail_program_load != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (((uint32_t)column + size) > FAKE_PAGE_SIZE) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (column == 0u) {
|
||
(void)memset(s_load_reg, 0xFF, sizeof(s_load_reg));
|
||
}
|
||
(void)memcpy(&s_load_reg[column], p_buf, size);
|
||
s_load_valid = 1;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_program_exec(uint32_t page_addr) {
|
||
uint32_t i;
|
||
|
||
g_fake_program_exec_cnt++;
|
||
if (page_addr >= FAKE_TOTAL_PAGES) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (s_load_valid == 0) {
|
||
return GD5F_ERROR; /* 没装载就 exec = 命令序列错 */
|
||
}
|
||
if (s_fail_program_exec != 0) {
|
||
return GD5F_PROGRAM_FAIL;
|
||
}
|
||
|
||
/* 严格语义探针:目标页已有非 0xFF 字节 = 双编程(is_free 误判的现场证据) */
|
||
for (i = 0; i < FAKE_PAGE_SIZE; i++) {
|
||
if (s_chip[page_addr][i] != 0xFFu) {
|
||
g_fake_double_program_cnt++;
|
||
g_fake_last_double_program_page = page_addr;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* NOR 叠加:真实 NAND 只能把 1 写成 0,双编程的结果是位碰撞 */
|
||
for (i = 0; i < FAKE_PAGE_SIZE; i++) {
|
||
s_chip[page_addr][i] = (uint8_t)(s_chip[page_addr][i] & s_load_reg[i]);
|
||
}
|
||
|
||
s_load_valid = 0;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f_block_erase(uint32_t block_addr) {
|
||
g_fake_block_erase_cnt++;
|
||
if (block_addr >= FAKE_BLOCKS) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (s_fail_block_erase != 0) {
|
||
return GD5F_ERASE_FAIL;
|
||
}
|
||
(void)memset(&s_chip[block_addr * FAKE_PAGES_PER_BLOCK][0], 0xFF,
|
||
(size_t)FAKE_PAGES_PER_BLOCK * FAKE_PAGE_SIZE);
|
||
return GD5F_OK;
|
||
}
|
||
|
||
int32_t gd5f2gq5ue_is_block_bad(uint32_t block) {
|
||
g_fake_is_bad_cnt++;
|
||
if (block >= FAKE_BLOCKS) {
|
||
return 1; /* 越界:保守判坏(与真实驱动同口径) */
|
||
}
|
||
return (s_bad[block] != 0u) ? 1 : 0;
|
||
}
|
||
|
||
void gd5f2gq5ue_mark_block_bad(uint32_t block) {
|
||
g_fake_mark_bad_cnt++;
|
||
if (g_fake_mark_bad_log_n < FAKE_MARKBAD_LOG) {
|
||
g_fake_mark_bad_log[g_fake_mark_bad_log_n] = block;
|
||
}
|
||
g_fake_mark_bad_log_n++;
|
||
if (block < FAKE_BLOCKS) {
|
||
s_bad[block] = 1u;
|
||
}
|
||
}
|
||
|
||
uint32_t gd5f_get_usable_blocks(void) {
|
||
return FAKE_USABLE_BLOCKS;
|
||
}
|
||
|
||
int gd5f_dma_buf_ok(const void *p_buf, size_t size) {
|
||
/* PC 上没有"DMA 不可达内存"这种物理形态;恒返 1 让 disk_initialize 继续。
|
||
* 该守卫本身由 gd5f2gq5ue.c 负责,不在本套件射程内。 */
|
||
(void)p_buf;
|
||
(void)size;
|
||
return 1;
|
||
}
|
||
|
||
/* ---- 假芯片辅助(测试脚手架,非驱动 API) ---- */
|
||
|
||
/* 复位到"刚上电":阵列全 0xFF、坏块表清空、全部计数与注入清零 */
|
||
static void fake_chip_reset(void) {
|
||
(void)memset(s_chip, 0xFF, sizeof(s_chip));
|
||
(void)memset(s_load_reg, 0xFF, sizeof(s_load_reg));
|
||
(void)memset(s_bad, 0, sizeof(s_bad));
|
||
(void)memset(g_fake_mark_bad_log, 0, sizeof(g_fake_mark_bad_log));
|
||
s_cache_page = -1;
|
||
s_load_valid = 0;
|
||
g_fake_write_enable_cnt = 0u;
|
||
g_fake_page_read_cnt = 0u;
|
||
g_fake_read_cache_cnt = 0u;
|
||
g_fake_check_ecc_cnt = 0u;
|
||
g_fake_program_load_cnt = 0u;
|
||
g_fake_program_exec_cnt = 0u;
|
||
g_fake_block_erase_cnt = 0u;
|
||
g_fake_is_bad_cnt = 0u;
|
||
g_fake_mark_bad_cnt = 0u;
|
||
g_fake_isfree_scan_cnt = 0u;
|
||
g_fake_mark_bad_log_n = 0u;
|
||
g_fake_double_program_cnt = 0u;
|
||
g_fake_last_double_program_page = 0xFFFFFFFFu;
|
||
s_fail_page_read = 0;
|
||
s_fail_read_cache = 0;
|
||
s_fail_check_ecc = 0;
|
||
s_fail_program_load = 0;
|
||
s_fail_program_exec = 0;
|
||
s_fail_block_erase = 0;
|
||
g_nandtest_err_count = 0;
|
||
g_nandtest_info_count = 0;
|
||
g_nandtest_debug_count = 0;
|
||
}
|
||
|
||
/* 全部芯片侧访问次数之和("越界不产生任何芯片访问"的判据量) */
|
||
static unsigned long fake_chip_access_total(void) {
|
||
return g_fake_write_enable_cnt + g_fake_page_read_cnt + g_fake_read_cache_cnt +
|
||
g_fake_check_ecc_cnt + g_fake_program_load_cnt + g_fake_program_exec_cnt +
|
||
g_fake_block_erase_cnt + g_fake_is_bad_cnt + g_fake_mark_bad_cnt;
|
||
}
|
||
|
||
/* 绕过驱动语义直写一页(构造"已编程页"的初始形态,不计双编程) */
|
||
static void fake_poke_page(uint32_t page, const uint8_t *p_src, uint32_t len) {
|
||
if (page < FAKE_TOTAL_PAGES) {
|
||
(void)memcpy(&s_chip[page][0], p_src, len);
|
||
}
|
||
}
|
||
|
||
/* 整页是否仍为擦除态 */
|
||
static int fake_page_all_ff(uint32_t page) {
|
||
uint32_t i;
|
||
|
||
if (page >= FAKE_TOTAL_PAGES) {
|
||
return 0;
|
||
}
|
||
for (i = 0; i < FAKE_PAGE_SIZE; i++) {
|
||
if (s_chip[page][i] != 0xFFu) {
|
||
return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* 整页是否与给定内容一致 */
|
||
static int fake_page_equals(uint32_t page, const uint8_t *p_src, uint32_t len) {
|
||
if (page >= FAKE_TOTAL_PAGES) {
|
||
return 0;
|
||
}
|
||
return memcmp(&s_chip[page][0], p_src, len) == 0;
|
||
}
|
||
|
||
/* 块内所有页是否都是擦除态 */
|
||
static int fake_block_all_ff(uint32_t block) {
|
||
uint32_t i;
|
||
|
||
if (block >= FAKE_BLOCKS) {
|
||
return 0;
|
||
}
|
||
for (i = 0; i < FAKE_PAGES_PER_BLOCK; i++) {
|
||
if (!fake_page_all_ff(block * FAKE_PAGES_PER_BLOCK + i)) {
|
||
return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* 生成可复现的伪随机页内容(非 0xFF 密集,用于往返比对) */
|
||
static void fake_fill_pattern(uint8_t *p_buf, uint32_t len, uint32_t seed) {
|
||
uint32_t i;
|
||
uint32_t x = seed | 1u;
|
||
|
||
for (i = 0; i < len; i++) {
|
||
x = (x * 1664525u) + 1013904223u;
|
||
p_buf[i] = (uint8_t)(x >> 24);
|
||
}
|
||
}
|
||
|
||
/* 坏块表快照 / 比对(断言"越界 mark_bad 后坏块表不变") */
|
||
static uint8_t s_bad_snapshot[FAKE_BLOCKS];
|
||
|
||
static void fake_bad_snapshot(void) {
|
||
(void)memcpy(s_bad_snapshot, s_bad, sizeof(s_bad_snapshot));
|
||
}
|
||
|
||
static int fake_bad_unchanged(void) {
|
||
return memcmp(s_bad_snapshot, s_bad, sizeof(s_bad_snapshot)) == 0;
|
||
}
|
||
|
||
/* 复位假芯片并在页 64(块 1 首页)铺一份已知内容:read / copy 用例的公共前置。
|
||
* 每次 fake_chip_reset() 后阵列回到 0xFF,**必须重新铺数据**,否则"内容比对"
|
||
* 会拿全 0xFF 去比模式串——测试自己假绿。 */
|
||
#define UT_SRC_PAGE 64u
|
||
|
||
static void prep_content_page(uint8_t *p_buf, uint32_t seed) {
|
||
fake_chip_reset();
|
||
fake_fill_pattern(p_buf, FAKE_PAGE_SIZE, seed);
|
||
fake_poke_page(UT_SRC_PAGE, p_buf, FAKE_PAGE_SIZE);
|
||
}
|
||
|
||
/* ===================== 断言 ===================== */
|
||
static int s_total;
|
||
static int s_passed;
|
||
static int s_failed;
|
||
|
||
#define CHECK(cond, name) do { \
|
||
s_total++; \
|
||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||
} while (0)
|
||
|
||
/* ===================== 被测代码(原样纳入生产源) ===================== */
|
||
/*
|
||
* MSVC /W4 下的**主机平台专属**噪音,与 MCU 构建(ARMCC,size_t = 32 bit)无关,
|
||
* 只在这几处 include 周围关闭;本测试单元自身仍保持 /W4 全开:
|
||
* 4267 nand_ftl.c:277 把 size_t length 交给 uint32_t 形参——x64 上 size_t 是
|
||
* 64 位才成立,ARM 上同宽(ARMCC 构建 0 错 0 警);
|
||
* 4244 / 4334 / 4100 / 4701 第三方 Lib/dhara(本仓裁剪版,只读、cppcheck 亦排除)。
|
||
*/
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(push)
|
||
#pragma warning(disable : 4267)
|
||
#endif
|
||
#include "nand_ftl.c"
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(pop)
|
||
#endif
|
||
|
||
/* dhara 核心(真实源,非桩):nand_ftl.c 引用 dhara_map_*,必须一并纳入才能链接。
|
||
* 纳入后本编译单元 = 移植层 + 它服务的 FTL 本体,故 dhara map 级场景
|
||
* (TC-NANDPORT-700 写入 -> 重新 resume)测的是真链路。顺序:journal 在 map 之前。 */
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(push)
|
||
#pragma warning(disable : 4100 4244 4334 4701)
|
||
#endif
|
||
#include "journal.c"
|
||
#include "map.c"
|
||
#if defined(_MSC_VER)
|
||
#pragma warning(pop)
|
||
#endif
|
||
|
||
/* ===================== 测试几何 =====================
|
||
* 与 disk_initialize 里给 s_nand 的赋值逐项一致(log2_page_size=11 -> 2048 B、
|
||
* log2_ppb=6 -> 64 页/块),块数取假芯片的 dhara 可见块数(32 - 4 保留块池)。
|
||
* FTL_START_BLOCK = 0 -> 逻辑页号 == 物理页号,越界期望值可直接算。 */
|
||
static struct dhara_nand s_ut_nand;
|
||
|
||
#define UT_TOTAL_PAGES ((dhara_page_t)s_ut_nand.num_blocks << s_ut_nand.log2_ppb)
|
||
#define UT_OOB_PAGE (UT_TOTAL_PAGES) /* 第一个越界页 */
|
||
#define UT_OOB_BLOCK ((dhara_block_t)s_ut_nand.num_blocks)
|
||
|
||
/* ===================== 旧算法对照(is_free 误判的"牙齿") =====================
|
||
*
|
||
* 整页精度修复**之前**的 dhara_nand_is_free:只读页首 64 B 就回答"空闲"。
|
||
* 本函数按当时的形状内联一份(同样经假芯片的两段式读),用来证明本套件**有牙**:
|
||
* 对同一页,旧算法答 1(误判空闲)、新实现答 0。将来有人把 is_free 改回只看
|
||
* 页首,TC-NANDPORT-105 立刻变红——而不是"测试跟着实现一起改回去"。
|
||
*/
|
||
static int old_is_free_first64(const struct dhara_nand *n, dhara_page_t p) {
|
||
uint8_t buf[64];
|
||
uint32_t i;
|
||
|
||
(void)n;
|
||
if (gd5f_page_read(ftl_phy_page(p)) != GD5F_OK) {
|
||
return 0;
|
||
}
|
||
if (gd5f_read_from_cache(0u, buf, sizeof(buf)) != GD5F_OK) {
|
||
return 0;
|
||
}
|
||
for (i = 0; i < sizeof(buf); i++) {
|
||
if (buf[i] != 0xFFu) {
|
||
return 0;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-100..108:dhara_nand_is_free(整页扫描) =====================
|
||
*
|
||
* 旧实现只看页首 64 B,会把"页首 0xFF、后段有数据"的已编程页误判空闲
|
||
* -> dhara 往已编程页再编程 = 数据丢失。本组把"必须扫完整页"钉死,
|
||
* 并用 TC-NANDPORT-105 的旧算法对照证明这组断言真的能咬人。
|
||
*/
|
||
static void tc_is_free(void) {
|
||
static uint8_t page_buf[FAKE_PAGE_SIZE];
|
||
struct dhara_nand zeroed;
|
||
|
||
/* TC-NANDPORT-100:整页擦除 -> 1 */
|
||
printf("[TC-NANDPORT-100] erased page is free\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 0u) == 1, "erased page -> is_free = 1");
|
||
CHECK(g_fake_page_read_cnt == 1u, "one page_read only (no per-byte commands)");
|
||
CHECK(g_fake_read_cache_cnt == (FAKE_PAGE_SIZE / 64u),
|
||
"erased page scans the whole page = 32 column reads (whole-page scan evidence)");
|
||
CHECK(g_fake_isfree_scan_cnt == (FAKE_PAGE_SIZE / 64u),
|
||
"64B column-read fingerprint calibrated (proves scenario really calls is_free)");
|
||
|
||
/* TC-NANDPORT-101:第 0 字节有数据 -> 0(快路径) */
|
||
printf("[TC-NANDPORT-101] byte 0 programmed -> not free\n");
|
||
fake_chip_reset();
|
||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||
page_buf[0] = 0x00u;
|
||
fake_poke_page(1u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 1u) == 0, "byte 0 programmed -> is_free = 0");
|
||
CHECK(g_fake_read_cache_cnt == 1u,
|
||
"early exit on first chunk = fast path (same cost as the old impl)");
|
||
|
||
/* TC-NANDPORT-102:is_free 整页精度回归用例
|
||
* 页首 64 B 全 0xFF、第 1000 字节 = 0x00 -> 必须答"非空"。
|
||
* 旧实现只看页首 64 B 会误判空闲,dhara 遂往这一页再编程。 */
|
||
printf("[TC-NANDPORT-102] is_free regression: hole in first 64B, data at byte 1000\n");
|
||
fake_chip_reset();
|
||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||
page_buf[1000] = 0x00u;
|
||
fake_poke_page(2u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 2u) == 0,
|
||
"first 64B all 0xFF, byte 1000 = 0x00 -> is_free = 0 (whole-page scan)");
|
||
CHECK(g_fake_read_cache_cnt > 1u,
|
||
"scan continues past the first 64B (old impl stopped here = root cause)");
|
||
|
||
/* TC-NANDPORT-103:只有最后一个字节非 0xFF -> 0 */
|
||
printf("[TC-NANDPORT-103] only last byte programmed -> not free\n");
|
||
fake_chip_reset();
|
||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||
page_buf[FAKE_PAGE_SIZE - 1u] = 0x5Au;
|
||
fake_poke_page(3u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 3u) == 0,
|
||
"only the very last byte non-0xFF -> is_free = 0");
|
||
CHECK(g_fake_read_cache_cnt == (FAKE_PAGE_SIZE / 64u),
|
||
"full scan to the page tail before answering (32 column reads)");
|
||
|
||
/* TC-NANDPORT-104:只有第 64 字节非 0xFF -> 0(旧窗口右边界外的第一个字节) */
|
||
printf("[TC-NANDPORT-104] only byte 64 programmed -> not free\n");
|
||
fake_chip_reset();
|
||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||
page_buf[64u] = 0x00u;
|
||
fake_poke_page(4u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 4u) == 0,
|
||
"only byte 64 (first byte outside the old window) -> is_free = 0");
|
||
|
||
/* TC-NANDPORT-105:旧算法对照(证明本套件有牙)
|
||
* 同一页形态:旧算法答 1 = 误判空闲,新实现答 0。
|
||
* 任何人把 is_free 改回"只看页首",这条立刻红。 */
|
||
printf("[TC-NANDPORT-105] old-algorithm contrast (test has teeth)\n");
|
||
fake_chip_reset();
|
||
(void)memset(page_buf, 0xFF, sizeof(page_buf));
|
||
page_buf[1000] = 0x00u;
|
||
fake_poke_page(5u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(old_is_free_first64(&s_ut_nand, 5u) == 1,
|
||
"old 64B-only algorithm misjudges the same page as free -> 1");
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 5u) == 0,
|
||
"new implementation answers not-free -> 0 (opposite verdict = teeth)");
|
||
|
||
/* TC-NANDPORT-106:读失败保守判非空 */
|
||
printf("[TC-NANDPORT-106] read failure is answered conservatively\n");
|
||
fake_chip_reset();
|
||
s_fail_page_read = 1;
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 6u) == 0,
|
||
"page_read failure -> not free (never treat unreadable as free)");
|
||
fake_chip_reset();
|
||
s_fail_read_cache = 1;
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 7u) == 0,
|
||
"read_from_cache failure -> not free (conservative)");
|
||
|
||
/* TC-NANDPORT-107:NULL / num_blocks = 0 的几何守卫 */
|
||
printf("[TC-NANDPORT-107] NULL geometry / zero blocks guard\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_free(NULL, 0u) == 0, "n = NULL -> not free, no crash");
|
||
CHECK(fake_chip_access_total() == 0u, "n = NULL causes zero chip access");
|
||
zeroed.log2_page_size = 11u;
|
||
zeroed.log2_ppb = 6u;
|
||
zeroed.num_blocks = 0u;
|
||
CHECK(dhara_nand_is_free(&zeroed, 0u) == 0, "num_blocks = 0 -> not free");
|
||
CHECK(fake_chip_access_total() == 0u, "num_blocks = 0 causes zero chip access");
|
||
|
||
/* TC-NANDPORT-108:擦除后重新判空(假芯片语义自洽,防"擦除没生效"假绿) */
|
||
printf("[TC-NANDPORT-108] fake chip erase really clears the array\n");
|
||
fake_chip_reset();
|
||
fake_fill_pattern(page_buf, FAKE_PAGE_SIZE, 7u);
|
||
fake_poke_page(8u, page_buf, FAKE_PAGE_SIZE);
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 8u) == 0, "programmed page -> not free");
|
||
CHECK(gd5f_block_erase(0u) == GD5F_OK, "erase the containing block (helper)");
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, 8u) == 1, "after erase -> free (fake chip sane)");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-110..112:is_free 越界守卫 ===================== */
|
||
static void tc_is_free_oob(void) {
|
||
printf("[TC-NANDPORT-110] is_free out-of-range pages\n");
|
||
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, DHARA_PAGE_NONE) == 0,
|
||
"DHARA_PAGE_NONE -> is_free = 0");
|
||
CHECK(fake_chip_access_total() == 0u,
|
||
"DHARA_PAGE_NONE causes zero chip access (no truncated address)");
|
||
|
||
printf("[TC-NANDPORT-111] is_free at total_pages boundary\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, UT_OOB_PAGE) == 0,
|
||
"page == total_pages -> is_free = 0");
|
||
CHECK(fake_chip_access_total() == 0u, "page == total_pages -> zero chip access");
|
||
|
||
printf("[TC-NANDPORT-112] is_free beyond total_pages\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_free(&s_ut_nand, UT_OOB_PAGE + 1u) == 0,
|
||
"page == total_pages + 1 -> is_free = 0");
|
||
CHECK(fake_chip_access_total() == 0u, "page == total_pages + 1 -> zero chip access");
|
||
}
|
||
|
||
/* 无任何写侧芯片访问("越界零写入"的判据量) */
|
||
static int fake_no_write_access(void) {
|
||
return (g_fake_write_enable_cnt == 0u) && (g_fake_program_load_cnt == 0u) &&
|
||
(g_fake_program_exec_cnt == 0u) && (g_fake_block_erase_cnt == 0u);
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-200..205:dhara_nand_prog ===================== */
|
||
static void tc_prog(void) {
|
||
static uint8_t page_buf[FAKE_PAGE_SIZE];
|
||
dhara_error_t err;
|
||
|
||
/* TC-NANDPORT-200:越界页 -> -1 + CORRUPT_MAP + 零写入 */
|
||
printf("[TC-NANDPORT-200] prog out-of-range page -> -1, zero write\n");
|
||
fake_chip_reset();
|
||
fake_fill_pattern(page_buf, FAKE_PAGE_SIZE, 11u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, UT_OOB_PAGE, page_buf, &err) == -1,
|
||
"prog page == total_pages -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "prog out-of-range -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_no_write_access(), "prog out-of-range -> zero write access");
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "prog out-of-range -> no block marked bad");
|
||
|
||
/* TC-NANDPORT-201:DHARA_PAGE_NONE -> -1 + CORRUPT_MAP + 零写入
|
||
* 旧实现会把 0xffffffff 加偏移送进 SPI 命令,被地址位截断成某个真实页
|
||
* -> 静默写坏无辜数据;现在必须在守卫处就挡住。 */
|
||
printf("[TC-NANDPORT-201] prog DHARA_PAGE_NONE -> -1, zero write\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, DHARA_PAGE_NONE, page_buf, &err) == -1,
|
||
"prog DHARA_PAGE_NONE -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "prog DHARA_PAGE_NONE -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_chip_access_total() == 0u, "prog DHARA_PAGE_NONE -> zero chip access");
|
||
|
||
/* TC-NANDPORT-202:data == NULL -> -1(守卫顺带挡住空指针) */
|
||
printf("[TC-NANDPORT-202] prog with NULL data -> -1\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, 0u, NULL, &err) == -1, "prog data = NULL -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "prog data = NULL -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_no_write_access(), "prog data = NULL -> zero write access");
|
||
|
||
/* TC-NANDPORT-203:正常页 -> 0 且回读一致 */
|
||
printf("[TC-NANDPORT-203] prog a valid page -> 0, read-back matches\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, 64u, page_buf, &err) == 0, "prog page 64 -> 0");
|
||
CHECK(err == DHARA_E_NONE, "prog success leaves err = NONE");
|
||
CHECK(fake_page_equals(64u, page_buf, FAKE_PAGE_SIZE), "prog page 64 content on chip");
|
||
CHECK(g_fake_double_program_cnt == 0u, "prog onto an erased page -> no violation");
|
||
|
||
/* TC-NANDPORT-204:最后一个合法页(上界内边界) */
|
||
printf("[TC-NANDPORT-204] prog last valid page (boundary inside)\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, UT_TOTAL_PAGES - 1u, page_buf, &err) == 0,
|
||
"prog page == total_pages - 1 -> 0");
|
||
CHECK(fake_page_equals(UT_TOTAL_PAGES - 1u, page_buf, FAKE_PAGE_SIZE),
|
||
"last valid page content on chip");
|
||
|
||
/* TC-NANDPORT-205:双编程探针自检——同一页再编程必须被记违规(防探针是死码) */
|
||
printf("[TC-NANDPORT-205] double-program probe really fires\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
(void)dhara_nand_prog(&s_ut_nand, 128u, page_buf, &err);
|
||
(void)dhara_nand_prog(&s_ut_nand, 128u, page_buf, &err);
|
||
CHECK(g_fake_double_program_cnt == 1u, "reprogramming a programmed page -> 1 violation");
|
||
CHECK(g_fake_last_double_program_page == 128u, "violation records the offending page");
|
||
|
||
/* TC-NANDPORT-206:编程失败 -> BAD_BLOCK + 标坏该块(错误分类不被守卫改动破坏) */
|
||
printf("[TC-NANDPORT-206] program failure -> BAD_BLOCK + mark bad\n");
|
||
fake_chip_reset();
|
||
s_fail_program_exec = 1;
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, 2u * 64u, page_buf, &err) == -1,
|
||
"program_exec failure -> -1");
|
||
CHECK(err == DHARA_E_BAD_BLOCK, "program_exec failure -> DHARA_E_BAD_BLOCK");
|
||
CHECK(g_fake_mark_bad_cnt == 1u, "program failure marks exactly one block bad");
|
||
CHECK(g_fake_mark_bad_log[0] == 2u, "the block marked bad is the page's own block");
|
||
|
||
/* TC-NANDPORT-207:program_load 失败(装载阶段)-> 同样 BAD_BLOCK + 标坏该块,
|
||
* 且在 exec 之前就短路(真实芯片:02h 都没成,10h 自然不发) */
|
||
printf("[TC-NANDPORT-207] program_load failure -> BAD_BLOCK + mark bad\n");
|
||
fake_chip_reset();
|
||
s_fail_program_load = 1;
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_prog(&s_ut_nand, 3u * 64u, page_buf, &err) == -1,
|
||
"program_load failure -> -1");
|
||
CHECK(err == DHARA_E_BAD_BLOCK, "program_load failure -> DHARA_E_BAD_BLOCK");
|
||
CHECK(g_fake_program_exec_cnt == 0u, "load failure short-circuits before program_exec");
|
||
CHECK(g_fake_mark_bad_cnt == 1u, "load failure marks exactly one block bad");
|
||
CHECK(g_fake_mark_bad_log[0] == 3u, "the block marked bad is the page's own block");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-300..307:dhara_nand_read ===================== */
|
||
static void tc_read(void) {
|
||
static uint8_t page_buf[FAKE_PAGE_SIZE];
|
||
static uint8_t rd[FAKE_PAGE_SIZE];
|
||
dhara_error_t err;
|
||
|
||
/* TC-NANDPORT-300/301:越界页 -> -1 + CORRUPT_MAP + 零访问 */
|
||
printf("[TC-NANDPORT-300] read out-of-range page -> -1, zero access\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, UT_OOB_PAGE, 0u, 512u, rd, &err) == -1,
|
||
"read page == total_pages -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "read out-of-range -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_chip_access_total() == 0u, "read out-of-range -> zero chip access");
|
||
|
||
printf("[TC-NANDPORT-301] read DHARA_PAGE_NONE -> -1, zero access\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, DHARA_PAGE_NONE, 0u, 512u, rd, &err) == -1,
|
||
"read DHARA_PAGE_NONE -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "read DHARA_PAGE_NONE -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_chip_access_total() == 0u, "read DHARA_PAGE_NONE -> zero chip access");
|
||
|
||
/* TC-NANDPORT-302:offset + length 越过 2048 -> -1 */
|
||
printf("[TC-NANDPORT-302] read spanning past the page end -> -1\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, 0u, 2000u, 100u, rd, &err) == -1,
|
||
"read offset 2000 + length 100 (> 2048) -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "read overrun -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_chip_access_total() == 0u, "read overrun -> zero chip access");
|
||
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, 0u, FAKE_PAGE_SIZE, 0u, rd, &err) == -1,
|
||
"read offset == 2048 (even with length 0) -> -1");
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, 0u, 0u, FAKE_PAGE_SIZE + 1u, rd, &err) == -1,
|
||
"read length 2049 -> -1");
|
||
|
||
/* TC-NANDPORT-303:NULL data -> -1 */
|
||
printf("[TC-NANDPORT-303] read with NULL data -> -1\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, 0u, 0u, 512u, NULL, &err) == -1,
|
||
"read data = NULL -> -1");
|
||
CHECK(fake_chip_access_total() == 0u, "read data = NULL -> zero chip access");
|
||
|
||
/* TC-NANDPORT-304:正常部分读 -> 0 且内容正确 */
|
||
printf("[TC-NANDPORT-304] partial read returns the right slice\n");
|
||
prep_content_page(page_buf, 21u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, UT_SRC_PAGE, 1024u, 512u, rd, &err) == 0,
|
||
"partial read (offset 1024, len 512) -> 0");
|
||
CHECK(memcmp(rd, &page_buf[1024], 512u) == 0, "partial read returns the right bytes");
|
||
CHECK(g_fake_page_read_cnt == 1u, "partial read issues one page_read");
|
||
|
||
/* TC-NANDPORT-305:整页读 -> 0 且内容正确(边界 length == 2048) */
|
||
printf("[TC-NANDPORT-305] full page read\n");
|
||
prep_content_page(page_buf, 21u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, UT_SRC_PAGE, 0u, FAKE_PAGE_SIZE, rd, &err) == 0,
|
||
"full page read (length == 2048) -> 0");
|
||
CHECK(memcmp(rd, page_buf, FAKE_PAGE_SIZE) == 0, "full page read matches the chip");
|
||
|
||
/* TC-NANDPORT-306:ECC 不可纠正 -> -1 + DHARA_E_ECC */
|
||
printf("[TC-NANDPORT-306] uncorrectable ECC -> -1 + DHARA_E_ECC\n");
|
||
prep_content_page(page_buf, 21u);
|
||
s_fail_check_ecc = 1;
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, UT_SRC_PAGE, 0u, 512u, rd, &err) == -1,
|
||
"check_ecc failure -> -1");
|
||
CHECK(err == DHARA_E_ECC, "check_ecc failure -> DHARA_E_ECC");
|
||
|
||
/* TC-NANDPORT-307:同一保守判据——read_from_cache 失败必须 -1(旧实现返回 0 交垃圾) */
|
||
printf("[TC-NANDPORT-307] read_from_cache failure must not report success\n");
|
||
prep_content_page(page_buf, 21u);
|
||
s_fail_read_cache = 1;
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_read(&s_ut_nand, UT_SRC_PAGE, 0u, 512u, rd, &err) == -1,
|
||
"read_from_cache failure -> -1 (not 0 with garbage data)");
|
||
CHECK(err == DHARA_E_ECC, "read_from_cache failure -> DHARA_E_ECC");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-400..405:dhara_nand_copy ===================== */
|
||
static void tc_copy(void) {
|
||
static uint8_t page_buf[FAKE_PAGE_SIZE];
|
||
dhara_error_t err;
|
||
|
||
/* TC-NANDPORT-400:src 越界 -> -1 + CORRUPT_MAP + 零写入 */
|
||
printf("[TC-NANDPORT-400] copy with out-of-range src -> -1, zero write\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_copy(&s_ut_nand, UT_OOB_PAGE, 100u, &err) == -1,
|
||
"copy src == total_pages -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "copy bad src -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_no_write_access(), "copy bad src -> zero write access");
|
||
CHECK(fake_chip_access_total() == 0u, "copy bad src -> zero chip access at all");
|
||
|
||
/* TC-NANDPORT-401:src = DHARA_PAGE_NONE -> -1 + CORRUPT_MAP */
|
||
printf("[TC-NANDPORT-401] copy with DHARA_PAGE_NONE src -> -1\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_copy(&s_ut_nand, DHARA_PAGE_NONE, 100u, &err) == -1,
|
||
"copy src = DHARA_PAGE_NONE -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "copy DHARA_PAGE_NONE src -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_chip_access_total() == 0u, "copy DHARA_PAGE_NONE src -> zero chip access");
|
||
|
||
/* TC-NANDPORT-402:dst 越界 -> -1 + CORRUPT_MAP + 零写入 + 零标坏块
|
||
* dst 越界最致命:那是一次写入,旧实现会静默写进被截断出来的无辜页。 */
|
||
printf("[TC-NANDPORT-402] copy with out-of-range dst -> -1, zero write, zero mark-bad\n");
|
||
prep_content_page(page_buf, 31u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_copy(&s_ut_nand, UT_SRC_PAGE, UT_OOB_PAGE, &err) == -1,
|
||
"copy dst == total_pages -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "copy bad dst -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_no_write_access(), "copy bad dst -> zero write access");
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "copy bad dst -> no block marked bad");
|
||
|
||
/* TC-NANDPORT-403:dst = DHARA_PAGE_NONE(哨兵漏进来的真实形态) */
|
||
printf("[TC-NANDPORT-403] copy with DHARA_PAGE_NONE dst -> -1, zero write\n");
|
||
prep_content_page(page_buf, 31u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_copy(&s_ut_nand, UT_SRC_PAGE, DHARA_PAGE_NONE, &err) == -1,
|
||
"copy dst = DHARA_PAGE_NONE -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "copy DHARA_PAGE_NONE dst -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(fake_no_write_access(), "copy DHARA_PAGE_NONE dst -> zero write access");
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "copy DHARA_PAGE_NONE dst -> zero mark_bad");
|
||
|
||
/* TC-NANDPORT-404:正常 copy -> 0 且目标页与源一致 */
|
||
printf("[TC-NANDPORT-404] normal copy moves the page content\n");
|
||
prep_content_page(page_buf, 31u);
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_copy(&s_ut_nand, UT_SRC_PAGE, 128u, &err) == 0,
|
||
"copy src -> erased dst -> 0");
|
||
CHECK(err == DHARA_E_NONE, "copy success leaves err = NONE");
|
||
CHECK(fake_page_equals(128u, page_buf, FAKE_PAGE_SIZE), "dst page equals src content");
|
||
CHECK(g_fake_double_program_cnt == 0u, "copy onto an erased page -> no violation");
|
||
|
||
/* TC-NANDPORT-405:copy 到已编程页 -> 探针记下双编程(场景层的判据基础) */
|
||
printf("[TC-NANDPORT-405] copy onto a programmed page is recorded as a violation\n");
|
||
prep_content_page(page_buf, 31u);
|
||
err = DHARA_E_NONE;
|
||
(void)dhara_nand_copy(&s_ut_nand, UT_SRC_PAGE, 128u, &err);
|
||
(void)dhara_nand_copy(&s_ut_nand, UT_SRC_PAGE, 128u, &err);
|
||
CHECK(g_fake_double_program_cnt == 1u, "second copy onto the same dst -> 1 violation");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-500..504:dhara_nand_erase ===================== */
|
||
static void tc_erase(void) {
|
||
static uint8_t page_buf[FAKE_PAGE_SIZE];
|
||
dhara_error_t err;
|
||
uint32_t i;
|
||
|
||
/* TC-NANDPORT-500:越界块 -> -1 + CORRUPT_MAP + 零标坏块 */
|
||
printf("[TC-NANDPORT-500] erase out-of-range block -> -1, zero mark-bad\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_erase(&s_ut_nand, UT_OOB_BLOCK, &err) == -1,
|
||
"erase block == num_blocks -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "erase out-of-range -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "erase out-of-range -> no block marked bad");
|
||
CHECK(fake_chip_access_total() == 0u, "erase out-of-range -> zero chip access");
|
||
|
||
/* TC-NANDPORT-501:块号 = 0xffffffff(哨兵当块号用) */
|
||
printf("[TC-NANDPORT-501] erase block 0xffffffff -> -1\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_erase(&s_ut_nand, 0xFFFFFFFFu, &err) == -1,
|
||
"erase block = 0xffffffff -> -1");
|
||
CHECK(err == DHARA_E_CORRUPT_MAP, "erase block 0xffffffff -> DHARA_E_CORRUPT_MAP");
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "erase block 0xffffffff -> zero mark_bad");
|
||
CHECK(fake_chip_access_total() == 0u, "erase block 0xffffffff -> zero chip access");
|
||
|
||
/* TC-NANDPORT-502:正常擦除 -> 0 且该块全页回 0xFF */
|
||
printf("[TC-NANDPORT-502] erase a valid block clears every page\n");
|
||
fake_chip_reset();
|
||
fake_fill_pattern(page_buf, FAKE_PAGE_SIZE, 41u);
|
||
for (i = 0; i < FAKE_PAGES_PER_BLOCK; i++) {
|
||
fake_poke_page(2u * FAKE_PAGES_PER_BLOCK + i, page_buf, FAKE_PAGE_SIZE);
|
||
}
|
||
CHECK(!fake_block_all_ff(2u), "block 2 programmed before erase");
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_erase(&s_ut_nand, 2u, &err) == 0, "erase block 2 -> 0");
|
||
CHECK(err == DHARA_E_NONE, "erase success leaves err = NONE");
|
||
CHECK(fake_block_all_ff(2u), "after erase every page of block 2 reads 0xFF");
|
||
CHECK(g_fake_block_erase_cnt == 1u, "erase issues one block-erase command");
|
||
|
||
/* TC-NANDPORT-503:最后一个合法块(上界内边界) */
|
||
printf("[TC-NANDPORT-503] erase last valid block (boundary inside)\n");
|
||
fake_chip_reset();
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_erase(&s_ut_nand, UT_OOB_BLOCK - 1u, &err) == 0,
|
||
"erase block == num_blocks - 1 -> 0");
|
||
|
||
/* TC-NANDPORT-504:擦除失败 -> BAD_BLOCK + 标坏该块 */
|
||
printf("[TC-NANDPORT-504] erase failure -> BAD_BLOCK + mark bad\n");
|
||
fake_chip_reset();
|
||
s_fail_block_erase = 1;
|
||
err = DHARA_E_NONE;
|
||
CHECK(dhara_nand_erase(&s_ut_nand, 3u, &err) == -1, "block_erase failure -> -1");
|
||
CHECK(err == DHARA_E_BAD_BLOCK, "block_erase failure -> DHARA_E_BAD_BLOCK");
|
||
CHECK(g_fake_mark_bad_cnt == 1u, "erase failure marks exactly one block bad");
|
||
CHECK(g_fake_mark_bad_log[0] == 3u, "the block marked bad is the one that failed");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-600..605:is_bad / mark_bad ===================== */
|
||
static void tc_bad_block(void) {
|
||
/* TC-NANDPORT-600:越界块 is_bad -> 1 且不碰驱动 */
|
||
printf("[TC-NANDPORT-600] is_bad out-of-range block -> 1, zero access\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_bad(&s_ut_nand, UT_OOB_BLOCK) == 1,
|
||
"is_bad block == num_blocks -> 1 (conservative)");
|
||
CHECK(dhara_nand_is_bad(&s_ut_nand, 0xFFFFFFFFu) == 1,
|
||
"is_bad block = 0xffffffff -> 1 (conservative)");
|
||
CHECK(g_fake_is_bad_cnt == 0u, "is_bad out-of-range never reaches the driver");
|
||
|
||
/* TC-NANDPORT-601:NULL 几何 is_bad -> 1 */
|
||
printf("[TC-NANDPORT-601] is_bad with NULL geometry -> 1\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_bad(NULL, 0u) == 1, "is_bad n = NULL -> 1, no crash");
|
||
CHECK(g_fake_is_bad_cnt == 0u, "is_bad n = NULL -> zero driver access");
|
||
|
||
/* TC-NANDPORT-602:合法好块 -> 0(透传驱动) */
|
||
printf("[TC-NANDPORT-602] is_bad on a valid block passes through\n");
|
||
fake_chip_reset();
|
||
CHECK(dhara_nand_is_bad(&s_ut_nand, 5u) == 0, "good block -> is_bad = 0");
|
||
CHECK(g_fake_is_bad_cnt == 1u, "valid block reaches the driver once");
|
||
|
||
/* TC-NANDPORT-603/604:越界 mark_bad -> 坏块表不变、计数为 0 */
|
||
printf("[TC-NANDPORT-603] mark_bad out-of-range leaves the BBT untouched\n");
|
||
fake_chip_reset();
|
||
fake_bad_snapshot();
|
||
dhara_nand_mark_bad(&s_ut_nand, UT_OOB_BLOCK);
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "mark_bad block == num_blocks -> driver not called");
|
||
CHECK(fake_bad_unchanged(), "mark_bad out-of-range -> BBT unchanged");
|
||
CHECK(fake_chip_access_total() == 0u, "mark_bad out-of-range -> zero chip access");
|
||
|
||
printf("[TC-NANDPORT-604] mark_bad with 0xffffffff leaves the BBT untouched\n");
|
||
fake_chip_reset();
|
||
fake_bad_snapshot();
|
||
dhara_nand_mark_bad(&s_ut_nand, 0xFFFFFFFFu);
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "mark_bad block = 0xffffffff -> driver not called");
|
||
CHECK(fake_bad_unchanged(), "mark_bad 0xffffffff -> BBT unchanged");
|
||
dhara_nand_mark_bad(NULL, 0u);
|
||
CHECK(g_fake_mark_bad_cnt == 0u, "mark_bad n = NULL -> driver not called, no crash");
|
||
|
||
/* TC-NANDPORT-605:合法块 mark_bad -> 驱动收到一次、块号正确、随后 is_bad = 1 */
|
||
printf("[TC-NANDPORT-605] mark_bad on a valid block really marks it\n");
|
||
fake_chip_reset();
|
||
dhara_nand_mark_bad(&s_ut_nand, 7u);
|
||
CHECK(g_fake_mark_bad_cnt == 1u, "valid mark_bad reaches the driver once");
|
||
CHECK(g_fake_mark_bad_log[0] == 7u, "driver receives the expected block number");
|
||
CHECK(dhara_nand_is_bad(&s_ut_nand, 7u) == 1, "marked block now reports bad");
|
||
}
|
||
|
||
/* ===================== TC-NANDPORT-700:dhara map 级场景(真链路) =====================
|
||
*
|
||
* 用真实 dhara_map_* 跑一轮"写入 -> sync -> 重新 resume -> 读回",断言:
|
||
* - 数据往返一致;
|
||
* - **全程双编程计数为 0**:is_free 一旦退回"只看页首 64 B",journal 会把
|
||
* 已编程的检查点页当空闲再编程一次,这个计数立刻非 0(场景级证据)。
|
||
*/
|
||
static void tc_dhara_scenario(void) {
|
||
static uint8_t wr[FAKE_PAGE_SIZE];
|
||
static uint8_t rd[FAKE_PAGE_SIZE];
|
||
dhara_error_t err;
|
||
int rc;
|
||
|
||
printf("[TC-NANDPORT-700] dhara map write -> resume -> read round-trip\n");
|
||
fake_chip_reset();
|
||
s_initialized = 0;
|
||
CHECK(nand_ftl_init() == 0, "first init on a blank chip creates the volume");
|
||
CHECK(s_nand.num_blocks == (unsigned int)FAKE_USABLE_BLOCKS,
|
||
"dhara sees gd5f_get_usable_blocks() blocks");
|
||
|
||
fake_fill_pattern(wr, FAKE_PAGE_SIZE, 0x5EEDu);
|
||
err = DHARA_E_NONE;
|
||
rc = dhara_map_write(&s_map, 0u, wr, &err);
|
||
CHECK(rc == 0, "dhara_map_write sector 0 -> 0");
|
||
err = DHARA_E_NONE;
|
||
rc = dhara_map_write(&s_map, 1u, wr, &err);
|
||
CHECK(rc == 0, "dhara_map_write sector 1 -> 0");
|
||
err = DHARA_E_NONE;
|
||
rc = dhara_map_sync(&s_map, &err);
|
||
CHECK(rc == 0, "dhara_map_sync -> 0");
|
||
CHECK(dhara_map_size(&s_map) == 2u, "map size = 2 sectors after two writes");
|
||
CHECK(g_fake_double_program_cnt == 0u, "write phase: zero double-program violations");
|
||
|
||
/* 模拟掉电重启:FTL 状态复位后重新 resume(同一块假芯片,内容保留) */
|
||
nand_ftl_deinit();
|
||
CHECK(nand_ftl_init() == 0, "re-init resumes the volume");
|
||
CHECK(g_fake_isfree_scan_cnt > 0u,
|
||
"resume really consulted dhara_nand_is_free (64B scan fingerprint > 0)");
|
||
CHECK(dhara_map_size(&s_map) == 2u, "resumed map still holds 2 sectors");
|
||
memset(rd, 0, sizeof(rd));
|
||
err = DHARA_E_NONE;
|
||
rc = dhara_map_read(&s_map, 0u, rd, &err);
|
||
CHECK(rc == 0, "read sector 0 after resume -> 0");
|
||
CHECK(memcmp(rd, wr, FAKE_PAGE_SIZE) == 0, "sector 0 data survives the resume");
|
||
memset(rd, 0, sizeof(rd));
|
||
err = DHARA_E_NONE;
|
||
rc = dhara_map_read(&s_map, 1u, rd, &err);
|
||
CHECK(rc == 0, "read sector 1 after resume -> 0");
|
||
CHECK(memcmp(rd, wr, FAKE_PAGE_SIZE) == 0, "sector 1 data survives the resume");
|
||
CHECK(g_fake_double_program_cnt == 0u,
|
||
"write + resume scenario: zero double-program violations (scenario guard)");
|
||
}
|
||
|
||
/* ===================== main ===================== */
|
||
|
||
int main(void) {
|
||
s_ut_nand.log2_page_size = 11u;
|
||
s_ut_nand.log2_ppb = 6u;
|
||
s_ut_nand.num_blocks = (unsigned int)FAKE_USABLE_BLOCKS;
|
||
|
||
printf("=== NAND Port Tests ===\n");
|
||
printf(" fake chip: %u blocks x %u pages x %u B; dhara sees %u blocks = %lu pages\n",
|
||
FAKE_BLOCKS, FAKE_PAGES_PER_BLOCK, FAKE_PAGE_SIZE,
|
||
s_ut_nand.num_blocks, (unsigned long)UT_TOTAL_PAGES);
|
||
|
||
fake_chip_reset();
|
||
CHECK(FAKE_TOTAL_PAGES == 2048u, "TC-NANDPORT-000 fake chip geometry (32 x 64 pages)");
|
||
CHECK(fake_page_all_ff(0u), "TC-NANDPORT-000b power-on state is all 0xFF");
|
||
CHECK(UT_TOTAL_PAGES == ((dhara_page_t)FAKE_USABLE_BLOCKS * FAKE_PAGES_PER_BLOCK),
|
||
"TC-NANDPORT-000c guard bound = num_blocks << log2_ppb");
|
||
|
||
printf("-- dhara_nand_is_free: whole-page scan --\n");
|
||
tc_is_free();
|
||
|
||
printf("-- dhara_nand_is_free: out-of-range guard --\n");
|
||
tc_is_free_oob();
|
||
|
||
printf("-- dhara_nand_prog --\n");
|
||
tc_prog();
|
||
|
||
printf("-- dhara_nand_read --\n");
|
||
tc_read();
|
||
|
||
printf("-- dhara_nand_copy --\n");
|
||
tc_copy();
|
||
|
||
printf("-- dhara_nand_erase --\n");
|
||
tc_erase();
|
||
|
||
printf("-- dhara_nand_is_bad / dhara_nand_mark_bad --\n");
|
||
tc_bad_block();
|
||
|
||
printf("-- dhara map scenario (real dhara, fake chip) --\n");
|
||
tc_dhara_scenario();
|
||
|
||
printf("\n=== NAND Port Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|