GD5F 补 DMA 守卫负路径用例 TC-704/705,导出阈值宏并对齐测试指南

This commit is contained in:
2026-09-21 03:01:49 +08:00
parent 45a3d0d06b
commit 5d386cedfb
4 changed files with 142 additions and 36 deletions
+6 -7
View File
@@ -45,13 +45,12 @@ extern SPI_HandleTypeDef hspi1;
/* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */
volatile uint8_t g_spi1_dma_done;
/* DMA 阈值(字节数超过此值使用 DMA 传输) */
/* DMA 阈值 1024B(2026-09-20):阈值以下走 CPU 轮询。存在把**栈上小缓冲**
* 直接传进本驱动做收发的大小(dhara meta 读 132B、dhara_nand_is_free 64B),
* 任务栈随 FreeRTOS 堆驻留 CCM(0x10000000),而 DMA1/DMA2 物理上不可达
* CCM——此类传输必须留在轮询域,否则静默失败(CCM 首次扩容实验实证的根因)。
* 页级大块传输(2048)仍走 DMA,源/目的全部为文件作用域静态缓冲。 */
#define GD5F_DMA_THRESHOLD 1024
/* DMA 阈值(字节数超过此值使用 DMA 传输):取值定义在头文件,理由如下——
* 阈值以下走 CPU 轮询。存在把**栈上小缓冲**直接传进本驱动做收发的大小
* (dhara meta 读 132B、dhara_nand_is_free 64B),任务栈随 FreeRTOS 堆驻留
* CCM(0x10000000),而 DMA1/DMA2 物理上不可达 CCM——此类传输必须留在轮询域,
* 否则静默失败(CCM 首次扩容实验实证的根因)。页级大块传输(2048)仍走 DMA,
* 源/目的全部为文件作用域静态缓冲。 */
/* DMA 可达内存区间:必须与分散加载文件 RW_IRAM1(0x20000000,128KB,
* SRAM1+SRAM2)一致。CCM(0x10000000,64KB)DMA1/DMA2 物理不可达,
+10
View File
@@ -72,6 +72,16 @@ extern "C" {
#define GD5F_TOTAL_BLOCKS 2048
#define GD5F_TOTAL_SIZE (GD5F_TOTAL_BLOCKS * GD5F_BLOCK_SIZE)
/*
* SPI DMA 传输阈值:超过该字节数走 DMA,否则走 CPU 轮询。
* 取值约束:① 必须 >= 存储路径上可能出现在任务栈的最大缓冲(dhara meta 132B、
* dhara_nand_is_free 64B)——任务栈随 FreeRTOS 堆驻留 CCM(0x10000000),
* 而 DMA1/DMA2 物理不可达 CCM,栈缓冲一旦进 DMA 即静默失败(2026-09-20 实证);
* ② 必须 < 页大小(2048),页级传输保留 DMA。
* 公开此宏供测试用例对齐阈值上下界,避免测试取值与驱动漂移。
*/
#define GD5F_DMA_THRESHOLD 1024
/* 坏块表持久化:保留块池数量(从 NAND 顶部向下挑选出厂好块存储 BBT) */
#define GD5F_BBT_POOL_COUNT 4
+51 -11
View File
@@ -28,7 +28,7 @@
| 总块数 | 2048 | `GD5F_TOTAL_BLOCKS` |
| 总容量 | 256 MB | `GD5F_TOTAL_SIZE` |
| 最小擦除单位 | 1 块(128 KB) | 不可按页/字节擦除 |
| DMA 阈值 | 32 B | `GD5F_DMA_THRESHOLD`,>32B 走 `HAL_SPI_*_DMA` |
| DMA 阈值 | 1024 B | `GD5F_DMA_THRESHOLD`,>1024B 走 `HAL_SPI_*_DMA`;非 SRAM 缓冲由运行时守卫回落轮询(见阶段 7) |
### 寄存器与状态位
@@ -422,9 +422,12 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
---
## 8. 阶段 7:DMA 边界(阈值 32 字节)
## 8. 阶段 7:DMA 边界与 DMA 可达性守卫(阈值 1024 字节)
> 驱动 `GD5F_DMA_THRESHOLD=32`:`size>32` 走 `HAL_SPI_*_DMA`,否则轮询。两路径结果必须一致。
> 驱动 `GD5F_DMA_THRESHOLD=1024`:`size>1024` 走 `HAL_SPI_*_DMA`,否则轮询。两路径结果必须一致。
> **前置约束**:DMA 源/目的缓冲必须落在 SRAM(`RW_IRAM1`,0x20000000~0x2001FFFF)。CCM(0x10000000)
> 为 DMA1/DMA2 物理不可达区,误用会静默不落地;驱动已加运行时守卫 `gd5f_dma_buf_ok()`,
> 越界缓冲自动回落轮询并打印 `[ERR][NAND] … not DMA-reachable … fallback poll`。
### TC-GD5F-701: 阈值下界(轮询路径)
@@ -433,9 +436,9 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **ID** | TC-GD5F-701 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | size=32 走非 DMA 路径,读写正确 |
| **标题** | size=1024(= `GD5F_DMA_THRESHOLD`)走非 DMA 路径,读写正确 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 写/读 32 字节并比对 |
| **测试步骤** | 写/读 1024 字节并比对 |
| **通过标准** | 数据一致 |
| **覆盖原则** | `GD5F_DMA_THRESHOLD` 下界 |
@@ -446,9 +449,9 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **ID** | TC-GD5F-702 |
| **优先级** | P1 |
| **类型** | 边界测试 |
| **标题** | size=33 走 DMA 路径,读写正确 |
| **标题** | size=1025(= `GD5F_DMA_THRESHOLD`+1)走 DMA 路径,读写正确 |
| **前置条件** | 目标块已擦除 |
| **测试步骤** | 写/读 33 字节并比对 |
| **测试步骤** | 写/读 1025 字节并比对 |
| **通过标准** | 数据一致 |
| **覆盖原则** | `GD5F_DMA_THRESHOLD` 上界 |
@@ -465,6 +468,41 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **通过标准** | 数据一致 |
| **覆盖原则** | DMA 大数据路径 |
### TC-GD5F-704: DMA 可达性守卫负路径(读方向)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-704 |
| **优先级** | P0 |
| **类型** | 边界/健壮性测试 |
| **标题** | 目的缓冲位于 CCM(不可达区)时守卫应拒绝 DMA 并回落轮询,数据仍正确 |
| **前置条件** | FreeRTOS 堆驻留 CCM(scatter `RW_CCM`);探针缓冲由 `pvPortMalloc(2048)` 取得(堆不在 CCM 时用例跳过) |
| **测试步骤** | 1. 先用 SRAM 静态缓冲参考读同页<br>2. 探针缓冲预填参考值的反码<br>3. `gd5f2gq5ue_read()` 整页读入 CCM 探针缓冲<br>4. 与参考缓冲逐字节比对 |
| **通过标准** | 数据与参考完全一致,且串口出现 `[ERR][NAND] RX DMA buffer not DMA-reachable (p=0x1000xxxx size=2048), fallback poll` |
| **判定依据** | 本板实证 DMA 对 CCM 静默不落地:若误走 DMA,探针缓冲保持预填反码必失配——故"数据一致"即证明回落真实发生,而非仅"未报错" |
| **覆盖原则** | DMA 可达性守卫(读方向);堆缓冲禁入 DMA 的运行时强制(内存分配策略 R3) |
### TC-GD5F-705: DMA 可达性守卫负路径(写方向)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-705 |
| **优先级** | P0 |
| **类型** | 边界/健壮性测试 |
| **标题** | 源缓冲位于 CCM(不可达区)时守卫应拒绝 DMA 并回落轮询,数据仍正确落盘 |
| **前置条件** | 同 TC-GD5F-704;目标块已擦除 |
| **测试步骤** | 1. 探针缓冲填入 `i^0x5A` 图案<br>2. `gd5f2gq5ue_write()` 整页写入<br>3. 读回 SRAM 静态缓冲逐字节比对 |
| **通过标准** | 写读一致,且串口出现 `[ERR][NAND] TX DMA buffer not DMA-reachable … fallback poll` |
| **判定依据** | 同 TC-GD5F-704(写方向 DMA 读 CCM 同样不落地) |
| **覆盖原则** | DMA 可达性守卫(写方向);堆缓冲禁入 DMA 的运行时强制(内存分配策略 R3) |
> ⚠ **卷破坏警告**:阶段 8 会调用 `nand_ftl_format()` 清空 dhara map,阶段 9 会写入文件——
> 整轮 GD5F 套件(含 Phase 8/9)运行后卷数据即失效,且大写入可能使 dhara 把若干块记为
> 运行期坏块(2026-09-21 实测:一次整轮运行容量 95248 → 95200,日志见
> `[ERR][NAND_FTL] program verify: data mismatch, blk 305`,该块由 dhara 自行隔离,测试仍 68/68 通过)。
> 需要恢复干净态时,启用 `ENABLE_STORAGE_RECOVERY_TESTS` 跑一次 STORAGE 套件的 TC-STO-04
> (重建 BBT + 重新格式化)。
## 9. 阶段 8:FTL 层(nand_ftl)
### TC-GD5F-801: nand_ftl_init 成功
@@ -673,9 +711,11 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| TC-GD5F-603 | block_erase 原语 | P1 | PASS |
| TC-GD5F-604 | write_enable WEL 位 | P2 | PASS |
| TC-GD5F-605 | wait_busy 正常返回 | P2 | PASS |
| TC-GD5F-701 | DMA 阈值下界(32) | P1 | PASS |
| TC-GD5F-702 | DMA 阈值上界(33) | P1 | PASS |
| TC-GD5F-701 | DMA 阈值下界(1024) | P1 | PASS |
| TC-GD5F-702 | DMA 阈值上界(1025) | P1 | PASS |
| TC-GD5F-703 | 大块 DMA 一致性 | P1 | PASS |
| TC-GD5F-704 | DMA 守卫负路径(读,CCM) | P0 | PASS |
| TC-GD5F-705 | DMA 守卫负路径(写,CCM) | P0 | PASS |
| TC-GD5F-801 | nand_ftl_init | P0 | PASS |
| TC-GD5F-802 | nand_ftl_format | P1 | PASS |
| TC-GD5F-901 | FatFS 挂载写读 | P2 | PASS |
@@ -688,7 +728,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| TC-STO-03 | 坏块注入下存储写入 | P1 | PASS |
| TC-STO-04 | BBT 重建 + 重新格式化(恢复) | P1 | 受 `ENABLE_STORAGE_RECOVERY_TESTS` 控制(默认关) |
> 当前实测:`TEST_SUITE_GD5F` 构建一次运行全部阶段,结尾输出 `=== GD5F2GQ5UE Tests: 65/65 PASSED (failed=0) ===`(65 为各 TC 内部断言总数,含 TC-GD5F-205~207 BBT 持久化用例;TC 覆盖见上表)。`TEST_SUITE_STORAGE` 套件结尾输出 `=== Storage Tests: 11/11 PASSED (failed=0) ===`(TC-STO-01/02/03 各含若干内部断言,且 TC-STO-01 依赖的 `s_cached_lpn` 哨兵 bug 已修复)。两套件均 0 错误 0 警告通过 MDK 编译。
> 当前实测:`TEST_SUITE_GD5F` 构建一次运行全部阶段,结尾输出 `=== GD5F2GQ5UE Tests: 68/68 PASSED (failed=0) ===`(68 为各 TC 内部断言总数,含 TC-GD5F-205~207 BBT 持久化用例、TC-GD5F-704/705 DMA 守卫负路径;TC 覆盖见上表)。`TEST_SUITE_STORAGE` 套件结尾输出 `=== Storage Tests: 11/11 PASSED (failed=0) ===`(TC-STO-01/02/03 各含若干内部断言,且 TC-STO-01 依赖的 `s_cached_lpn` 哨兵 bug 已修复)。两套件均 0 错误 0 警告通过 MDK 编译。
---
@@ -733,7 +773,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| 总块数 / 容量 | 2048 / 256 MB |
| ID | MID=0xC8, DID=0x52 |
| 擦除最小单位 | 1 块(128 KB,须对齐) |
| DMA 阈值 | 32 B(>32 走 DMA) |
| DMA 阈值 | 1024 B(>1024 走 DMA;非 SRAM 缓冲由守卫回落轮询) |
| 状态寄存器 | 0xC0;OIP=0, WEL=1, E_FAIL=2, P_FAIL=3, ECCS=4/5 |
| Feature 寄存器 | 0xB0;ECC_EN=4, QE=0 |
| Protect 寄存器 | 0xA0;init 后写 0x00 解除保护 |
+75 -18
View File
@@ -2,8 +2,9 @@
* 模块名称:GD5F2GQ5UE Driver Test Task
* 模块功能:GD5F2GQ5UE 驱动全阶段测试入口,统一在 defaultTask 上下文(同步阻塞)
* 中运行。各阶段由 gd5f_test.h 中的 ENABLE_GD5F_PHASE*_TESTS 宏独立开关,
* 覆盖 init/read_id/reset、BBT、页读写、块擦除、ECC、SPI 原语、DMA 边界、
* FTL 与 FatFS。所有结果通过 GD5F_TEST_CHECK / GD5F_TEST_REPORT 输出串口。
* 覆盖 init/read_id/reset、BBT、页读写、块擦除、ECC、SPI 原语、DMA 边界与
* DMA 可达性守卫负路径、FTL 与 FatFS。所有结果通过 GD5F_TEST_CHECK /
* GD5F_TEST_REPORT 输出串口。
* 作者:王建锋
* 创建日期:2026-08-26
*
@@ -12,6 +13,7 @@
******************************************************************************/
#include <string.h>
#include "FreeRTOS.h"
#include "test_config.h"
#include "gd5f_test.h"
#include "nand_ftl.h"
@@ -30,6 +32,10 @@ gd5f_test_stats_t g_gd5f_test_stats;
static uint8_t s_wbuf[GD5F_PAGE_SIZE * 2];
static uint8_t s_rbuf[GD5F_PAGE_SIZE * 2];
/* CCM(SRAM3)区间:DMA1/DMA2 物理不可达,用于守卫负路径的前提判定 */
#define GD5F_TEST_CCM_BASE 0x10000000UL
#define GD5F_TEST_CCM_END 0x10010000UL
/* -------------------------------------------------------------------
* 辅助:从最高块向下寻找连续 n 个好块,返回其中最高块号;找不到返回 GD5F_TOTAL_BLOCKS
* ------------------------------------------------------------------- */
@@ -514,7 +520,7 @@ static void gd5f_phase6(void)
#endif /* ENABLE_GD5F_PHASE6_TESTS */
/* -------------------------------------------------------------------
* Phase 7 — DMA 边界(阈值 32 字节)
* Phase 7 — DMA 边界与 DMA 可达性守卫(阈值见 GD5F_DMA_THRESHOLD)
* ------------------------------------------------------------------- */
#ifdef ENABLE_GD5F_PHASE7_TESTS
static void gd5f_phase7(void)
@@ -522,37 +528,42 @@ static void gd5f_phase7(void)
uint32_t blk;
int32_t base;
int32_t ret, ok, i;
uint8_t *p_heap; /* 堆缓冲探针(堆驻留 CCM → DMA 不可达) */
uintptr_t p_addr;
DBG_INFO("=== GD5F Phase 7: DMA boundary (threshold 32) ===");
DBG_INFO("=== GD5F Phase 7: DMA boundary (threshold %d) ===",
(int)GD5F_DMA_THRESHOLD);
blk = gd5f_find_run(1);
base = (int32_t)blk * GD5F_BLOCK_SIZE;
/* TC-GD5F-701: size=32 轮询路径 */
/* TC-GD5F-701: 阈值下界(= 阈值,轮询路径) */
gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
for (i = 0; i < 32; i++) {
for (i = 0; i < (int32_t)GD5F_DMA_THRESHOLD; i++) {
s_wbuf[i] = (uint8_t)(i + 0x10);
}
ret = gd5f2gq5ue_write(base, s_wbuf, 32);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-701 write(32) ret=%d", ret);
memset(s_rbuf, 0, 32);
ret = gd5f2gq5ue_read(base, s_rbuf, 32);
ret = gd5f2gq5ue_write(base, s_wbuf, GD5F_DMA_THRESHOLD);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-701 write(%d) ret=%d",
(int)GD5F_DMA_THRESHOLD, ret);
memset(s_rbuf, 0, GD5F_DMA_THRESHOLD);
ret = gd5f2gq5ue_read(base, s_rbuf, GD5F_DMA_THRESHOLD);
ok = 1;
for (i = 0; i < 32; i++) {
for (i = 0; i < (int32_t)GD5F_DMA_THRESHOLD; i++) {
if (s_rbuf[i] != (uint8_t)(i + 0x10)) { ok = 0; break; }
}
GD5F_TEST_CHECK(ok, "TC-GD5F-701 polling path %s", ok ? "OK" : "MISMATCH");
/* TC-GD5F-702: size=33 DMA 路径 */
/* TC-GD5F-702: 阈值上界(= 阈值+1,DMA 路径,缓冲在 SRAM 静态区) */
gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
for (i = 0; i < 33; i++) {
for (i = 0; i < (int32_t)GD5F_DMA_THRESHOLD + 1; i++) {
s_wbuf[i] = (uint8_t)(i + 0x20);
}
ret = gd5f2gq5ue_write(base, s_wbuf, 33);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-702 write(33) ret=%d", ret);
memset(s_rbuf, 0, 33);
ret = gd5f2gq5ue_read(base, s_rbuf, 33);
ret = gd5f2gq5ue_write(base, s_wbuf, GD5F_DMA_THRESHOLD + 1U);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-702 write(%d) ret=%d",
(int)GD5F_DMA_THRESHOLD + 1, ret);
memset(s_rbuf, 0, GD5F_DMA_THRESHOLD + 1U);
ret = gd5f2gq5ue_read(base, s_rbuf, GD5F_DMA_THRESHOLD + 1U);
ok = 1;
for (i = 0; i < 33; i++) {
for (i = 0; i < (int32_t)GD5F_DMA_THRESHOLD + 1; i++) {
if (s_rbuf[i] != (uint8_t)(i + 0x20)) { ok = 0; break; }
}
GD5F_TEST_CHECK(ok, "TC-GD5F-702 DMA path %s", ok ? "OK" : "MISMATCH");
@@ -571,6 +582,52 @@ static void gd5f_phase7(void)
if (s_rbuf[i] != (uint8_t)(i & 0xFF)) { ok = 0; break; }
}
GD5F_TEST_CHECK(ok, "TC-GD5F-703 DMA whole page %s", ok ? "OK" : "MISMATCH");
/* ================= DMA 可达性守卫(负路径) =================
* 探针缓冲取自 FreeRTOS 堆:堆驻留 CCM(0x10000000),DMA1/DMA2 不可达。
* 期望:驱动守卫拒绝该缓冲 → 回落 CPU 轮询 → 数据仍正确落位(轮询可写 CCM)。
* 判定依据(本板实证):若误走 DMA,CCM 内容不会改变(静默不落地),
* 故"数据与参考一致"即证明回落确实发生——非仅未报错。
* 堆不在 CCM 时本用例前提不成立(改为验证正路径无意义),跳过并记录。 */
p_heap = (uint8_t *)pvPortMalloc(GD5F_PAGE_SIZE);
if (p_heap == NULL) {
GD5F_TEST_CHECK(0, "TC-GD5F-704 pvPortMalloc(2048) failed");
} else {
p_addr = (uintptr_t)p_heap;
if ((p_addr < GD5F_TEST_CCM_BASE) || (p_addr >= GD5F_TEST_CCM_END)) {
DBG_INFO("TC-GD5F-704/705 SKIP: heap buf not in CCM (p=0x%08lX)",
(unsigned long)p_addr);
} else {
/* TC-GD5F-704: 读方向 —— 目的缓冲在 CCM,应回落轮询 */
memset(s_rbuf, 0, GD5F_PAGE_SIZE);
(void)gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE); /* 参考读(SRAM 静态) */
for (i = 0; i < GD5F_PAGE_SIZE; i++) {
p_heap[i] = (uint8_t)~s_rbuf[i]; /* 预填参考值反码:未落数必失配 */
}
ret = gd5f2gq5ue_read(base, p_heap, GD5F_PAGE_SIZE);
ok = (ret == GD5F_OK) &&
(memcmp(p_heap, s_rbuf, GD5F_PAGE_SIZE) == 0);
GD5F_TEST_CHECK(ok, "TC-GD5F-704 CCM(heap) rx poll fallback %s",
ok ? "OK" : "MISMATCH");
/* TC-GD5F-705: 写方向 —— 源缓冲在 CCM,应回落轮询 */
gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
for (i = 0; i < GD5F_PAGE_SIZE; i++) {
p_heap[i] = (uint8_t)(i ^ 0x5A);
}
ret = gd5f2gq5ue_write(base, p_heap, GD5F_PAGE_SIZE);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-705 write(CCM src) ret=%d", ret);
memset(s_rbuf, 0, GD5F_PAGE_SIZE);
ret = gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE);
ok = 1;
for (i = 0; i < GD5F_PAGE_SIZE; i++) {
if (s_rbuf[i] != (uint8_t)(i ^ 0x5A)) { ok = 0; break; }
}
GD5F_TEST_CHECK(ok, "TC-GD5F-705 CCM(heap) tx poll fallback %s",
ok ? "OK" : "MISMATCH");
}
vPortFree(p_heap);
}
}
#endif /* ENABLE_GD5F_PHASE7_TESTS */