Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c
T
wandering1 029437be89 台架执行《实机测试实施指南》步骤 3~7 并闭环
步骤 3/4(IWDG V2/V4/V5 + 堆低水位 S11):
- V2 任务饿死 stall 后 ≤2.5s IWDG 复位;V4 关中断死循环 ~4.9s 稳定复位;
  V5 30min 120×FTP 全 PASS、零 IWDG 复位。堆告警全程未触发(min_ever=4728B)。
- task_watchdog.c:stall 证据写盘会经 nand_ftl_wd_feed 喂狗 → 增 s_stall_evidenced
  每次 stall 仅取一次证据,避免"取证据"本身阻止复位。
- default_task.c:堆低水位告警(HEAP_LOW_WATER_B=3072)。

步骤 5(GD5F 错误通道 S9):gd5f2gq5ue.c/.h + nand_ftl.c 错误码统一为 int32_t 并
全量检查返回值;套件回归 16/16 PASSED。
- test/run_all_suites.py:--forbidden 改为按套件覆盖(GD5F 用 "HardFault;assert")——
  TC-GD5F-704 故意触发 [ERR] 回落轮询,统一默认值会误判 FAIL。

步骤 6(网络稳定性):7.1 正常路径 ✅(V5 证据);7.2 N-2 丢弃可观测性代码已具备、
49s 静默在 V5 未复现(降级低风险观察项);7.3 N-3 待 PC peer。

步骤 7(工程卫生):
- S5:新增 test-cppcheck 钩子(仅 warning,排除模拟器桩)。
- S4:新增 scripts/check_test_traceability.py(默认仅告警)。
- 待核实项 TEST_SUITE_SELECTED 实机核实 = 死代码(无使用点);RTC 套件实际正常执行,
  真缺口 = 缺套件级汇总行 → sd2506_test_task.c 补 === RTC Tests: N/M PASSED === 并
  修正 Phase 级汇总累计问题;test_config.h 补齐 RTC 并注释"当前无引用"。

验证:产品 @build 0/0;pre-commit --all-files 17/17 Passed;
run_all_suites.py --suites RTC → PASS。
文档:实机测试实施指南 / SD2506_Test_Guide / 待处理内容 / 测试与架构改进建议 同步。
2026-10-04 00:35:06 +08:00

456 lines
14 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.
/*
* 模块名称:NAND FTL 适配层(dhara + FatFS diskio 胶水)
* 模块功能:实现 dhara NAND HAL 和 FatFS 磁盘 I/O,连接 GD5F2GQ5UE 底层驱动
* 适用平台:STM32F407ZGT6
* 作者:王建锋
* 创建日期:2026-07-20
* 修改记录:
* 2026-07-20 王建锋 创建初始版本
* 2026-09-21 E-31 resume 失败按错误分类:仅真空白判新卷,其余失败
* 返回 mount 层(禁止静默清 map 触发整卷重格式化)
* 2026-10-02 王建锋 resume 成功后增打 journal head/tail/epoch:复盘
* "复位后所有写入丢失"时 sectors used 不足以判定
* journal 回退(覆盖写不改 count),head/tail/epoch 是
* 唯一能暴露 resume 锚错 checkpoint 的证据
*/
/* 头文件包含区 */
#include <string.h>
#include "gd5f2gq5ue.h"
#include "ff.h"
#include "diskio.h"
#include "nand.h"
#include "map.h"
/* FTL 分区起始地址(块 0,偏移 0,占用全部 256MB) */
#define FTL_FATFS_OFFSET 0U
/* 调试输出配置 */
#define DBG_TAG "[NAND_FTL]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define FTL_PAGE_SIZE GD5F_PAGE_SIZE
#define FTL_SECTORS_PER_PAGE (FTL_PAGE_SIZE / 512)
#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE)
#define FTL_NUM_BLOCKS (GD5F_TOTAL_BLOCKS - FTL_START_BLOCK)
#define FTL_START_PAGE (FTL_START_BLOCK * GD5F_PAGES_PER_BLOCK)
/* dhara 逻辑地址到物理地址转换 */
static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLOCK; }
static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; }
/* ======================== 私有变量 ======================== */
/* dhara 核心数据结构 */
static struct dhara_nand s_nand;
static struct dhara_map s_map;
static uint8_t s_page_buf[FTL_PAGE_SIZE]; /* dhara 内部页缓冲区 */
/* 页面缓存(FatFS 读写缓存) */
static uint8_t s_cache_buf[FTL_PAGE_SIZE];
static dhara_sector_t s_cached_lpn;
static uint8_t s_cache_dirty;
/* 拷贝临时缓冲区 */
static uint8_t s_copy_buf[FTL_PAGE_SIZE];
/* 初始化状态 */
static uint8_t s_initialized;
/* ======================== 私有函数声明 ======================== */
static int ftl_flush_cache(void);
static int ftl_read_page(dhara_sector_t lpn);
/* SPI 原语从 gd5f2gq5ue.h 获取 */
/* ======================== 页面缓存管理 ======================== */
static int ftl_flush_cache(void) {
if (!s_cache_dirty) {
return GD5F_OK;
}
dhara_error_t err;
if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err);
return GD5F_ERROR;
}
s_cache_dirty = 0;
return GD5F_OK;
}
static int ftl_read_page(dhara_sector_t lpn) {
dhara_error_t err;
if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err);
return GD5F_ERROR;
}
s_cached_lpn = lpn;
s_cache_dirty = 0;
return GD5F_OK;
}
/* ======================== dhara NAND HAL ======================== */
int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
return gd5f2gq5ue_is_block_bad(ftl_phy_block(b));
}
void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
gd5f2gq5ue_mark_block_bad(ftl_phy_block(b));
}
int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) {
(void)n;
int ret = gd5f_block_erase(ftl_phy_block(b));
if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, b);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
return 0;
}
int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) {
int ret;
uint32_t phy_pg = ftl_phy_page(p);
gd5f_write_enable();
ret = gd5f_program_load(0, data, FTL_PAGE_SIZE);
if (ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
ret = gd5f_program_exec(phy_pg);
if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
/* 编程正确性以 P_FAIL 状态位(gd5f_program_exec 内检查)+ 读时 ECC +
* journal 读时门(magic/CRC)为准;数据路径不做整页回读比对——
* 2026-09-20 曾加回读校验(GD5F_PROG_VERIFY),2026-09-21 经评估整体
* 移除(依据见 docs/内存管理/存储栈RAM优化调研.md 与 GD5F Trap Records Trap 05) */
return 0;
}
int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) {
(void)n;
uint32_t phy_pg = ftl_phy_page(p);
int fre;
if (gd5f_page_read(phy_pg) != GD5F_OK) {
fre = 0;
} else {
uint8_t buf[64];
if (gd5f_read_from_cache(0, buf, sizeof(buf)) != GD5F_OK) {
/* E-42 同族(2026-10-03):读失败 → buf 可能为陈旧/垃圾内容,
* 保守判为非空(块不可复用),避免把"没读到内容的块"判成空闲。 */
fre = 0;
} else {
uint32_t i;
fre = 1;
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != 0xFF) {
fre = 0;
break;
}
}
}
}
return fre;
}
int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) {
(void)n;
uint32_t phy_pg = ftl_phy_page(p);
if (gd5f_page_read(phy_pg) != GD5F_OK) {
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
if (gd5f_check_ecc() != GD5F_OK) {
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
/* E-42 同族(2026-10-03):read_from_cache 返回值必须检查——失败时 data 内容
* 不可信,原实现仍返回 0(成功),会把未读到的数据当有效数据交给上层。 */
if (gd5f_read_from_cache((uint16_t)offset, data, length) != GD5F_OK) {
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
return 0;
}
int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) {
(void)n;
if (dhara_nand_read(n, src, 0, FTL_PAGE_SIZE, s_copy_buf, err) < 0) {
return -1;
}
if (dhara_nand_prog(n, dst, s_copy_buf, err) < 0) {
return -1;
}
return 0;
}
/* ======================== FatFS 磁盘 I/O ======================== */
DSTATUS disk_initialize(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
}
if (s_initialized) {
return 0;
}
DBG_INFO("Initializing NAND FTL...");
s_nand.log2_page_size = 11;
s_nand.log2_ppb = 6;
/* dhara 可见块数 = 总块数 - 保留块池(BBT 存储区不交给 dhara,避免被擦写) */
s_nand.num_blocks = gd5f_get_usable_blocks();
dhara_map_init(&s_map, &s_nand, s_page_buf, 4);
memset(s_cache_buf, 0, FTL_PAGE_SIZE);
s_cached_lpn = (dhara_sector_t)-1; /* -1 表示缓存未加载;LPN 0 合法,不能用 0 作哨兵 */
s_cache_dirty = 0;
{
dhara_error_t err = DHARA_E_NONE;
if (dhara_map_resume(&s_map, &err) < 0) {
if (err == DHARA_E_TOO_BAD) {
/* 扫描全程 I/O 干净且无 checkpoint = 真空白卷(出厂首启 /
* 显式格式化后);失败的 resume 已把 journal 复位为空态 →
* 按新卷处理,由上层自动建卷(此处 dhara_map_clear 为空操作:
* resume 失败时 count 已清零,map 本已为空)。 */
DBG_INFO("No valid map found (err=%d), creating fresh", err);
dhara_map_clear(&s_map);
} else {
/* E-31(2026-09-21 审计 P0 修复):非"空白"错误(ECC /
* BAD_BLOCK / CORRUPT_MAP / 瞬态 I/O 等)= 真实完整性故障——
* 芯片上 journal 可能仍持有有效 checkpoint。旧行为把任何失败
* 当新卷清 map,上层随即整卷重格式化 → 单次瞬态毛刺即静默销毁
* 全部用户数据。现把失败返回给 mount 层:f_mount 失败 →
* defaultTask 自检上报 "filesystem fault" 并保留卷;瞬态故障
* 由主循环重试/下次上电重新 resume;重格式化只走显式命令
* (nand_ftl_format)。 */
DBG_ERROR("Map resume failed (err=%d), volume kept, "
"storage unavailable", err);
return STA_NOINIT;
}
} else {
DBG_INFO("Map resumed: %lu/%lu sectors used",
dhara_map_size(&s_map), dhara_map_capacity(&s_map));
/* journal 头/尾/epoch 一并打印:2026-10-02 台架复盘"复位后写入全部
* 丢失"时,只看 sectors used 判断不出 journal 是否回退(覆盖写场景
* count 不变),必须看 head/tail/epoch 才能发现 resume 锚错了
* checkpoint(详见 Lib/dhara/journal.c 的 2026-10-02 本地修复注释)。
* 纯内存字段、零 I/O,保留作长期可观测性。 */
DBG_INFO("Map head=%lu tail=%lu epoch=%u",
(unsigned long)s_map.journal.head,
(unsigned long)s_map.journal.tail,
(unsigned)s_map.journal.epoch);
}
}
s_initialized = 1;
DBG_INFO("NAND FTL ready (capacity: %lu pages = %lu MB)",
dhara_map_capacity(&s_map),
(dhara_map_capacity(&s_map) * FTL_PAGE_SIZE) / (1024 * 1024));
return 0;
}
int nand_ftl_init(void)
{
DSTATUS sta = disk_initialize(0);
return (sta == 0) ? 0 : -1;
}
void nand_ftl_deinit(void)
{
/* 复位 FTL 状态,使下次 nand_ftl_init() 真正重新走 dhara_map_resume,
用于测试"掉电恢复"与"坏块注入后重建"场景(模拟设备重启) */
s_initialized = 0;
s_cached_lpn = (dhara_sector_t)-1;
s_cache_dirty = 0;
}
DSTATUS disk_status(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
}
if (!s_initialized) {
return STA_NOINIT;
}
return 0;
}
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(buff, s_cache_buf + offset, batch * 512);
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(s_cache_buf + offset, buff, batch * 512);
s_cache_dirty = 1;
if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
}
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
if (pdrv != 0) {
return RES_PARERR;
}
if (!s_initialized) {
return RES_NOTRDY;
}
switch (cmd) {
case CTRL_SYNC:
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
{
dhara_error_t err;
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync failed: %d", err);
return RES_ERROR;
}
}
return RES_OK;
case GET_SECTOR_COUNT: {
LBA_t *p_sectors = (LBA_t *)buff;
dhara_sector_t pages = dhara_map_capacity(&s_map);
*p_sectors = pages * FTL_SECTORS_PER_PAGE;
return RES_OK;
}
case GET_SECTOR_SIZE:
*(WORD *)buff = 512;
return RES_OK;
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
return RES_OK;
default:
return RES_PARERR;
}
}
/* ======================== 公共函数定义 ======================== */
int nand_ftl_format(void) {
dhara_error_t err;
if (!s_initialized) {
if (disk_initialize(0) != 0) {
return GD5F_ERROR;
}
}
if (ftl_flush_cache() != GD5F_OK) {
return GD5F_ERROR;
}
dhara_map_clear(&s_map);
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync after clear failed: %d", err);
return GD5F_ERROR;
}
s_cache_dirty = 0;
s_cached_lpn = (dhara_sector_t)-1;
DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map));
return GD5F_OK;
}