Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h
T
super_agent_bot 5538af829b
CI / 静态门禁 (pre-commit) (push) Failing after 43s
CI / 主机侧单测 (MSVC) (push) Failing after 45s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 44s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 43s
fix(nand-ftl): 传输失败不再误标坏块 + format 不再被脏缓存挡死(#79)
根因(真机定案,问题 #79):旧 dhara_nand_erase/prog() 把一切失败一律
mark_bad,而 mark_block_bad 每次**立即持久化**板级 BBT(不可撤销)——
一次传输层/等忙抖动就把好块永久记成坏块。台架 129 个好块(1016–1142
连续段)被固化,journal prepare_head() 连续 8 次撞 is_bad → TOO_BAD →
Cache flush FAIL → reformat 又被同一页挡住 → 整卷禁用、重启不恢复。

- 修复①:只有芯片明确回 E_FAIL/P_FAIL(状态位证据)才标坏;
  传输/超时失败只报 DHARA_E_BAD_BLOCK 让 dhara 重试,不标坏
- 修复②:nand_ftl_format() 尽力刷一次脏缓存,失败即丢弃并继续
  dhara_map_clear()(格式化语义本就是销毁映射;旧实现第一步返回,
  恢复动作在同一页死锁,f_mkfs 全在旧映射上必失败)
- C4 还债:新增跨调用失败类快照 nand_ftl_fault_get/clear(20 B,
  resume/读/刷/同步四点位记账);defaultTask 探针按类分流,
  BSP_ERR_NO_SPACE 跳过重格式化(容量问题不该用 mkfs 解)
- 哨兵与诊断:init 解锁块保护(A0h)后补回读校验(与 B0h ECC 同族
  缺口,实测本板 A0h=0,非本次根因,留作哨兵);gd5f_bbt_load()
  合并后坏块数 > 出厂扫描时打「BBT after merge」,现场可区分
  真坏与误标固化
- 测试:主机单测 149→178 全绿;新增 format 死锁(TC-NANDPORT-800..
  813b)、传输失败不标坏(207 语义翻转 + 208/505);牙齿检查过——
  把修复回退即见红
- 真机:恢复动作 A 重建 BBT 清除已固化误标(3/3 PASSED,BBT
  version=71);产品镜像复烧 [FS] FATFS mounted、配置出厂模板
  落盘、0 条 flush FAIL——#79 真机闭环(旧卷内容随 map clear
  作废,台架卷为可弃数据)
- 文档:存储架构说明 §10.4 口径修正 + 新增 §10.4.1/§10.4.2(页级
  失败升级链与误标持久化机理),存储维护与容量恢复 §2 补板级 BBT
  独立持久化注记 + §2.1(动作 A 此前静默失效),登记簿 C4 销项
- Keil 0 错 0 警(Code=255552,与烧录验证镜像一致);doc-links 213
  文档通过
2026-11-04 18:58:13 +08:00

122 lines
6.4 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.
/*
* 模块名称:SPI NAND FTL(dhara 移植层 + FatFs 磁盘接口)
* 模块功能:把 GD5F SPI NAND 经 dhara 映射层暴露成 FatFs 的 0 号磁盘
* (disk_initialize/status/ioctl/read/write),并提供显式格式化入口。
* 适用平台:STM32F4 系列 + GD5F2GQ5UE
* 作者:王建锋
* 创建日期:2026-09
* 修改记录:
* 2026-10-23 王建锋 补模块头与函数注释(return-doc 检查);返回值语义写明"失败类目前
* 只进日志、不外传",避免调用方以为能拿到 ECC / 预算耗尽的区别
*/
#ifndef NAND_FTL_H
#define NAND_FTL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/*
* 失败快照(C4):把"最近一次存储失败的归类"从失败点带出来。
*
* 为什么要单独成一个结构而不是复用 bsp_err_t:调用方需要的不只是失败类,
* 还要能回答"这次失败值不值得再试一次"(重试同一 LPN 会不会又撞同一页)。
* 故带上 LPN 与 op,便于 run.log/wdev.log 里一条日志说清现场。
*
* 字段:
* active - 1 = 快照可用(自上次 nand_ftl_fault_clear() 以来发生过失败)
* op - 失败发生的操作(NAND_FTL_OP_*),0 = 无
* cls - 失败类(bsp_err_t:BSP_ERR_CRC / BSP_ERR_NO_SPACE / BSP_ERR_IO …)
* lpn - 失败涉及的逻辑页号(未知时填 NAND_FTL_LPN_NONE)
* count - 自上次 clear 以来累计的失败次数(饱和不减,用于判"反复撞同一页")
*
* 尺寸:5 × uint32 = **20 B**,无隐式填充(字段全为 4 字节宽,任何编译器布局一致)。
* 不引入任何动态分配。
*/
#define NAND_FTL_LPN_NONE ((uint32_t)0xFFFFFFFFu)
#define NAND_FTL_OP_NONE ((uint32_t)0u)
#define NAND_FTL_OP_MAP_RESUME ((uint32_t)1u) /* disk_initialize 的 dhara_map_resume */
#define NAND_FTL_OP_READ ((uint32_t)2u) /* ftl_read_page(FatFs 读路径) */
#define NAND_FTL_OP_FLUSH ((uint32_t)3u) /* ftl_flush_cache(写回路径) */
#define NAND_FTL_OP_SYNC ((uint32_t)4u) /* disk_ioctl(CTRL_SYNC) 的 map sync */
typedef struct {
uint32_t active; /* 1 = 快照可用(自上次 nand_ftl_fault_clear() 以来发生过失败) */
uint32_t op; /* 失败发生的操作(NAND_FTL_OP_*),0 = 无 */
int32_t cls; /* 失败类(bsp_err_t:BSP_ERR_CRC / BSP_ERR_NO_SPACE / BSP_ERR_IO …)。
* 写死 int32_t 宽度:bsp_err_t 是 int,跨编译器布局才稳定 */
uint32_t lpn; /* 失败涉及的逻辑页号(未知时 = NAND_FTL_LPN_NONE) */
uint32_t count; /* 自上次 clear 以来累计失败次数(饱和不减,用于判"反复撞同一页") */
} nand_ftl_fault_t;
/*
* 函数功能:初始化 FTL(等价于 disk_initialize(0):SPI 起链 + dhara_map_resume)
* 入口参数:无
* 返回值:0 - 成功(映射可 resume 或判为真空白卷);-1 - 失败(disk_initialize 非 0)
* 限定条件:由 FatFs 的 f_mount(forced=1) 间接触发,或测试代码直接调用
* 函数说明:⚠ 这里的 -1 **抹掉了失败类**——"ECC 读不动"、"journal 满"与"映射损坏"对外
* 都只是 -1。分类信息目前只在失败点就地进日志(见 nand_ftl.c 的 ftl_class_from_dhara
* 与 ../架构设计/STM32F4-Base存储架构说明.md §10.4);要让调用方按类分支,需要跨调用
* 存活的失败类快照(C4 待还项,当前无消费者故未做)。
* 调用方**不得**把 -1 解读成"卷坏了,可以重格式化"——教训正在这条上。
*/
int nand_ftl_init(void);
/*
* 函数功能:清空 dhara 映射树并同步(**破坏性**:整卷逻辑地址→物理映射作废)
* 入口参数:无
* 返回值:GD5F_OK(0) - 已清空并同步;GD5F_ERROR(-1) - 初始化/刷写缓存/同步失败(卷状态未定,
* 须重新挂载判定,不要连环重试)
* 限定条件:只允许走显式命令或恢复流程调用;不得因"某次读写失败"自动调用
* 函数说明:新卷从空树写起,避免旧 journal 残留与新卷共存(结构卫生)。
* 容量恢复动作的区分(A 清日志 / B 重挂载 / C 全片擦除 / D 残留清理)
* → docs/测试说明/专项验证/存储维护与容量恢复.md。
*/
int nand_ftl_format(void);
/*
* 函数功能:复位 FTL 内存态,使下次 nand_ftl_init() 重新走 dhara_map_resume
* 入口参数:无
* 返回值:无
* 限定条件:测试用途(掉电恢复 / 坏块注入后重建场景模拟设备重启)
* 函数说明:只清 RAM 状态位,不碰闪存内容。
*/
void nand_ftl_deinit(void);
/*
* 函数功能:取"最近一次存储失败"的跨调用快照(C4,2026-11-04 补还)
* 入口参数:p_out - 快照输出 nand_ftl_fault_t* 可为 NULL(=只查询是否有失败)
* 返回值:无 void(新 API 不把"有没有失败"塞进返回值:本函数是纯查询,
* 判定一律读 p_out->active)
* 限定条件:任何任务上下文可读;**仅诊断用途**,不得据此自动清卷/重格式化
* 函数说明:为什么需要它——disk_read/disk_write 对 FatFs 只能回 RES_ERROR
* (FatFs 契约),失败类在函数返回时就被抹掉,上层因此无法区分
* 「页级 TOO_BAD,重格式化可解」与「块池见底 / 映射损坏,重格式化只会把问题
* 推回去」。本快照把 nand_ftl.c 就地分类的结果留在 RAM 里,供
* defaultTask 的卷健康探针按类分支(消费点见 storage_bring_up())。
* ⚠ 多写者:defaultTask 与 testTask 都可能触发写入失败,故快照是"最近一次"
* 语义、非累计账本;并发下可能被另一路覆盖(诊断场景可接受)。
* 成功路径**不清**它(失败证据要留存到显式 clears),语义同 dbg latch。
*/
void nand_ftl_fault_get(nand_ftl_fault_t *p_out);
/*
* 函数功能:清除失败快照(把 active 置 0)
* 入口参数:无
* 返回值:无
* 限定条件:仅"已按快照处置完毕"的调用方清除(当前 = defaultTask 卷修复路径)
* 函数说明:不清就会让一次历史失败永久污染后续判定;清早了又会丢掉现场。
* 调用点因此只有一处,见 storage_bring_up() 的重格式化分支。
*/
void nand_ftl_fault_clear(void);
#ifdef __cplusplus
}
#endif
#endif /* NAND_FTL_H */