/* * 模块名称: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 持久化 * 2026-11-04 问题 #79 取证配套:① init 解锁块保护(A0h)后补回读校验—— * 与 2026-10-23 修过的 ECC(B0h)同族的"只写不回读"缺口 * (实测本板 A0h 已为 0,写保护非本次根因,校验留作哨兵); * ② `gd5f_bbt_load()` 合并后若坏块数 > 出厂扫描数则打一条 * "BBT after merge",让现场一眼区分真坏与误标固化。 */ /* 头文件包含区 */ #include #include "gd5f2gq5ue.h" #include "spi.h" /* SPI1 句柄由 Inc/spi.h 统一导出,本文件不再重复 extern(2026-10-22 分层纠偏) */ /* * 调试输出配置 * 深调用链模块(NAND 驱动在 FTL 之下):日志行缓冲收到 128 B(默认 192)。 * 本模块最长行 "ID mismatch: expected MID=0x.. DID=0x.., got MID=0x.. DID=0x.." * 渲染后 ≈ 103 B(含时间戳 21 + 标签)< 128 → 不截断 */ #define DBG_LINE_BUF_MAX 128U #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" */ /* DMA 完成标志(本模块私有,2026-10-22 收编): * HAL 的 SPI 完成/错误回调是**全局唯一符号**(整个工程只能有一份实现),当前由 * Drivers/BSP/CH395F/ch395f.c 实现并按 hspi->Instance 分发——SPI1 分支必须转调本 * 模块导出的 gd5f_spi1_notify_done() / gd5f_spi1_notify_error(),不得反过来由 * CH395F 直接写本模块的变量(原先是跨模块裸 extern,删掉那两行不会报错,只会让 * 下面的 DMA 等待循环在出错时退化成干等 GD5F_SPI_TIMEOUT)。 */ static volatile uint8_t s_spi1_dma_done; /* DMA 阈值(字节数超过此值使用 DMA 传输):取值定义在头文件,理由如下—— * 阈值以下走 CPU 轮询。存在把**栈上小缓冲**直接传进本驱动做收发的大小 * (dhara meta 读 132B、dhara_nand_is_free 64B),任务栈随 FreeRTOS 堆驻留 * CCM(0x10000000),而 DMA1/DMA2 物理上不可达 CCM——此类传输必须留在轮询域, * 否则静默失败(CCM 首次扩容实验实证的根因)。页级大块传输(2048)仍走 DMA, * 源/目的全部为文件作用域静态缓冲。 */ /* DMA 可达内存窗口 GD5F_DMA_RAM_BASE/END 与 CCM 窗口常量已上移到 gd5f2gq5ue.h * (DMA 可达性三层防线,2026-10-12:nand_ftl.c 的 init 期自检也要用),并带编译期守卫 * (白名单窗口与 CCM 永不相交、阈值契约)。此处只留语义提醒: * 窗口必须与分散加载 RW_IRAM1(0x20000000,128KB,SRAM1+SRAM2)一致; * CCM(0x10000000,64KB)DMA1/DMA2 物理不可达,RW_CCM 区任何缓冲都不得进 DMA。 * 阈值只能挡住"当前已知的小栈缓冲",挡不住"将来新加的 >1024B 栈缓冲"; * gd5f_dma_buf_ok() 把这条约定升级为运行时强制检查(2026-09-21 加固)。 */ /* * 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用) * 入口参数:hspi - 触发回调的 SPI 句柄 SPI_HandleTypeDef* * 返回值:无 * 限定条件:由 HAL DMA 中断调用 * 函数说明:仅当 SPI1 传输完成时置位 s_spi1_dma_done 标志 */ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { s_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) { s_spi1_dma_done = 1U; } } /* * 函数功能:SPI1 DMA 完成通知(供实际注册 HAL 回调的一方转调) * 入口参数:无 * 返回值:无 * 限定条件:由 HAL SPI 回调(中断上下文)调用;只写一个标志,不阻塞 * 函数说明:HAL 的 SPI 回调在整个工程里只能有一份实现,本模块不抢占 Error 回调 * 的所有权,只导出这两个通知入口。**转调方不得省略本调用**——少了它, * 出错路径只能靠 GD5F_SPI_TIMEOUT 超时退出(能自保但会静默变慢)。 */ void gd5f_spi1_notify_done(void) { s_spi1_dma_done = 1U; } /* * 函数功能:SPI1 DMA 错误通知(释放等待者;语义与 notify_done 相同) * 入口参数:无 * 返回值:无 * 限定条件:由 HAL SPI 错误回调(中断上下文)调用 */ void gd5f_spi1_notify_error(void) { s_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 可达性守卫:实现见下方(随 DMA 可达性三层防线升为公开 API,声明在 gd5f2gq5ue.h) */ /* ======================== 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 区间即判不可达。 */ 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_WAIT_EXPIRED(-2) - 超时(可重试); * GD5F_ERROR(-1) - SPI 传输真故障(不可重试,须上报自检) * 限定条件:SPI 已初始化 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时。 * (2026-10-03,GD5F 错误通道统一):三处 HAL 调用的返回值**必须**检查 * ——原实现全部丢弃,失败时 `status` 保持初值 0 → `(status & OIP) == 0` * 立即成立 → **把"操作完成"伪造出来**,后续命令在未就绪的芯片上执行, * 最终表现为"静默报成功但数据没落盘"。现区分两类失败: * ① SPI 层失败(HAL≠OK)→ GD5F_ERROR(真故障); * ② OIP 长时间不落 → GD5F_WAIT_EXPIRED(可重试)。 * 调用点据此分级:超时重试 / 真故障上报自检并降级(见审计 §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) { /* SPI 真故障——绝不伪造成"操作完成"(详见函数说明) */ return GD5F_ERROR; } if ((status & GD5F_STATUS_OIP) == 0) { return GD5F_OK; } if ((HAL_GetTick() - tick_start) >= timeout_ms) { return GD5F_WAIT_EXPIRED; } 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-GD5F-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(); s_spi1_dma_done = 0U; if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) { GD5F_CS_HIGH(); return GD5F_ERROR; } while (s_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); return GD5F_WAIT_EXPIRED; } } } 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(); s_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 (s_spi1_dma_done == 0U) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); return GD5F_WAIT_EXPIRED; } } } 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 + 地址 + 数据,等待操作完成。 * (2026-10-03):HAL 传输与 wait_busy 的返回值必须检查并上报—— * 原实现恒返回 GD5F_OK,"配置未生效"对上层不可见(同族问题)。 */ 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 已初始化 * 函数说明:(2026-10-03):init 需**回读** B0h 校验 ECC 使能位真正生效 * (原实现只写不读,"init 返回 OK 但 ECC 未打开"→ 存储栈在无 ECC * 保护下静默降级)。与 gd5f_read_status 同构(后者固定 C0h), * 此处参数化地址;同样检查 HAL 返回值。 */ 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)。 * (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-STORAGE-03 等运行时 * 标记的坏块。调用后建议再执行 nand_ftl_format() 清空 dhara map。 */ int32_t gd5f2gq5ue_bbt_rebuild(void) { int ret; int restore_ret; uint8_t feat = 0; DBG_INFO("Rebuilding BBT from factory scan..."); /* 1. 关闭 ECC 以读取原始出厂坏块标记(与 gd5f2gq5ue_init 的扫描前提一致)。 * 2026-10-23(bsp-err 的 consume-result 检查上线当天照出来的一处真窟窿):这两行原是**裸调用**——关 ECC 失败就是没关掉,扫描会在 * ECC 语义下读 spare,把厂标好块误判成坏块,"重建 BBT"反而制造坏块。 */ ret = gd5f_set_feature(0xB0, 0x00); if (ret != GD5F_OK) { DBG_ERROR("BBT rebuild: ECC off failed (%d), aborted", ret); return ret; } if (gd5f_wait_busy(100) != GD5F_OK) { /* 命令发了但没等到完成 = 状态未知;宁可什么都不做(ECC 仍在,是安全侧) */ DBG_ERROR("BBT rebuild: wait after ECC off failed, aborted"); return GD5F_ERROR; } /* 2. 重新扫描出厂坏块(覆盖 RAM BBT,剔除运行时注入的坏块) */ ret = gd5f_bbt_scan(); /* 3. 重新使能 ECC:无论扫描成败都必须恢复,而且要**验证真的生效** * (2026-10-23,由 bsp-err 的 consume-result 检查照出)。原先这两行返回值全丢:SPI 一抖 ECC 就保持关闭,而 * gd5f_check_ecc() 在 ECC 关闭时恒读"无 ECC 错"——整条链再没有任何信号,设备 * 从此在无位翻转保护下读写却"一切正常"。init 路径有"写完回读 B0h 校验 * ECC_EN 位",这里补同一套;ECC 没恢复比扫描失败更严重,所以优先返回它。 */ restore_ret = gd5f_set_feature(0xB0, 0x10); if ((gd5f_wait_busy(100) != GD5F_OK) && (restore_ret == GD5F_OK)) { restore_ret = GD5F_WAIT_EXPIRED; } if (restore_ret == GD5F_OK) { restore_ret = gd5f_private_get_feature(GD5F_REG_FEATURE, &feat); } if ((restore_ret == GD5F_OK) && ((feat & GD5F_FEATURE_ECC_EN) == 0u)) { restore_ret = GD5F_ERROR; /* 写了但没生效,与写失败同罪 */ } if (restore_ret != GD5F_OK) { DBG_ERROR("BBT rebuild: ECC restore FAILED (ret=%d B0h=0x%02X), NAND UNPROTECTED", restore_ret, feat); /* 没持久化成功就别让 RAM 留着"比盘上更新"的表:回读盘上副本,使 RAM 与 * 下次上电必然看到的状态一致(否则本次运行按新表跑、重启又退回旧表)。 */ (void)gd5f_bbt_load(); return restore_ret; } /* (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) { /* 扫坏块失败 → 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; } /* 回读 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; } /* 回读 A0h 校验保护位**真正清零**(2026-11-04,问题 #79 取证发现)。 * * 为什么必须校验:本项与 2026-10-23 修过的 ECC(B0h) 是同一条缺陷的另一半—— * `gd5f_set_feature()` 只写不回读,"写了但没生效"对上层完全不可见。 * A0h 没清干净的后果比 ECC 没开更直接:**整片只读**。 * * 台架现场(11-04 17:40 本次启动,`repair-nand-volume` 取证): * BBT scan: 0 bad blocks found ← 介质出厂坏块为 0 * 读操作全部正常(Map resumed / FAT 探针能读到内容) * 写操作**全部**失败:LPN=211(块 3)与 LPN=15(块 0)两个不同块 * 连 nand_ftl_format() 后的 map sync 都失败 → storage disabled * "读正常 + 写全废 + 跨多个无关块 + 出厂坏块为 0" 这组特征排除了 * "某一页/某一块真坏"(那不会跨块、也不会让全新 map sync 失败), * 指向芯片处于**写保护态**。 * * 判据:A0h bit0-3 (BP0..BP3) 与非易失的 BP 位组合决定保护范围; * 读到非 0 即保护未解除,必须**明确失败**而不是继续跑(继续跑会让 * 上层把"写保护"误判成"介质损坏",进而反复格式化——正是本单现场)。 */ { uint8_t prot = 0; ret = gd5f_private_get_feature(GD5F_REG_PROTECT, &prot); if (ret != GD5F_OK) { DBG_ERROR("Read A0h failed: %d", ret); return ret; } if (prot != 0u) { DBG_ERROR("Block protection NOT cleared (A0h=0x%02X), NAND is read-only", prot); return GD5F_ERROR; } } 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); } /* 【#79】持久化 BBT 若把连续段判坏(出厂扫描 0 坏块、底层写全部成功), * 那是运行期误标被固化后的结果,不是介质坏。此处只打一条汇总, * 让下次现场一眼能区分"真坏"与"误标"。 */ { uint32_t bad = 0; for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) { if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad++; } if (bad > 0) { DBG_ERROR("BBT after merge: %lu bad blocks (factory scan was 0)", (unsigned long)bad); } } 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(); }