5538af829b
根因(真机定案,问题 #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 文档通过
122 lines
6.4 KiB
C
122 lines
6.4 KiB
C
/*
|
||
* 模块名称: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 */
|