Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.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

1207 lines
42 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.
/*
* 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动
* 模块功能:提供 GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接口
* 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI
* 作者:王建锋
* 创建日期:2026-07-16
* 修改记录:
* 2026-07-16 王建锋 创建初始版本
* 2026-07-17 王建锋 切换硬件 SPI,修正擦除地址,参考 NuttX 驱动
* 2026-08-27 补充函数注释(代码规范 V1.0);修正工厂坏块扫描为首页;增加 BBT 持久化
*/
/* 头文件包含区 */
#include <string.h>
#include "gd5f2gq5ue.h"
/*
* 调试输出配置
*/
#define DBG_TAG "[NAND]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define GD5F_SPI_TIMEOUT 100
#define GD5F_BBT_SIZE (GD5F_TOTAL_BLOCKS / 8)
/* BBT 静态数组(1 bit 表示一个块,0=好块 1=坏块) */
static uint8_t s_bbt[GD5F_BBT_SIZE];
/* BBT 持久化:保留块池(从 NAND 顶部向下挑选出厂好块,存储 BBT 副本) */
#define GD5F_BBT_HDR_SIZE 16
static uint32_t s_bbt_pool_blocks[GD5F_BBT_POOL_COUNT]; /* BBT 保留池块号列表(出厂好块) */
static uint32_t s_bbt_pool_count = 0; /* 实际可用池块数(<= GD5F_BBT_POOL_COUNT) */
static uint32_t s_usable_blocks = GD5F_TOTAL_BLOCKS; /* dhara 可见块数(= 最低池块号) */
static uint32_t s_bbt_write_slot = 0; /* 下一写入槽(轮转指针) */
static uint32_t s_bbt_version = 0; /* 当前持久化 BBT 版本号 */
static uint8_t s_bbt_slot_dead = 0; /* 池块损坏位掩码 */
static uint8_t s_bbt_page_buf[GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE]; /* BBT 读写缓冲(静态,避免占栈):实际载荷仅 16B 头+256B 位图,按实际传输长度声明(2026-09-21 由整页 2048 收紧,省 1776B) */
static uint8_t s_bbt_best[GD5F_BBT_SIZE]; /* 最高版本位图缓存 */
static const uint8_t s_bbt_magic[4] = { 'G', 'B', 'B', 'T' }; /* BBT 存储魔数 "GBBT" */
/* 外部 SPI 句柄声明 */
extern SPI_HandleTypeDef hspi1;
/* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */
volatile uint8_t g_spi1_dma_done;
/* 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 物理不可达,
* RW_CCM 区任何缓冲都不得进 DMA。阈值只能挡住"当前已知的小栈缓冲",
* 挡不住"将来新加的 >1024B 栈缓冲";gd5f_dma_buf_ok() 把这条约定
* 升级为运行时强制检查(2026-09-21 加固)。若将来新增其它 DMA 可达
* 内存(如外部 SDRAM),此处区间必须同步扩展。 */
#define GD5F_DMA_RAM_BASE 0x20000000UL
#define GD5F_DMA_RAM_END 0x20020000UL
/*
* 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用)
* 入口参数:hspi - 触发回调的 SPI 句柄 SPI_HandleTypeDef*
* 返回值:无
* 限定条件:由 HAL DMA 中断调用
* 函数说明:仅当 SPI1 传输完成时置位 g_spi1_dma_done 标志
*/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
g_spi1_dma_done = 1U;
}
}
/*
* 函数功能:SPI1 RX DMA 完成回调(READ_FROM_CACHE 数据接收用)
* 函数说明:HAL_SPI_Receive_DMA() 在 2 线 master 模式下
* 内部走 HAL_SPI_TransmitReceive_DMA 但 state=BUSY_RX,
* 完成回调是 HAL_SPI_RxCpltCallback 而非 TxRxCpltCallback
*/
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
g_spi1_dma_done = 1U;
}
}
/* ======================== 私有函数声明 ======================== */
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
const uint8_t *p_buf, size_t size);
static int gd5f_set_feature(uint8_t addr, uint8_t data);
static int gd5f_private_get_feature(uint8_t addr, uint8_t *p_data);
/* BBT 持久化内部函数 */
static int gd5f_private_bbt_locate_pool(void);
static uint32_t gd5f_private_crc32(const uint8_t *p_buf, uint32_t len);
static int gd5f_private_bbt_find_best(void);
static int gd5f_bbt_load(void);
static int gd5f_bbt_save(void);
/* DMA 可达性守卫(见下方实现) */
static int gd5f_dma_buf_ok(const void *p_buf, size_t size);
/* ======================== DMA 可达性守卫 ======================== */
/*
* 函数功能:判定缓冲是否位于 DMA 可达内存(SRAM1+SRAM2)
* 入口参数:p_buf - 传输缓冲首地址 const void*
* size - 传输字节数 size_t
* 返回值:1 - 整段缓冲 DMA 可达;0 - 不可达(调用方须回落 CPU 轮询)
* 限定条件:无
* 函数说明:DMA1/DMA2 物理不可达 CCM(0x10000000)——以 CCM 缓冲做 DMA
* 源/目的会静默不落地(2026-09-20 首轮 CCM 扩容实验实证根因)。
* 本函数把"栈/堆缓冲禁入 DMA"的约定升级为运行时强制约束:
* 起止地址任一越出 SRAM 区间即判不可达。
*/
static int gd5f_dma_buf_ok(const void *p_buf, size_t size) {
uint32_t start = (uint32_t)(uintptr_t)p_buf;
uint32_t end;
if (size == 0U) {
return 0;
}
end = start + (uint32_t)size;
if (end < start) { /* 地址回绕:不可能落在同一区间内 */
return 0;
}
if ((start < GD5F_DMA_RAM_BASE) || (end > GD5F_DMA_RAM_END)) {
return 0;
}
return 1;
}
/* ======================== SPI 原语定义 ======================== */
/*
* 函数功能:等待芯片操作完成(轮询 OIP 位)
* 入口参数:timeout_ms - 超时时间 uint32_t > 0
* 返回值:GD5F_OK(0) - 操作完成;GD5F_BUSY_TIMEOUT(-2) - 超时(可重试);
* GD5F_ERROR(-1) - SPI 传输真故障(不可重试,须上报自检)
* 限定条件:SPI 已初始化
* 函数说明:循环读取状态寄存器直到 OIP 位清零或超时。
* E-53(2026-10-03,GD5F 错误通道统一):三处 HAL 调用的返回值**必须**检查
* ——原实现全部丢弃,失败时 `status` 保持初值 0 → `(status & OIP) == 0`
* 立即成立 → **把"操作完成"伪造出来**,后续命令在未就绪的芯片上执行,
* 最终表现为"静默报成功但数据没落盘"。现区分两类失败:
* ① SPI 层失败(HAL≠OK)→ GD5F_ERROR(真故障);
* ② OIP 长时间不落 → GD5F_BUSY_TIMEOUT(可重试)。
* 调用点据此分级:超时重试 / 真故障上报自检并降级(见审计 §5 第二档 #1)。
*/
/* 看门狗心跳弱钩子:默认空实现(无看门狗/模拟器构建);强符号由
* App/util/task_watchdog.c 提供,路由到调用者任务的心跳位 */
__weak void nand_ftl_wd_beat(void) {
}
/* 看门狗直接喂狗弱钩子:默认空实现;强符号由 App/util/task_watchdog.c
* 提供(IWDG 已启动时写 KR)——覆盖 defaultTask(喂狗点)自身陷于长 FTL
* 操作、主循环喂狗停摆的形态(2026-10-02 台架实证) */
__weak void nand_ftl_wd_feed(void) {
}
int32_t gd5f_wait_busy(uint32_t timeout_ms) {
uint8_t cmd = GD5F_CMD_GET_FEATURE;
uint8_t addr = GD5F_REG_STATUS;
uint8_t status = 0;
uint32_t tick_start = HAL_GetTick();
uint32_t last_beat = tick_start;
nand_ftl_wd_beat(); /* 看门狗:入口心跳(长操作在调用者任务上下文;内核前调用为空操作) */
nand_ftl_wd_feed(); /* 看门狗:入口直接喂狗(覆盖 defaultTask 长 I/O 阻塞主循环喂狗点的形态) */
while (1) {
HAL_StatusTypeDef hs;
GD5F_CS_LOW();
hs = HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
if (hs == HAL_OK) {
hs = HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
}
if (hs == HAL_OK) {
hs = HAL_SPI_Receive(&hspi1, &status, 1, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH();
if (hs != HAL_OK) {
/* E-53:SPI 真故障——绝不伪造成"操作完成"(详见函数说明) */
return GD5F_ERROR;
}
if ((status & GD5F_STATUS_OIP) == 0) {
return GD5F_OK;
}
if ((HAL_GetTick() - tick_start) >= timeout_ms) {
return GD5F_BUSY_TIMEOUT;
}
if ((uint32_t)(HAL_GetTick() - last_beat) >= 100u) {
last_beat = HAL_GetTick();
nand_ftl_wd_beat(); /* 看门狗:循环内每 100 ms 心跳(擦除等待最长 5 s) */
nand_ftl_wd_feed(); /* 看门狗:同步直接喂狗(同节奏) */
}
}
}
/*
* 函数功能:发送写使能命令
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:SPI 已初始化
* 函数说明:CS# 拉低后发 06h 命令再拉高 */
int32_t gd5f_write_enable(void) {
uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
return GD5F_OK;
}
/*
* 函数功能:读取状态寄存器
* 出口参数:p_status - 状态值输出指针 uint8_t* 不为 NULL
* 返回值:0 - 成功
* 限定条件:SPI 已初始化
* 函数说明:发 0Fh + C0h 地址后读 1 字节状态 */
int32_t gd5f_read_status(uint8_t *p_status) {
uint8_t cmd = GD5F_CMD_GET_FEATURE;
uint8_t addr = GD5F_REG_STATUS;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
HAL_SPI_Receive(&hspi1, p_status, 1, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
return GD5F_OK;
}
/*
* 函数功能:发送一次 13h 页读取命令(gd5f_page_read 的装载单元)
* 入口参数:page_addr - 页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
* 返回值:无
* 限定条件:SPI 已初始化
* 函数说明:发 13h + 3字节行地址
*/
static void gd5f_page_read_issue(uint32_t page_addr) {
uint8_t cmd[4] = {0};
cmd[0] = GD5F_CMD_PAGE_READ;
cmd[1] = (page_addr >> 16) & 0xFF;
cmd[2] = (page_addr >> 8) & 0xFF;
cmd[3] = page_addr & 0xFF;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
}
/*
* 函数功能:页读取(将数据从存储阵列加载到内部缓存)
* 入口参数:page_addr - 页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
* 返回值:0 - 成功,其它 - 错误
* 限定条件:SPI 已初始化
* 函数说明:13h 单次发令 + WIP 轮询(与 Linux 主线 / GigaDevice 厂商驱动
* 形状完全一致:13h×1 → OIP 轮询 → 0Bh)。
* 2026-09-20 台架异常史实(归因、证伪、沉降/二次发令的加与除)
* 统一记于 docs/问题记录/GD5F2GQ5UE_Trap_Records.md Trap 05,
* 复现手段见测试 Phase 11 用例。
*/
int32_t gd5f_page_read(uint32_t page_addr) {
int ret;
gd5f_page_read_issue(page_addr);
ret = gd5f_wait_busy(100);
if (ret != GD5F_OK) {
return ret;
}
return GD5F_OK;
}
/*
* 函数功能:从内部缓存读取数据
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
* size - 读取字节数 size_t > 0
* 出口参数:p_buf - 数据输出缓冲区 uint8_t* 不为 NULL
* 返回值:0 - 成功
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
* 函数说明:发 0Bh + 2字节列地址 +1字节 dummy 后读取数据 */
int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
size_t size) {
uint8_t cmd[4] = {0};
uint8_t use_dma = 0U;
/* 守卫判定须在 CS 拉低之前完成:DBG_ERROR 走串口,不能占着 CS 窗口 */
if (size > GD5F_DMA_THRESHOLD) {
use_dma = (uint8_t)gd5f_dma_buf_ok(p_buf, size);
if (use_dma == 0U) {
DBG_ERROR("RX DMA buffer not DMA-reachable "
"(p=0x%08lX size=%lu), fallback poll",
(unsigned long)(uintptr_t)p_buf, (unsigned long)size);
}
}
cmd[0] = GD5F_CMD_READ_FROM_CACHE;
cmd[1] = (column >> 8) & 0xFF;
cmd[2] = column & 0xFF;
cmd[3] = 0x00;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
if (use_dma != 0U) {
uint32_t tick_start = HAL_GetTick();
g_spi1_dma_done = 0U;
if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) {
GD5F_CS_HIGH();
return GD5F_ERROR;
}
while (g_spi1_dma_done == 0U) {
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
HAL_SPI_DMAStop(&hspi1);
GD5F_CS_HIGH();
return GD5F_BUSY_TIMEOUT;
}
}
} else {
HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH();
return GD5F_OK;
}
/*
* 函数功能:页编程(将数据写入指定页)
* 入口参数:page_addr - 页地址 uint32_t
* column - 列地址 uint16_t 0 - 2047
* p_buf - 数据缓冲区 const uint8_t*
* size - 写入字节数 size_t > 0
* 返回值:0 - 成功,其它 - 错误 * 限定条件:目标区域已擦除
* 函数说明:1. 写使能 - 02h 加载数据 - 10h 执行编程 - 等待完成
* 2. 编程完成后检查 P_FAIL 位 */
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
const uint8_t *p_buf, size_t size) {
int ret;
gd5f_write_enable();
ret = gd5f_program_load(column, p_buf, size);
if (ret != GD5F_OK) return ret;
return gd5f_program_exec(page_addr);
}
/*
* 函数功能:将数据加载到内部缓存(PROGRAM LOAD)
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
* p_buf - 待写入数据缓冲区 const uint8_t* 不为 NULL
* size - 写入字节数 size_t > 0
* 返回值:0 - 成功,其它 - 错误
* 限定条件:SPI 已初始化,目标区域已擦除
* 函数说明:发 02h + 2字节列地址后发送数据;超过 DMA 阈值走 DMA
*/
/*
* 函数功能:将数据加载到芯片内部缓存(PROGRAM LOAD)
* 入口参数:column - 列(页内)偏移 uint16_t 本驱动恒为 0
* p_buf - 数据缓冲区 const uint8_t* 不为 NULL
* size - 数据长度 size_t > 0
* 返回值:0 - 成功,其它 - 错误
* 限定条件:SPI 已初始化;目标区域已擦除
* 函数说明:发 02h + 2字节列地址后发送数据;超过 DMA 阈值走 DMA。
* (buffer 由 SPI 数据流直接填充,无 array→buffer 竞态,
* 无需二次装载;program exec 的落盘校验由 FTL 回读完成)
*/
int32_t gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) {
uint8_t cmd[3];
uint8_t use_dma = 0U;
/* 守卫判定须在 CS 拉低之前完成:DBG_ERROR 走串口,不能占着 CS 窗口 */
if (size > GD5F_DMA_THRESHOLD) {
use_dma = (uint8_t)gd5f_dma_buf_ok(p_buf, size);
if (use_dma == 0U) {
DBG_ERROR("TX DMA buffer not DMA-reachable "
"(p=0x%08lX size=%lu), fallback poll",
(unsigned long)(uintptr_t)p_buf, (unsigned long)size);
}
}
cmd[0] = GD5F_CMD_PROGRAM_LOAD;
cmd[1] = (uint8_t)(column >> 8);
cmd[2] = (uint8_t)(column);
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
if (use_dma != 0U) {
uint32_t tick_start = HAL_GetTick();
g_spi1_dma_done = 0U;
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size)
!= HAL_OK) {
GD5F_CS_HIGH();
return GD5F_ERROR;
}
while (g_spi1_dma_done == 0U) {
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
HAL_SPI_DMAStop(&hspi1);
GD5F_CS_HIGH();
return GD5F_BUSY_TIMEOUT;
}
}
} else {
HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH();
return GD5F_OK;
}
/*
* 函数功能:执行页编程(PROGRAM EXECUTE)
* 入口参数:page_addr - 目标页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
* 返回值:0 - 成功,其它 - 错误(GD5F_PROGRAM_FAIL 等)
* 限定条件:已先调用 gd5f_program_load 加载数据
* 函数说明:发 10h + 3字节页地址触发编程,等待完成后检查 P_FAIL 位
*/
int32_t gd5f_program_exec(uint32_t page_addr) {
int ret;
uint8_t cmd[4];
uint8_t status;
cmd[0] = GD5F_CMD_PROGRAM_EXEC;
cmd[1] = (page_addr >> 16) & 0xFF;
cmd[2] = (page_addr >> 8) & 0xFF;
cmd[3] = page_addr & 0xFF;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
ret = gd5f_wait_busy(1000);
if (ret != GD5F_OK) return ret;
gd5f_read_status(&status);
if (status & GD5F_STATUS_P_FAIL) return GD5F_PROGRAM_FAIL;
return GD5F_OK;
}
/*
* 函数功能:检查上次读操作的 ECC 状态
* 入口参数:无
* 返回值:0 - 无 ECC 错误,GD5F_ECC_ERROR - 存在不可纠正 ECC 错误
* 限定条件:SPI 已初始化,ECC 已使能
* 函数说明:读取状态寄存器 ECCS[5:4] 位,0x02 表示不可纠正错误
*/
int32_t gd5f_check_ecc(void) {
uint8_t status;
gd5f_read_status(&status);
uint8_t ecc = (status >> 4) & 0x03;
if (ecc == 0x02) return GD5F_ECC_ERROR;
return GD5F_OK;
}
/*
* 函数功能:设置 Feature 寄存器
* 入口参数:addr - 寄存器地址 uint8_t
* data - 写入数据 uint8_t
* 返回值:GD5F_OK - 成功;其它 - SPI 传输/等待失败错误码
* 限定条件:SPI 已初始化
* 函数说明:先写使能,发 1Fh + 地址 + 数据,等待操作完成。
* E-53(2026-10-03):HAL 传输与 wait_busy 的返回值必须检查并上报——
* 原实现恒返回 GD5F_OK,"配置未生效"对上层不可见(E-10 同族问题)。
*/
static int gd5f_set_feature(uint8_t addr, uint8_t data) {
uint8_t cmd[3];
int ret;
gd5f_write_enable();
cmd[0] = GD5F_CMD_SET_FEATURE;
cmd[1] = addr;
cmd[2] = data;
GD5F_CS_LOW();
if (HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT) != HAL_OK) {
GD5F_CS_HIGH();
return GD5F_ERROR;
}
GD5F_CS_HIGH();
ret = gd5f_wait_busy(100);
if (ret != GD5F_OK) {
return ret;
}
return GD5F_OK;
}
/*
* 函数功能:读取任意 Feature 寄存器(0Fh + 地址 + 读 1 字节)
* 入口参数:addr - feature 寄存器地址 uint8_t
* 出口参数:p_data - 读到的字节输出指针 uint8_t* 不为 NULL
* 返回值:GD5F_OK - 成功;GD5F_ERROR - SPI 传输失败
* 限定条件:SPI 已初始化
* 函数说明:E-10(2026-10-03):init 需**回读** B0h 校验 ECC 使能位真正生效
* (原实现只写不读,"init 返回 OK 但 ECC 未打开"→ 存储栈在无 ECC
* 保护下静默降级)。与 gd5f_read_status 同构(后者固定 C0h),
* 此处参数化地址;同样检查 HAL 返回值(E-53)。
*/
static int gd5f_private_get_feature(uint8_t addr, uint8_t *p_data) {
uint8_t cmd = GD5F_CMD_GET_FEATURE;
HAL_StatusTypeDef hs;
GD5F_CS_LOW();
hs = HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
if (hs == HAL_OK) {
hs = HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
}
if (hs == HAL_OK) {
hs = HAL_SPI_Receive(&hspi1, p_data, 1, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH();
return (hs == HAL_OK) ? GD5F_OK : GD5F_ERROR;
}
/*
* 函数功能:块擦除(擦除128KB块)
* 入口参数:block_addr - 块编号 uint32_t 0 - 2047
* 返回值:0 - 成功,其他 - 错误码
* 限定条件:gd5f2gq5ue_init() 已调用
* 函数说明:1. 写使能 - D8h + 3字节页地址 - 等待完成
* 2. 擦除完成后检查 E_FAIL */
int32_t gd5f_block_erase(uint32_t block_addr) {
int ret = GD5F_OK;
uint8_t cmd[4];
uint8_t status = 0;
uint32_t page_addr = block_addr * GD5F_PAGES_PER_BLOCK;
gd5f_write_enable();
cmd[0] = GD5F_CMD_BLOCK_ERASE;
cmd[1] = (page_addr >> 16) & 0xFF;
cmd[2] = (page_addr >> 8) & 0xFF;
cmd[3] = page_addr & 0xFF;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
ret = gd5f_wait_busy(5000);
if (ret != GD5F_OK) {
return ret;
}
gd5f_read_status(&status);
if (status & GD5F_STATUS_E_FAIL) {
return GD5F_ERASE_FAIL;
}
return GD5F_OK;
}
/*
* 函数功能:扫描所有块构建 BBT(在 ECC 使能前调用)
* 入口参数:无
* 返回值:GD5F_OK - 扫描完成;其它 - 读失败错误码(扫描已中止,s_bbt 为部分结果)
* 限定条件:SPI 已初始化,ECC 尚未使能
* 函数说明:读取每个块首页(page 0)的 spare byte 0,
* 若不为 0xFF 则为出厂坏块(符合 datasheet §12.4)。
* E-42(2026-10-03,GD5F 错误通道统一):原实现丢弃 page_read /
* read_from_cache 的返回值且对外恒返回 GD5F_OK——SPI 读失败时
* `spare[8]` 保持上一轮的**陈旧内容**,且"整片扫描全失败也返回成功",
* 上层以为坏块表可用(rebuild 路径还会把错表持久化覆盖好副本)。
* 现:① 每轮先清零 spare(防陈旧内容误判);② 任一读失败即**中止**
* 并返回错误码,由调用方决定"放弃持久化 / 让 init 失败"。
*/
static int gd5f_bbt_scan(void) {
uint32_t block;
uint8_t spare[8];
uint32_t bad_count = 0;
memset(s_bbt, 0, GD5F_BBT_SIZE);
for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) {
uint32_t page = block << 6;
int ret;
memset(spare, 0, sizeof(spare)); /* 每轮清零:防读失败时用陈旧内容误判 */
ret = gd5f_page_read(page);
if (ret != GD5F_OK) {
DBG_ERROR("BBT scan: page_read fail at block %lu (ret=%d)",
(unsigned long)block, ret);
return ret; /* 扫描失败即中止,不得对外报"扫描完成" */
}
ret = gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1);
if (ret != GD5F_OK) {
DBG_ERROR("BBT scan: read_from_cache fail at block %lu (ret=%d)",
(unsigned long)block, ret);
return ret;
}
if (spare[0] != 0xFF) {
s_bbt[block >> 3] |= (1 << (block & 7));
bad_count++;
}
}
DBG_INFO("BBT scan: %lu bad blocks found", bad_count);
return GD5F_OK;
}
/*
* 函数功能:清空 RAM 中的 BBT
* 入口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:仅清空内存位图,不影响闪存中的持久化副本
*/
void gd5f2gq5ue_bbt_clear(void) {
memset(s_bbt, 0, GD5F_BBT_SIZE);
}
/*
* 函数功能:重建 BBT(重新扫描出厂坏块并持久化,覆盖运行时注入的坏块)
* 入口参数:无
* 返回值:GD5F_OK - 成功,其他 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:临时关闭 ECC 重新扫描每块 page0 spare[0] 的出厂坏块标记,覆盖 RAM BBT,
* 再写回持久化 BBT 池(版本 +1,掉电安全),从而清除 TC-STO-03 等运行时
* 标记的坏块。调用后建议再执行 nand_ftl_format() 清空 dhara map。
*/
int32_t gd5f2gq5ue_bbt_rebuild(void) {
int ret;
DBG_INFO("Rebuilding BBT from factory scan...");
/* 1. 关闭 ECC 以读取原始出厂坏块标记(与 gd5f2gq5ue_init 的扫描前提一致) */
gd5f_set_feature(0xB0, 0x00);
gd5f_wait_busy(100);
/* 2. 重新扫描出厂坏块(覆盖 RAM BBT,剔除运行时注入的坏块) */
ret = gd5f_bbt_scan();
/* 3. 重新使能 ECC(无论扫描成败都须恢复,否则后续 I/O 在无 ECC 下运行) */
gd5f_set_feature(0xB0, 0x10);
gd5f_wait_busy(100);
/* E-42(2026-10-03,数据安全优先):扫描失败 → **放弃持久化**(保留持久区
* 既有好副本),并从盘上回读 BBT 恢复 RAM 视图。原实现无论扫描成败都照常
* save(),会把"读失败拼出来的错表"覆盖掉正确副本。 */
if (ret != GD5F_OK) {
DBG_ERROR("BBT rebuild: scan FAIL (ret=%d), persist abandoned", ret);
(void)gd5f_bbt_load();
return ret;
}
/* 4. 持久化重建后的 BBT(覆盖持久化区中的脏副本) */
if (gd5f_bbt_save() != GD5F_OK) {
DBG_ERROR("BBT rebuild: persist failed");
return GD5F_ERROR;
}
DBG_INFO("BBT rebuilt and persisted");
return GD5F_OK;
}
/*
* 函数功能:通过调试串口打印当前 BBT 统计与坏块列表
* 入口参数:无
* 返回值:无
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:最多打印前 32 个坏块块号与偏移
*/
void gd5f2gq5ue_print_bbt(void) {
uint32_t bad_count = 0;
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) {
if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad_count++;
}
DBG_INFO("BBT: %lu/%lu blocks bad", bad_count, GD5F_TOTAL_BLOCKS);
if (bad_count > 0) {
uint32_t shown = 0;
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS && shown < 32; b++) {
if ((s_bbt[b >> 3] >> (b & 7)) & 1) {
DBG_INFO(" block %lu (offset 0x%08lX)", b, b * GD5F_BLOCK_SIZE);
shown++;
}
}
if (bad_count > 32) DBG_INFO(" ... and %lu more", bad_count - 32);
}
}
/* ======================== 公共函数定义 ======================== */
/*
* 函数功能:初始化 GD5F2GQ5UE
* 入口参数:无
* 返回值:0 - 成功,其他 - 错误码
* 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成
* 函数说明:1. 发送复位命令并等待完成
* 2. 读取芯片 ID 并校验
* 3. 扫描出厂坏块构建 BBT(ECC 使能前,读每块首页 spare[0])
* 4. 使能内部 ECC (B0h bit4)
* 5. 解除所有块保护 (A0h = 0x00)
* 6. 定位保留块池并加载持久化 BBT(ECC 使能后)
*/
int32_t gd5f2gq5ue_init(void) {
int ret = GD5F_OK;
uint8_t mid = 0;
uint8_t did = 0;
GD5F_CS_HIGH();
GD5F_WP_HIGH();
GD5F_HOLD_HIGH();
HAL_Delay(10);
DBG_INFO("Resetting NAND...");
ret = gd5f2gq5ue_reset();
if (ret != GD5F_OK) {
DBG_ERROR("Reset failed: %d", ret);
return ret;
}
HAL_Delay(5);
DBG_INFO("Reading NAND ID...");
ret = gd5f2gq5ue_read_id(&mid, &did);
if (ret != GD5F_OK) {
DBG_ERROR("Read ID failed: %d", ret);
return ret;
}
DBG_INFO("NAND ID: MID=0x%02X, DID=0x%02X", mid, did);
if (mid != GD5F_MANUFACTURER_ID || did != GD5F_DEVICE_ID) {
DBG_ERROR("ID mismatch: expected MID=0x%02X DID=0x%02X, got MID=0x%02X DID=0x%02X",
GD5F_MANUFACTURER_ID, GD5F_DEVICE_ID, mid, did);
return GD5F_ID_MISMATCH;
}
DBG_INFO("Scanning bad blocks...");
ret = gd5f_bbt_scan();
if (ret != GD5F_OK) {
/* E-10:扫坏块失败 → init 失败,不静默继续(上层据此上报自检并降级) */
DBG_ERROR("BBT scan failed: %d", ret);
return ret;
}
DBG_INFO("Enabling ECC...");
ret = gd5f_set_feature(0xB0, 0x10);
if (ret != GD5F_OK) {
DBG_ERROR("Enable ECC failed: %d", ret);
return ret;
}
/* E-10:回读 B0h 校验 ECC 使能位(bit4)**真正生效**——原实现只写不读,
* "init 返回 OK 但 ECC 未真正打开"会使存储栈在无 ECC 保护下静默降级。 */
{
uint8_t feat = 0;
ret = gd5f_private_get_feature(GD5F_REG_FEATURE, &feat);
if (ret != GD5F_OK) {
DBG_ERROR("Read B0h failed: %d", ret);
return ret;
}
if ((feat & GD5F_FEATURE_ECC_EN) == 0u) {
DBG_ERROR("ECC not enabled (B0h=0x%02X)", feat);
return GD5F_ERROR;
}
}
DBG_INFO("Unlocking block protection...");
ret = gd5f_set_feature(GD5F_REG_PROTECT, 0x00);
if (ret != GD5F_OK) {
DBG_ERROR("Unlock block protection failed: %d", ret);
return ret;
}
DBG_INFO("Locating BBT pool...");
ret = gd5f_private_bbt_locate_pool();
if (ret != GD5F_OK) {
DBG_ERROR("Locate BBT pool failed: %d", ret);
return ret;
}
DBG_INFO("Loading persisted BBT...");
ret = gd5f_bbt_load();
if (ret != GD5F_OK) {
/* BBT 持久副本缺失/损坏不致命(RAM BBT 已由扫描建立),仅告警 */
DBG_ERROR("Load persisted BBT failed: %d (use scanned BBT)", ret);
}
DBG_INFO("NAND init OK");
return GD5F_OK;
}
/*
* 函数功能:读取芯片 ID(MID + DID)
* 出口参数:p_mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* p_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) {
uint8_t cmd = GD5F_CMD_READ_ID;
uint8_t id_buf[3] = {0};
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
HAL_SPI_Receive(&hspi1, id_buf, 3, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
*p_mid = id_buf[1];
*p_did = id_buf[2];
return GD5F_OK;
}
/*
* 函数功能:从 NAND 读取数据(支持跨页)
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
* size - 读取字节数 size_t > 0
* 出口参数:p_buf - 数据缓冲区 uint8_t* 不为 NULL
* 返回值:0 - 成功,其它 - 错误
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:自动处理跨页读取;offset+size 越界返回错误
*/
int32_t gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
return GD5F_ERROR;
}
while (size > 0) {
int ret;
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
uint16_t column = offset % GD5F_PAGE_SIZE;
size_t bytes = GD5F_PAGE_SIZE - column;
if (bytes > size) {
bytes = size;
}
ret = gd5f_page_read(page_addr);
if (ret != GD5F_OK) {
return ret;
}
ret = gd5f_read_from_cache(column, p_buf, bytes);
if (ret != GD5F_OK) {
return ret;
}
offset += bytes;
p_buf += bytes;
size -= bytes;
}
return GD5F_OK;
}
/*
* 函数功能:向 NAND 写入数据(支持跨页)
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
* p_buf - 数据缓冲区 const uint8_t* 不为 NULL
* size - 写入字节数 size_t > 0
* 返回值:0 - 成功,其它 - 错误
* 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦除
* 函数说明:自动处理跨页写入;offset+size 越界返回错误
*/
int32_t gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
return GD5F_ERROR;
}
while (size > 0) {
int ret;
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
uint16_t column = offset % GD5F_PAGE_SIZE;
size_t bytes = GD5F_PAGE_SIZE - column;
if (bytes > size) {
bytes = size;
}
ret = gd5f_page_program(page_addr, column, p_buf, bytes);
if (ret != GD5F_OK) {
return ret;
}
offset += bytes;
p_buf += bytes;
size -= bytes;
}
return GD5F_OK;
}
/*
* 函数功能:擦除块(按块擦除,最小单位 128KB)
* 入口参数:offset - 起始字节偏移 long 必须 GD5F_BLOCK_SIZE 对齐
* size - 擦除字节数 size_t 必须 GD5F_BLOCK_SIZE 整数倍
* 返回值:0 - 成功,其它 - 错误码
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:循环擦除 [offset, offset+size) 覆盖的每个块,偏移/长度不对齐返回错误
*/
int32_t gd5f2gq5ue_erase(long offset, size_t size) {
if (offset % GD5F_BLOCK_SIZE != 0) {
return GD5F_ERROR;
}
if (size % GD5F_BLOCK_SIZE != 0) {
return GD5F_ERROR;
}
while (size > 0) {
int ret;
uint32_t block_addr = offset / GD5F_BLOCK_SIZE;
ret = gd5f_block_erase(block_addr);
if (ret != GD5F_OK) {
return ret;
}
offset += GD5F_BLOCK_SIZE;
size -= GD5F_BLOCK_SIZE;
}
return GD5F_OK;
}
/*
* 函数功能:复位芯片
* 返回值:0 - 成功
* 限定条件:SPI 已初始化
* 函数说明:发 FFh 复位命令后等待 5ms
*/
int32_t gd5f2gq5ue_reset(void) {
uint8_t cmd = GD5F_CMD_RESET;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
HAL_Delay(5);
return GD5F_OK;
}
/*
* 函数功能:查询块是否坏块
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:0 - 好块,1 - 坏块
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:通过 BBT 查询
*/
int32_t gd5f2gq5ue_is_block_bad(uint32_t block) {
if (block >= GD5F_TOTAL_BLOCKS) {
return 1;
}
return (s_bbt[block >> 3] >> (block & 7)) & 1;
}
/*
* 函数功能:标记块为坏块
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:无
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:更新 BBT
*/
void gd5f2gq5ue_mark_block_bad(uint32_t block) {
if (block >= GD5F_TOTAL_BLOCKS) {
return;
}
uint32_t byte = block >> 3;
uint8_t bit = (uint8_t)(1U << (block & 7));
if (s_bbt[byte] & bit) {
return; /* 已是坏块,幂等,无需重复持久化 */
}
s_bbt[byte] |= bit;
gd5f_bbt_save(); /* 持久化到保留块池(带版本号 + CRC) */
}
/* ======================== BBT 持久化实现 ======================== */
/*
* 函数功能:从 NAND 顶部向下挑选出厂好块作为 BBT 保留池
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f_bbt_scan() 已完成(s_bbt 已就绪)
* 函数说明:保留池不交给 dhara 管理;最低池块号以上的块全部排除,
* 并将可用块数 s_usable_blocks 设为最低池块号
*/
static int gd5f_private_bbt_locate_pool(void) {
uint32_t cnt = 0;
uint32_t b = GD5F_TOTAL_BLOCKS;
while (b-- > 0 && cnt < GD5F_BBT_POOL_COUNT) {
if (((s_bbt[b >> 3] >> (b & 7)) & 1) == 0) {
s_bbt_pool_blocks[cnt++] = b;
}
}
s_bbt_pool_count = cnt;
if (cnt > 0) {
s_usable_blocks = s_bbt_pool_blocks[cnt - 1]; /* 最低池块 = dhara 可见上限 */
}
return GD5F_OK;
}
/*
* 函数功能:CRC32 计算(IEEE 802.3,多项式 0xEDB88320)
* 入口参数:p_buf - 数据缓冲区 const uint8_t* 不为 NULL
* len - 数据长度 uint32_t > 0
* 返回值:32 位 CRC 校验值
* 限定条件:无
* 函数说明:采用位级(bit-by-bit)实现,初始值 0xFFFFFFFF
*/
static uint32_t gd5f_private_crc32(const uint8_t *p_buf, uint32_t len) {
uint32_t crc = 0xFFFFFFFFU;
for (uint32_t i = 0; i < len; i++) {
crc ^= p_buf[i];
for (int j = 0; j < 8; j++) {
uint32_t mask = (crc & 1U) ? 0xEDB88320U : 0U;
crc = (crc >> 1) ^ mask;
}
}
return ~crc;
}
/*
* 函数功能:在保留池中找出版本号最高的有效 BBT 副本
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
* 函数说明:校验 magic / len / CRC;结果缓存到 s_bbt_best / s_bbt_version,
* 并设置下一轮转写入槽 s_bbt_write_slot
*/
static int gd5f_private_bbt_find_best(void) {
uint32_t best_ver = 0;
int best_slot = -1;
s_bbt_slot_dead = 0;
for (uint32_t i = 0; i < s_bbt_pool_count; i++) {
uint32_t block = s_bbt_pool_blocks[i];
uint32_t page = block * GD5F_PAGES_PER_BLOCK;
uint32_t ver, len, crc;
gd5f_page_read(page);
gd5f_read_from_cache(0, s_bbt_page_buf, GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE);
if (memcmp(s_bbt_page_buf, s_bbt_magic, 4) != 0) {
continue;
}
memcpy(&ver, s_bbt_page_buf + 4, 4);
memcpy(&len, s_bbt_page_buf + 8, 4);
memcpy(&crc, s_bbt_page_buf + 12, 4);
if (len != GD5F_BBT_SIZE) {
continue;
}
if (gd5f_private_crc32(s_bbt_page_buf + GD5F_BBT_HDR_SIZE, GD5F_BBT_SIZE) != crc) {
continue;
}
if (best_slot < 0 || (int32_t)ver > (int32_t)best_ver) {
best_ver = ver;
best_slot = (int)i;
memcpy(s_bbt_best, s_bbt_page_buf + GD5F_BBT_HDR_SIZE, GD5F_BBT_SIZE);
}
}
if (best_slot >= 0) {
s_bbt_version = best_ver;
s_bbt_write_slot = (uint32_t)(best_slot + 1) % s_bbt_pool_count;
} else {
s_bbt_write_slot = 0;
}
return GD5F_OK;
}
/*
* 函数功能:加载持久化 BBT(OR 进 RAM BBT)
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
* 函数说明:工厂扫描已填好块,持久化副本补充运行期坏块
*/
static int gd5f_bbt_load(void) {
gd5f_private_bbt_find_best();
if (s_bbt_pool_count == 0) {
return GD5F_OK;
}
for (uint32_t i = 0; i < GD5F_BBT_SIZE; i++) {
s_bbt[i] |= s_bbt_best[i];
}
return GD5F_OK;
}
/*
* 函数功能:将当前 RAM BBT 持久化到保留池(轮转写入,掉电安全)
* 入口参数:无
* 返回值:0 - 成功,GD5F_ERROR - 写入失败
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
* 函数说明:版本号自增;先擦除目标块再编程;损坏块标记后跳到下一槽
*/
static int gd5f_bbt_save(void) {
if (s_bbt_pool_count == 0) {
return GD5F_OK;
}
uint32_t slot = s_bbt_write_slot;
while (s_bbt_slot_dead & (1U << slot)) {
slot = (slot + 1) % s_bbt_pool_count;
if (slot == s_bbt_write_slot) {
break; /* 所有池块均损坏 */
}
}
uint32_t block = s_bbt_pool_blocks[slot];
uint32_t page = block * GD5F_PAGES_PER_BLOCK;
uint32_t ver = s_bbt_version + 1;
uint32_t len = GD5F_BBT_SIZE;
uint32_t crc = gd5f_private_crc32(s_bbt, GD5F_BBT_SIZE);
if (gd5f_block_erase(block) != GD5F_OK) {
s_bbt_slot_dead |= (1U << slot);
s_bbt_write_slot = (slot + 1) % s_bbt_pool_count;
return GD5F_ERROR;
}
memcpy(s_bbt_page_buf, s_bbt_magic, 4);
memcpy(s_bbt_page_buf + 4, &ver, 4);
memcpy(s_bbt_page_buf + 8, &len, 4);
memcpy(s_bbt_page_buf + 12, &crc, 4);
memcpy(s_bbt_page_buf + GD5F_BBT_HDR_SIZE, s_bbt, GD5F_BBT_SIZE);
gd5f_write_enable();
gd5f_program_load(0, s_bbt_page_buf, GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE);
gd5f_program_exec(page);
gd5f_wait_busy(100);
s_bbt_version = ver;
s_bbt_write_slot = (slot + 1) % s_bbt_pool_count;
return GD5F_OK;
}
/*
* 函数功能:获取 dhara 可用块数(= 最低保留池块号)
* 入口参数:无
* 返回值:dhara 可见的块数量(= 保留块池起始块号)
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:保留块池用于存储持久化 BBT,不交给 dhara 管理
*/
uint32_t gd5f_get_usable_blocks(void) {
return s_usable_blocks;
}
/*
* 函数功能:从闪存读取当前生效的 BBT 位图(测试/调试用)
* 入口参数:无
* 出口参数:p_bbt - 位图输出缓冲 uint8_t* 不为 NULL,长度 >= GD5F_BBT_SIZE
* p_version - 版本号输出 uint32_t* 不为 NULL
* 返回值:0 - 成功,GD5F_ERROR - 参数非法
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:返回闪存中版本号最高的有效 BBT 拷贝(供测试验证持久化)
*/
int32_t gd5f_bbt_dump_flash(uint8_t *p_bbt, uint32_t *p_version) {
if (p_bbt == NULL || p_version == NULL) {
return GD5F_ERROR;
}
gd5f_private_bbt_find_best();
memcpy(p_bbt, s_bbt_best, GD5F_BBT_SIZE);
*p_version = s_bbt_version;
return GD5F_OK;
}
/*
* 函数功能:擦除保留块池(撤销持久化 BBT,回到仅工厂扫描态)
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:仅供测试/出厂重置;调用方需自行还原 RAM BBT
*/
int32_t gd5f_bbt_wipe_pool(void) {
if (s_bbt_pool_count == 0) {
return GD5F_OK;
}
for (uint32_t i = 0; i < s_bbt_pool_count; i++) {
gd5f_block_erase(s_bbt_pool_blocks[i]);
}
s_bbt_write_slot = 0;
s_bbt_version = 0;
return GD5F_OK;
}
/*
* 函数功能:从保留块池重新加载持久化 BBT 到 RAM(测试/调试用)
* 入口参数:无
* 返回值:0 - 成功
* 限定条件:gd5f2gq5ue_init() 已成功调用
* 函数说明:将闪存中版本号最高的有效副本 OR 进 RAM BBT
*/
int32_t gd5f_bbt_reload(void) {
return gd5f_bbt_load();
}