Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c
2026-08-27 18:22:44 +08:00

963 lines
30 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_PAGE_SIZE]; /* BBT 读写页缓冲(静态,避免占栈) */
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 传输) */
#define GD5F_DMA_THRESHOLD 32
/*
* 函数功能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);
/* 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);
/* ======================== SPI 原语定义 ======================== */
/*
* 函数功能:等待芯片操作完成(轮询 OIP 位)
* 入口参数timeout_ms - 超时时间 uint32_t > 0
* 返回值0 - 操作完成,-2 - 超时
* 限定条件SPI 已初始化
* 函数说明:循环读取状态寄存器直到 OIP 位清零或超时
*/
int 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();
while (1) {
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, &status, 1, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
if ((status & GD5F_STATUS_OIP) == 0) {
return GD5F_OK;
}
if ((HAL_GetTick() - tick_start) >= timeout_ms) {
return GD5F_BUSY_TIMEOUT;
}
}
}
/*
* 函数功能:发送写使能命令
* 入口参数:无
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明CS# 拉低后发 06h 命令再拉高 */
int 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 字节状态 */
int 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;
}
/*
* 函数功能:页读取(将数据从存储阵列加载到内部缓存)
* 入口参数page_addr - 页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
* 返回值0 - 成功,其它 - 错误
* 限定条件SPI 已初始化
* 函数说明:发 13h + 3字节行地址等待 OIP 清零
*/
int gd5f_page_read(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();
return gd5f_wait_busy(100);
}
/*
* 函数功能:从内部缓存读取数据
* 入口参数column - 列地址(页内偏移) uint16_t 0 - 2047
* size - 读取字节数 size_t > 0
* 出口参数p_buf - 数据输出缓冲区 uint8_t* 不为 NULL
* 返回值0 - 成功
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
* 函数说明:发 0Bh + 2字节列地址 +1字节 dummy 后读取数据 */
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
size_t size) {
uint8_t cmd[4] = {0};
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 (size > GD5F_DMA_THRESHOLD) {
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
*/
int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) {
uint8_t cmd[3];
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 (size > GD5F_DMA_THRESHOLD) {
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 位
*/
int 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 表示不可纠正错误
*/
int 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
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:先写使能,发 1Fh + 地址 + 数据,等待操作完成 */
static int gd5f_set_feature(uint8_t addr, uint8_t data) {
uint8_t cmd[3];
gd5f_write_enable();
cmd[0] = GD5F_CMD_SET_FEATURE;
cmd[1] = addr;
cmd[2] = data;
GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
GD5F_CS_HIGH();
gd5f_wait_busy(100);
return GD5F_OK;
}
/*
* 函数功能块擦除擦除128KB块
* 入口参数block_addr - 块编号 uint32_t 0 - 2047
* 返回值0 - 成功,其他 - 错误码
* 限定条件gd5f2gq5ue_init() 已调用
* 函数说明1. 写使能 - D8h + 3字节页地址 - 等待完成
* 2. 擦除完成后检查 E_FAIL */
int 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 使能前调用)
* 入口参数:无
* 返回值0 - 成功
* 限定条件SPI 已初始化ECC 尚未使能
* 函数说明读取每个块首页page 0的 spare byte 0
* 若不为 0xFF 则为出厂坏块(符合 datasheet §12.4
*/
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;
gd5f_page_read(page);
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1);
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。
*/
int gd5f2gq5ue_bbt_rebuild(void) {
DBG_INFO("Rebuilding BBT from factory scan...");
/* 1. 关闭 ECC 以读取原始出厂坏块标记(与 gd5f2gq5ue_init 的扫描前提一致) */
gd5f_set_feature(0xB0, 0x00);
gd5f_wait_busy(100);
/* 2. 重新扫描出厂坏块(覆盖 RAM BBT剔除运行时注入的坏块 */
gd5f_bbt_scan();
/* 3. 重新使能 ECC */
gd5f_set_feature(0xB0, 0x10);
gd5f_wait_busy(100);
/* 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. 扫描出厂坏块构建 BBTECC 使能前,读每块首页 spare[0]
* 4. 使能内部 ECC (B0h bit4)
* 5. 解除所有块保护 (A0h = 0x00)
* 6. 定位保留块池并加载持久化 BBTECC 使能后)
*/
int 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...");
gd5f_bbt_scan();
DBG_INFO("Enabling ECC...");
gd5f_set_feature(0xB0, 0x10);
gd5f_wait_busy(100);
DBG_INFO("Unlocking block protection...");
gd5f_set_feature(GD5F_REG_PROTECT, 0x00);
gd5f_wait_busy(100);
DBG_INFO("Locating BBT pool...");
gd5f_private_bbt_locate_pool();
DBG_INFO("Loading persisted BBT...");
gd5f_bbt_load();
DBG_INFO("NAND init OK");
return GD5F_OK;
}
/*
* 函数功能:读取芯片 IDMID + DID
* 出口参数p_mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* p_did - 设备 ID 输出指针 uint8_t* 不为 NULL
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:发 9Fh 命令后接 1 字节 dummy + MID + DID
*/
int 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 越界返回错误
*/
int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
int ret = GD5F_OK;
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
return GD5F_ERROR;
}
while (size > 0) {
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 越界返回错误
*/
int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
int ret = GD5F_OK;
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
return GD5F_ERROR;
}
while (size > 0) {
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) 覆盖的每个块,偏移/长度不对齐返回错误
*/
int gd5f2gq5ue_erase(long offset, size_t size) {
int ret = GD5F_OK;
if (offset % GD5F_BLOCK_SIZE != 0) {
return GD5F_ERROR;
}
if (size % GD5F_BLOCK_SIZE != 0) {
return GD5F_ERROR;
}
while (size > 0) {
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
*/
int 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 查询
*/
int 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;
}
/*
* 函数功能:加载持久化 BBTOR 进 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();
}