Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h
T
wandering1 7e77af08a0 fix(BSP): SPI1 完成标志收回 GD5F 私有,HAL 回调改契约转调
推荐清单第 4a 步(结构债 S-2 剩余面里唯一带"静默失效"属性的那条)。

**问题不是风格**:HAL 的 SPI 完成/错误回调是**全局唯一符号**,整个工程只能有一份
实现,当前由 CH395F 代管并按 `hspi->Instance` 分发——于是 GD5F 的完成标志
`g_spi1_dma_done` 被 CH395F 跨模块裸 `extern` 直接写,**GD5F 出错时的"释放等待者"
寄居在别人的 .c 里**。谁哪天做"CH395F 清理",把那两个 `else if (SPI1)` 分支当无关
代码删掉:编译零报错,GD5F 的 DMA 等待在出错时只能干等 `GD5F_SPI_TIMEOUT` 兜底。

**改法(时序一字不动,只换所有权)**:
· 标志收为 GD5F 文件内 `static volatile uint8_t s_spi1_dma_done`(6 处引用同步改名)
· GD5F 导出 `gd5f_spi1_notify_done()` / `gd5f_spi1_notify_error()`,头文件里写明
  "SPI1 分支必须转调这两个函数,否则本模块的 DMA 等待在出错时只能靠超时兜底"
· CH395F 删除裸 `extern`、include `gd5f2gq5ue.h`、两个 SPI1 分支改为转调并就地注明
  "勿删:少了这行 GD5F 的 DMA 出错只能靠超时退出"

`HAL_SPI_AbortCpltCallback`(`HAL_SPI_DMAStop()` 会触发)本次**刻意不做**——它会改
变中止语义、需要真机专项,当前超时兜底可用(已在报告 §0.4 记为观察项)。

**体量代价**:`ch395f.o` −8 B、`gd5f2gq5ue.o` +24 B → `Code` 213,188 → **213,204 B
(+16 B)**,RO/RW/ZI 全不变,`ER_IROM1` 231,768 → **231,784 B(22.1%)**。16 B 换
掉一个静默失效类,划算。

**真机 `@test` PASS**:无 `[ERR]`/Fault,复位原因 software;`[STACK] heap
free=15800 min_ever=8464` 与改前**逐项相同**;各任务实测峰值 default 2152 / net
1116 / ftp 764 / ui 4052 / timesync 616 / adc 952 / iec104 540 / protect 624,
**静态最坏链全部未变**(3024/1912/1456/4052/1264/1320/1456/872)→ 9/9 PASS。

⚠ 记一条读数陷阱:测试脚本打印的 "boot to net init: OK = 7,907 ms" 是采集窗口
锚点造成的度量偏差,真机日志里 `boot: syslog init OK → net init: OK` 实测 ≈699 ms,
与改前一致,**不是性能回退**。

**数字联动**:`data/ram_report_20261022.md`、`data/stack_report_20261022.md` 重跑;
`ram_baseline.py --update/--write`;`check_mem_numbers.py --write`(RAM 方案受管区 +
台账);`gen_memory_svg.py` 常量更新后重新生成 FLASH 与任务栈两张配图(逐文本按
字号与锚点复查越界 = 0;RAM 图无需变更);报告 §1.3/§5.1/§5.4、部署文档 §3.3 的
Flash 数字同步;§0.4/§9 的 S-2 口径改为"24 处手写已消 23 处,只剩
`net_socket.c:77`"。

门禁:`@build` 0 错 0 警告;`cubemx-survival --check`、`mem-numbers`、`mem-budget`
(57.4% / 87.9%)、`task-table`、`doc-links --all --sections-strict`(146 文档 0 问题)、
`id-check --strict`(增量拦截通过、可判定 82.83%)全绿;字节卫生 BOM/CR/U+FFFD/尾
空格全清;未提交 UV4 抖动的 `.uvprojx`(本次未被改写)。未推送。
2026-10-22 19:21:08 +08:00

345 lines
16 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.
#ifndef GD5F2GQ5UE_H
#define GD5F2GQ5UE_H
/*
* 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动
* 模块功能:提供 GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接口
* 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI
* 作者:王建锋
* 创建日期:2026-07-16
* 修改记录:
* 2026-07-16 王建锋 创建初始版本
* 2026-07-17 王建锋 切换为硬件 SPI,修正擦除地址,参考 NuttX 驱动
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include <stdint.h>
/* ======================== 宏定义 ======================== */
/* SPI 命令码 */
#define GD5F_CMD_WRITE_ENABLE 0x06
#define GD5F_CMD_WRITE_DISABLE 0x04
#define GD5F_CMD_GET_FEATURE 0x0F
#define GD5F_CMD_SET_FEATURE 0x1F
#define GD5F_CMD_READ_ID 0x9F
#define GD5F_CMD_PAGE_READ 0x13
#define GD5F_CMD_READ_FROM_CACHE 0x0B
#define GD5F_CMD_PROGRAM_LOAD 0x02
#define GD5F_CMD_PROGRAM_EXEC 0x10
#define GD5F_CMD_BLOCK_ERASE 0xD8
#define GD5F_CMD_RESET 0xFF
/* 寄存器地址 */
#define GD5F_REG_PROTECT 0xA0
#define GD5F_REG_FEATURE 0xB0
#define GD5F_REG_STATUS 0xC0
#define GD5F_REG_DRIVER 0xD0
/* 状态位定义 */
#define GD5F_STATUS_OIP (1 << 0)
#define GD5F_STATUS_WEL (1 << 1)
#define GD5F_STATUS_E_FAIL (1 << 2)
#define GD5F_STATUS_P_FAIL (1 << 3)
#define GD5F_STATUS_ECCS0 (1 << 4)
#define GD5F_STATUS_ECCS1 (1 << 5)
/* Feature 位定义 */
#define GD5F_FEATURE_ECC_EN (1 << 4)
#define GD5F_FEATURE_QE (1 << 0)
/* 芯片参数 */
#define GD5F_PAGE_SIZE 2048
#define GD5F_SPARE_SIZE 64
#define GD5F_TOTAL_PAGE_SIZE 2112
#define GD5F_PAGES_PER_BLOCK 64
#define GD5F_BLOCK_SIZE (GD5F_PAGES_PER_BLOCK * GD5F_PAGE_SIZE)
/*
* 页读取就绪判定 = WIP 轮询(与 Linux 主线 / GigaDevice 厂商驱动完全一致,
* 单发令形状)。2026-09-20 沉降 / 13h 二次发令史实统一记于
* GD5F2GQ5UE_Trap_Records.md TRAP-GD5F-05。
*/
#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
/* ==================== DMA 可达内存窗口(N-26,2026-10-12) ====================
* DMA1/DMA2 物理不可达 CCM(0x10000000~0x1000FFFF):以 CCM 缓冲做 DMA 源/目的
* 会静默不落地(2026-09-20 首轮 CCM 扩容实验实证根因;口径见
* docs/内存管理/内存内容分配策略.md §2 与 §4 规则 R2/R3)。
* 白名单窗口必须与分散加载 RW_IRAM1(0x20000000,128KB,SRAM1+SRAM2)一致
* (MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct);将来新增其它 DMA 可达内存
* (如外部 SDRAM)须同步扩展本窗口。
* ⚠ 缓冲**自身地址**无法编译期断言:.bss 地址链接期才确定,armcc 的常量表达式
* 取不到(__builtin 系在 MicroLIB 下亦不可用,见 Src/main.c Error_Handler 注释)。
* 静态缓冲的落位由 scatter(RW_CCM 只列 heap_4.o)+ nand_ftl.c 的 init 期自检
* (gd5f_dma_buf_ok)双层保证;动态传入缓冲由每次传输的守卫强制。 */
#define GD5F_DMA_RAM_BASE 0x20000000UL
#define GD5F_DMA_RAM_END 0x20020000UL
#define GD5F_CCM_RAM_BASE 0x10000000UL
#define GD5F_CCM_RAM_END 0x10010000UL
/* 编译期守卫(N-26):AC5 无 _Static_assert(C11 不支持),用项目惯用的
* 负数组 typedef(同 App/cfg/cfg_file.c、Drivers/BSP/UI/ui_menu_tab.h)——
* 条件不成立则数组尺寸 -1 → 编译错。
* ① DMA 白名单窗口与 CCM 窗口**永不得重叠**(有人改上面常量时立即爆炸);
* ② 阈值契约(见 GD5F_DMA_THRESHOLD 处注释):≥ 存储路径最大栈缓冲
* (dhara meta 132 B = DHARA_META_SIZE 上限)且 < 页大小(页级传输保留 DMA)。 */
typedef char gd5f_dma_window_no_ccm_check_t[
((GD5F_DMA_RAM_END <= GD5F_CCM_RAM_BASE) || (GD5F_DMA_RAM_BASE >= GD5F_CCM_RAM_END))
? 1 : -1];
typedef char gd5f_dma_threshold_contract_check_t[
((GD5F_DMA_THRESHOLD >= 132U) && (GD5F_DMA_THRESHOLD < GD5F_PAGE_SIZE))
? 1 : -1];
/* 坏块表持久化:保留块池数量(从 NAND 顶部向下挑选出厂好块存储 BBT) */
#define GD5F_BBT_POOL_COUNT 4
/* 制造商 ID 和设备 ID */
#define GD5F_MANUFACTURER_ID 0xC8
#define GD5F_DEVICE_ID 0x52
/* 返回值定义 */
#define GD5F_OK 0
#define GD5F_ERROR -1
#define GD5F_BUSY_TIMEOUT -2
#define GD5F_ECC_ERROR -3
#define GD5F_PROGRAM_FAIL -4
#define GD5F_ERASE_FAIL -5
#define GD5F_ID_MISMATCH -6
/* 控制引脚宏 */
#define GD5F_CS_LOW() HAL_GPIO_WritePin(GD_CS_GPIO_Port, GD_CS_Pin, GPIO_PIN_RESET)
#define GD5F_CS_HIGH() HAL_GPIO_WritePin(GD_CS_GPIO_Port, GD_CS_Pin, GPIO_PIN_SET)
#define GD5F_WP_HIGH() HAL_GPIO_WritePin(GD_WP_GPIO_Port, GD_WP_Pin, GPIO_PIN_SET)
#define GD5F_HOLD_HIGH() HAL_GPIO_WritePin(GD_HOLD_GPIO_Port, GD_HOLD_Pin, GPIO_PIN_SET)
/* ======================== 函数声明 ======================== */
/*
* 函数功能:初始化 GD5F2GQ5UE(读 ID + 使能 ECC + 解除块保护)
* 入口参数:无
* 返回值:0 - 成功,其他 - 错误码
* 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成
* 函数说明:1. 发送复位命令并等待完成
* 2. 读取芯片 ID 并校验
* 3. 使能内部 ECC (B0h bit4)
* 4. 解除所有块保护 (A0h = 0x00)
*/
int32_t gd5f2gq5ue_init(void);
/*
* 函数功能:读取芯片 ID(MID + DID)
* 入口参数:mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* did - 设备 ID 输出指针 uint8_t* 不为 NULL
* 返回值:0 - 成功
* 限定条件:SPI 已初始化
* 函数说明:发送 9Fh 命令后接收1个 dummy + MID + DID
*/
int32_t gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did);
/*
* 函数功能:从 NAND 读取数据(支持跨页)
* 入口参数:offset - 起始字节偏移 long 0 - GD5F_TOTAL_SIZE-1
* buf - 数据缓冲区 uint8_t* 不为 NULL
* size - 读取字节数 size_t > 0
* 返回值:GD5F_OK(0) - SPI 传输完成;其它 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:自动处理跨页读取,每次读取不超过当前页剩余空间。
* ⚠ **raw 契约(E-56,2026-10-03 显式文档化)**:返回 0 只表示 **SPI 传输完成**,
* **不代表数据无位翻转**——ECC 不可纠正页(ECCS 非零)在 SPI 层属正常完成,
* 本函数仍返回 0。数据正确性由 **ECC 层**保证:生产主路径经 dhara glue
* (`nand_ftl.c`)自有 ECC 检查保护;**直接使用本 raw API** 的调用方
* (测试/诊断路径)须自行处理位翻转。详见
* docs/问题记录/GD5F2GQ5UE_Trap_Records.md。
*/
int32_t gd5f2gq5ue_read(long offset, uint8_t *buf, size_t size);
/*
* 函数功能:向 NAND 写入数据(支持跨页)
* 入口参数:offset - 起始字节偏移 long 0 - GD5F_TOTAL_SIZE-1
* buf - 数据缓冲区 uint8_t* 不为 NULL
* size - 写入字节数 size_t > 0
* 返回值:GD5F_OK(0) - 成功;其它 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦除
* 函数说明:自动处理跨页写入,每次写入不超过当前页剩余空间
*/
int32_t gd5f2gq5ue_write(long offset, const uint8_t *buf, size_t size);
/*
* 函数功能:擦除块(按块擦除,最小单位 128KB)
* 入口参数:offset - 起始字节偏移 long 必须 GD5F_BLOCK_SIZE 对齐
* size - 擦除字节数 size_t 必须 GD5F_BLOCK_SIZE 整数倍
* 返回值:GD5F_OK(0) - 成功;其它 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:擦除操作以块为单位,offset 和 size 必须块对齐
*/
int32_t gd5f2gq5ue_erase(long offset, size_t size);
/*
* 函数功能:复位芯片
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:SPI 已初始化
* 函数说明:发送 FFh 复位命令后等待 5ms
*/
int32_t gd5f2gq5ue_reset(void);
/*
* 函数功能:查询块是否坏块
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:0 - 好块,1 - 坏块
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:通过 BBT(Bad Block Table)查询,BBT 在 init 阶段 ECC 使能前扫描构建
*/
int32_t gd5f2gq5ue_is_block_bad(uint32_t block);
/*
* 函数功能:标记块为坏块
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:无
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:更新 RAM 中的 BBT,并持久化到保留块池(带版本号 + CRC,
* 轮转写入,掉电安全);仅在管理区内的块才纳入持久化 BBT
*/
void gd5f2gq5ue_mark_block_bad(uint32_t block);
void gd5f2gq5ue_bbt_clear(void);
void gd5f2gq5ue_print_bbt(void);
/*
* 函数功能:重建 BBT(重新扫描出厂坏块并持久化,覆盖运行时注入的坏块)
* 入口参数:无
* 返回值:GD5F_OK - 成功,其他 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:用于测试(如 TC-STORAGE-03 注入坏块后)或出厂重置后恢复。
* 临时关闭 ECC 重新扫描每块 page0 spare[0] 的出厂坏块标记,覆盖 RAM BBT,
* 再写回持久化 BBT 池(版本 +1,掉电安全),从而清除运行时 mark 的坏块。
* 调用后建议再执行 nand_ftl_format() 清空 dhara map。
*/
int32_t gd5f2gq5ue_bbt_rebuild(void);
/*
* 函数功能:获取 dhara 可用块数(总块数减去保留块池)
* 入口参数:无
* 返回值:dhara 可见的块数量(= 保留块池起始块号)
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:保留块池用于存储持久化 BBT,不交给 dhara 管理
*/
uint32_t gd5f_get_usable_blocks(void);
/*
* 函数功能:从闪存读取当前生效的 BBT 位图(测试/调试用)
* 入口参数:p_bbt - 位图输出缓冲 uint8_t* 不为 NULL,长度 >= GD5F_BBT_SIZE
* p_version - 版本号输出 uint32_t* 不为 NULL
* 返回值:0 - 成功,其他 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:返回闪存中版本号最高的有效 BBT 拷贝(供测试验证持久化)
*/
int32_t gd5f_bbt_dump_flash(uint8_t *p_bbt, uint32_t *p_version);
/*
* 函数功能:擦除保留块池(撤销持久化 BBT,回到仅工厂扫描态)
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:仅供测试/出厂重置使用;擦除后下次启动 load 找不到副本,
* 仅保留 RAM 中工厂扫描结果(调用方需自行还原 RAM BBT)。
*/
int32_t gd5f_bbt_wipe_pool(void);
/*
* 函数功能:从保留块池重新加载持久化 BBT 到 RAM(测试/调试用)
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:将闪存中版本号最高的有效副本 OR 进 RAM BBT
*/
int32_t gd5f_bbt_reload(void);
/* ==================== SPI 原语(FTL 共享) ==================== */
int32_t gd5f_wait_busy(uint32_t timeout_ms);
int32_t gd5f_write_enable(void);
int32_t gd5f_read_status(uint8_t *p_status);
int32_t gd5f_page_read(uint32_t page_addr);
int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, uint32_t size);
int32_t gd5f_program_load(uint16_t column, const uint8_t *p_buf, uint32_t size);
int32_t gd5f_program_exec(uint32_t page_addr);
int32_t gd5f_block_erase(uint32_t block_addr);
int32_t gd5f_check_ecc(void);
/*
* 函数功能:判定缓冲整段是否位于 DMA 可达内存(GD5F_DMA_RAM_* 白名单窗口)
* 入口参数:p_buf - 传输缓冲首地址 const void*
* size - 传输字节数 size_t
* 返回值:1 - 整段 DMA 可达;0 - 不可达(含 size=0 / 地址回绕 / 越窗)
* 限定条件:无
* 函数说明:N-26(2026-10-12)由 gd5f2gq5ue.c 私有守卫升级为公开 API——
* 驱动内部每次 >阈值 传输前用它(不可达即回落 CPU 轮询),
* FTL 层(nand_ftl.c)在 init 期用它自检静态页缓冲的落位。
*/
int gd5f_dma_buf_ok(const void *p_buf, size_t size);
/*
* 函数功能:NAND 长耗时操作看门狗心跳弱钩子
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文可调(内核启动前调用为空操作)
* 函数说明:gd5f_wait_busy 的长轮询(块擦除等待最长 5 s、GC 页拷贝、
* 日志写)都运行在调用者任务上下文(default/ftp/ui/timesync 等);
* 本钩子默认空实现,App/util/task_watchdog.c 提供强符号,按调用者
* 任务句柄路由到该任务的心跳位,防止长耗时窗口心跳缺席导致 IWDG
* 误复位(2026-10-02 台架:0 字节 STOR 截断 → dhara GC > 800 ms
* 无心跳 → 停喂 → IWDG 误复位)。
*/
void nand_ftl_wd_beat(void);
/*
* 函数功能:NAND 长耗时操作看门狗直接喂狗弱钩子
* 入口参数:无
* 返回值:无
* 限定条件:任意上下文可调(内核启动前 / IWDG 未启动时为空操作)
* 函数说明:心跳钩子(nand_ftl_wd_beat)只能维持"心跳位",喂狗动作本身在
* defaultTask 主循环(task_wd_poll)——若长耗时 FTL 操作(GC/整卷
* 格式化)发生在 defaultTask 上下文,主循环被阻塞在 FTL 调用里,
* 喂狗点停摆 → 心跳位再新也无人喂 → IWDG 误复位(2026-10-02 台架
* 实证:console tx fault 条目落盘触发 GC,poller 饥饿 ~2 s → IWDG
* 复位、无任何 stall 日志)。本钩子让 FTL 长路径在操作进行中直接
* 喂狗,覆盖"喂狗点自身被长 I/O 阻塞"的形态。强符号由
* App/util/task_watchdog.c 提供(仅 IWDG 已启动时生效)。
*/
void nand_ftl_wd_feed(void);
/*
* 函数功能:SPI1 DMA 完成 / 错误通知(由实际注册 HAL SPI 回调的模块转调)
* 入口参数:无
* 返回值:无
* 限定条件:中断上下文(HAL SPI 回调内),两者只写一个完成标志
* 函数说明:HAL 的 SPI 完成/错误回调是全局唯一符号,本模块只实现 TxCplt/RxCplt
* 两个(供自己的收发用),Error 回调的所有权留给回调的实际注册方
* (当前 = Drivers/BSP/CH395F/ch395f.c,按 hspi->Instance 分发)。**SPI1
* 分支必须转调这两个函数**,否则本模块的 DMA 等待在出错时只能靠超时
* 兜底。2026-10-22:取代原先被 CH395F 跨模块裸 extern 直接写的全局变量。
*/
void gd5f_spi1_notify_done(void);
void gd5f_spi1_notify_error(void);
#ifdef __cplusplus
}
#endif
#endif /* GD5F2GQ5UE_H */