Files
STM32F4-Base/test/host/nand_port/nand_port_test.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

1030 lines
45 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.
/*
* 模块名称: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;
}