存储测试通过

This commit is contained in:
2026-08-27 18:22:44 +08:00
parent 88160a5134
commit cf33171c8f
19 changed files with 2952 additions and 336 deletions

View File

@@ -1,9 +1,13 @@
/*
* 模块名称GD5F2GQ5UE SPI NAND Flash 驱动
* 模块功能:提<EFBFBD>?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接<EFBFBD>? * 适用平台STM32F407ZGT6 + SPI1 硬件 SPI
* 作者王建<EFBFBD>? * 创建日期<E697A5>?026-07-16
* 修改记录<EFBFBD>? * 2026-07-16 王建<E78E8B>? 创建初始版本
* 2026-07-17 王建<E78E8B>? 切换硬件 SPI修正擦除地址<EFBC8C>?NuttX 驱动
* 模块功能:提GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接
* 适用平台STM32F407ZGT6 + SPI1 硬件 SPI
* 作者:王建锋
* 创建日期:2026-07-16
* 修改记录:
* 2026-07-16 王建锋 创建初始版本
* 2026-07-17 王建锋 切换硬件 SPI修正擦除地址参考 NuttX 驱动
* 2026-08-27 补充函数注释(代码规范 V1.0);修正工厂坏块扫描为首页;增加 BBT 持久化
*/
/* 头文件包含区 */
@@ -23,6 +27,18 @@
/* 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;
@@ -34,6 +50,10 @@ volatile uint8_t g_spi1_dma_done;
/*
* 函数功能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) {
@@ -59,6 +79,13 @@ 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 原语定义 ======================== */
/*
@@ -95,7 +122,7 @@ int gd5f_wait_busy(uint32_t timeout_ms) {
* 入口参数:无
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明CS# 拉低后发<EFBFBD>?06h 命令再拉<EFBFBD>? */
* 函数说明CS# 拉低后发 06h 命令再拉 */
int gd5f_write_enable(void) {
uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
@@ -108,10 +135,10 @@ int gd5f_write_enable(void) {
/*
* 函数功能:读取状态寄存器
* 口参数p_status - 状态值输出指<EFBFBD>? uint8_t* 不为 NULL
* 口参数p_status - 状态值输出指 uint8_t* 不为 NULL
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:发<EFBFBD>?0Fh + C0h 地址后读<EFBFBD>?字节状态<EFBFBD>? */
* 函数说明:发 0Fh + C0h 地址后读 1 字节状态 */
int gd5f_read_status(uint8_t *p_status) {
uint8_t cmd = GD5F_CMD_GET_FEATURE;
uint8_t addr = GD5F_REG_STATUS;
@@ -126,9 +153,11 @@ int gd5f_read_status(uint8_t *p_status) {
}
/*
* 函数功能:页读取(将数据从存储阵列加载到内部缓存<EFBFBD>? * 入口参数page_addr - 页地址 uint32_t
* 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件SPI 已初始化
* 函数说明<EFBFBD>?13h + 3字节行地址<EFBC8C>?OIP 清零
* 函数功能:页读取(将数据从存储阵列加载到内部缓存
* 入口参数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};
@@ -148,11 +177,11 @@ int gd5f_page_read(uint32_t page_addr) {
/*
* 函数功能:从内部缓存读取数据
* 入口参数column - 列地址(页内偏移) uint16_t 0 - 2047
* p_buf - 数据输出缓冲<E7BC93>? uint8_t* 不为 NULL
* size - 读取字节<E5AD97>? size_t > 0
* size - 读取字节数 size_t > 0
* 出口参数p_buf - 数据输出缓冲区 uint8_t* 不为 NULL
* 返回值0 - 成功
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
* 函数说明:发<EFBFBD>?0Bh + 2字节列地址 +1字节 dummy 后读取数<EFBFBD>? */
* 函数说明:发 0Bh + 2字节列地址 +1字节 dummy 后读取数 */
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
size_t size) {
uint8_t cmd[4] = {0};
@@ -191,11 +220,11 @@ int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
* 函数功能:页编程(将数据写入指定页)
* 入口参数page_addr - 页地址 uint32_t
* column - 列地址 uint16_t 0 - 2047
* p_buf - 数据缓冲<EFBFBD>? const uint8_t*
* size - 写入字节<EFBFBD>? size_t > 0
* 返回值0 - 成功,其<EFBFBD>?- 错误<EFBFBD>? * 限定条件:目标区域已擦除
* 函数说明<EFBFBD>?. 写使<EFBFBD>?- 02h 加载数据 - 10h 执行编程 - 等待完成
* 2. 编程完成后检<EFBFBD>?P_FAIL <EFBFBD>? */
* 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;
@@ -205,6 +234,15 @@ static int gd5f_page_program(uint32_t page_addr, uint16_t column,
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;
@@ -237,6 +275,13 @@ int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) {
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];
@@ -260,6 +305,13 @@ int gd5f_program_exec(uint32_t page_addr) {
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);
@@ -269,11 +321,12 @@ int gd5f_check_ecc(void) {
}
/*
* 函数功能:设<EFBFBD>?Feature 寄存<EFBFBD>? * 入口参数addr - 寄存器地址 uint8_t
* 函数功能:设Feature 寄存
* 入口参数addr - 寄存器地址 uint8_t
* data - 写入数据 uint8_t
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:先写使能,发<EFBFBD>?1Fh + 地址 + 数据,等待操作完<EFBFBD>? */
* 函数说明:先写使能,发 1Fh + 地址 + 数据,等待操作完 */
static int gd5f_set_feature(uint8_t addr, uint8_t data) {
uint8_t cmd[3];
@@ -334,8 +387,8 @@ int gd5f_block_erase(uint32_t block_addr) {
* 入口参数:无
* 返回值0 - 成功
* 限定条件SPI 已初始化ECC 尚未使能
* 函数说明:读取每个块最后一page 63)的 spare byte 0
* 若不为 0xFF 则为出厂坏块
* 函数说明:读取每个块page 0)的 spare byte 0
* 若不为 0xFF 则为出厂坏块(符合 datasheet §12.4
*/
static int gd5f_bbt_scan(void) {
uint32_t block;
@@ -345,7 +398,7 @@ static int gd5f_bbt_scan(void) {
memset(s_bbt, 0, GD5F_BBT_SIZE);
for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) {
uint32_t page = (block << 6) | 63;
uint32_t page = block << 6;
gd5f_page_read(page);
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1);
@@ -360,24 +413,57 @@ static int gd5f_bbt_scan(void) {
return GD5F_OK;
}
void gd5f2gq5ue_bbt_rescan(void) {
uint32_t blk;
uint8_t spare[64];
memset(s_bbt, 0, GD5F_BBT_SIZE);
for (blk = 0; blk < GD5F_TOTAL_BLOCKS; blk++) {
uint32_t page = (blk << 6) | 63;
gd5f_page_read(page);
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, sizeof(spare));
if (spare[0] != 0xFF) {
s_bbt[blk >> 3] |= (1 << (blk & 7));
}
}
}
/*
* 函数功能:清空 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++) {
@@ -405,9 +491,10 @@ void gd5f2gq5ue_print_bbt(void) {
* 限定条件SPI1 和相关 GPIO 已由 CubeMX 初始化完成
* 函数说明1. 发送复位命令并等待完成
* 2. 读取芯片 ID 并校验
* 3. 扫描出厂坏块构建 BBTECC 使能前)
* 3. 扫描出厂坏块构建 BBTECC 使能前,读每块首页 spare[0]
* 4. 使能内部 ECC (B0h bit4)
* 5. 解除所有块保护 (A0h = 0x00)
* 6. 定位保留块池并加载持久化 BBTECC 使能后)
*/
int gd5f2gq5ue_init(void) {
int ret = GD5F_OK;
@@ -455,16 +542,23 @@ int gd5f2gq5ue_init(void) {
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;
}
/*
* 函数功能:读取芯<EFBFBD>?IDMID + DID<EFBFBD>? * 入口参数mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* did - 设备 ID 输出指针 uint8_t* 不为 NULL
* 函数功能:读取芯IDMID + DID
* 出口参数p_mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* p_did - 设备 ID 输出指针 uint8_t* 不为 NULL
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:发<EFBFBD>?9Fh 命令后接<EFBFBD>?<3F>?dummy + MID + DID
* 函数说明:发 9Fh 命令后接 1 字节 dummy + MID + DID
*/
int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
uint8_t cmd = GD5F_CMD_READ_ID;
@@ -483,13 +577,20 @@ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
/*
* 函数功能:从 NAND 读取数据(支持跨页)
* 入口参数offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
* p_buf - 数据缓冲<E7BC93>? uint8_t* 不为 NULL
* size - 读取字节<E5AD97>? size_t > 0
* 返回值0 - 成功,其<EFBFBD>?- 错误<EFBFBD>? * 限定条件gd5f2gq5ue_init() 已成功调<E58A9F>? * 函数说明自动处理跨页读<E9A1B5>? */
* 入口参数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;
@@ -519,13 +620,20 @@ int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
/*
* 函数功能:向 NAND 写入数据(支持跨页)
* 入口参数offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
* p_buf - 数据缓冲<EFBFBD>? const uint8_t* 不为 NULL
* size - 写入字节<EFBFBD>? size_t > 0
* 返回值0 - 成功,其<EFBFBD>?- 错误<EFBFBD>? * 限定条件gd5f2gq5ue_init() 已成功调用目标区域已擦<E5B7B2>? * 函数说明自动处理跨页写<E9A1B5>? */
* 入口参数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;
@@ -549,7 +657,12 @@ int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
}
/*
* 函数功能:擦除块(按块擦除,最小单<EFBFBD>?128KB<EFBFBD>? * 入口参数offset - 起始字节偏移 long 必须块对<E59D97>? * size - 擦除字节<E5AD97>? size_t 必须块大小整数<E695B4>? * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件gd5f2gq5ue_init() 已成功调<E58A9F>? * 函数说明:擦除操作以块为单位
* 函数功能:擦除块(按块擦除,最小单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;
@@ -577,10 +690,10 @@ int gd5f2gq5ue_erase(long offset, size_t size) {
}
/*
* 函数功能:复位芯<EFBFBD>? * 入口参数:无
* 函数功能:复位芯
* 返回值0 - 成功
* 限定条件SPI 已初始化
* 函数说明:发<EFBFBD>?FFh 复位命令后等<EFBFBD>?5ms
* 函数说明:发 FFh 复位命令后等5ms
*/
int gd5f2gq5ue_reset(void) {
uint8_t cmd = GD5F_CMD_RESET;
@@ -619,6 +732,231 @@ void gd5f2gq5ue_mark_block_bad(uint32_t block) {
if (block >= GD5F_TOTAL_BLOCKS) {
return;
}
s_bbt[block >> 3] |= (1 << (block & 7));
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();
}

View File

@@ -17,6 +17,7 @@ extern "C" {
#endif
#include "main.h"
#include <stdint.h>
/* ======================== 宏定义 ======================== */
@@ -60,6 +61,9 @@ extern "C" {
#define GD5F_TOTAL_BLOCKS 2048
#define GD5F_TOTAL_SIZE (GD5F_TOTAL_BLOCKS * GD5F_BLOCK_SIZE)
/* 坏块表持久化:保留块池数量(从 NAND 顶部向下挑选出厂好块存储 BBT */
#define GD5F_BBT_POOL_COUNT 4
/* 制造商 ID 和设备 ID */
#define GD5F_MANUFACTURER_ID 0xC8
#define GD5F_DEVICE_ID 0x52
@@ -91,7 +95,7 @@ extern "C" {
* 3. 使能内部 ECC (B0h bit4)
* 4. 解除所有块保护 (A0h = 0x00)
*/
int gd5f2gq5ue_init(void);
int32_t gd5f2gq5ue_init(void);
/*
* 函数功能:读取芯片 IDMID + DID
@@ -101,13 +105,13 @@ int gd5f2gq5ue_init(void);
* 限定条件SPI 已初始化
* 函数说明:发送 9Fh 命令后接收1个 dummy + MID + DID
*/
int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did);
int32_t gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did);
/*
* 函数功能:从 NAND 读取数据(支持跨页)
* 入口参数offset - 起始字节偏移 long 0 - GD5F_TOTAL_SIZE-1
* 入口参数offset - 起始字节偏移 int32_t 0 - GD5F_TOTAL_SIZE-1
* buf - 数据缓冲区 uint8_t* 不为 NULL
* size - 读取字节数 size_t > 0
* size - 读取字节数 uint32_t > 0
* 返回值0 - 成功,其他 - 错误码
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:自动处理跨页读取,每次读取不超过当前页剩余空间
@@ -116,9 +120,9 @@ int gd5f2gq5ue_read(long offset, uint8_t *buf, size_t size);
/*
* 函数功能:向 NAND 写入数据(支持跨页)
* 入口参数offset - 起始字节偏移 long 0 - GD5F_TOTAL_SIZE-1
* 入口参数offset - 起始字节偏移 int32_t 0 - GD5F_TOTAL_SIZE-1
* buf - 数据缓冲区 uint8_t* 不为 NULL
* size - 写入字节数 size_t > 0
* size - 写入字节数 uint32_t > 0
* 返回值0 - 成功,其他 - 错误码
* 限定条件gd5f2gq5ue_init() 已成功调用,目标区域已擦除
* 函数说明:自动处理跨页写入,每次写入不超过当前页剩余空间
@@ -127,8 +131,8 @@ int gd5f2gq5ue_write(long offset, const uint8_t *buf, size_t size);
/*
* 函数功能:擦除块(按块擦除,最小单位 128KB
* 入口参数offset - 起始字节偏移 long 必须 GD5F_BLOCK_SIZE 对齐
* size - 擦除字节数 size_t 必须 GD5F_BLOCK_SIZE 整数倍
* 入口参数offset - 起始字节偏移 int32_t 必须 GD5F_BLOCK_SIZE 对齐
* size - 擦除字节数 uint32_t 必须 GD5F_BLOCK_SIZE 整数倍
* 返回值0 - 成功,其他 - 错误码
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明擦除操作以块为单位offset 和 size 必须块对齐
@@ -142,7 +146,7 @@ int gd5f2gq5ue_erase(long offset, size_t size);
* 限定条件SPI 已初始化
* 函数说明:发送 FFh 复位命令后等待 5ms
*/
int gd5f2gq5ue_reset(void);
int32_t gd5f2gq5ue_reset(void);
/*
* 函数功能:查询块是否坏块
@@ -151,31 +155,81 @@ int gd5f2gq5ue_reset(void);
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:通过 BBTBad Block Table查询BBT 在 init 阶段 ECC 使能前扫描构建
*/
int gd5f2gq5ue_is_block_bad(uint32_t block);
int32_t gd5f2gq5ue_is_block_bad(uint32_t block);
/*
* 函数功能:标记块为坏块
* 入口参数block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:无
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:更新 BBT并尝试物理标记该块最后一页 spare byte 0 为 0x00
* 函数说明:更新 RAM 中的 BBT并持久化到保留块池带版本号 + CRC
* 轮转写入,掉电安全);仅在管理区内的块才纳入持久化 BBT
*/
void gd5f2gq5ue_mark_block_bad(uint32_t block);
void gd5f2gq5ue_bbt_clear(void);
void gd5f2gq5ue_bbt_rescan(void);
void gd5f2gq5ue_print_bbt(void);
/*
* 函数功能:重建 BBT重新扫描出厂坏块并持久化覆盖运行时注入的坏块
* 入口参数:无
* 返回值GD5F_OK - 成功,其他 - 错误码
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:用于测试(如 TC-STO-03 注入坏块后)或出厂重置后恢复。
* 临时关闭 ECC 重新扫描每块 page0 spare[0] 的出厂坏块标记,覆盖 RAM BBT
* 再写回持久化 BBT 池(版本 +1掉电安全从而清除运行时 mark 的坏块。
* 调用后建议再执行 nand_ftl_format() 清空 dhara map。
*/
int 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 共享) ==================== */
int gd5f_wait_busy(uint32_t timeout_ms);
int gd5f_write_enable(void);
int gd5f_read_status(uint8_t *p_status);
int gd5f_page_read(uint32_t page_addr);
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, size_t size);
int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size);
int gd5f_program_exec(uint32_t page_addr);
int gd5f_block_erase(uint32_t block_addr);
int gd5f_check_ecc(void);
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);
#ifdef __cplusplus
}

View File

@@ -200,12 +200,13 @@ DSTATUS disk_initialize(BYTE pdrv) {
s_nand.log2_page_size = 11;
s_nand.log2_ppb = 6;
s_nand.num_blocks = FTL_NUM_BLOCKS;
/* dhara 可见块数 = 总块数 - 保留块池BBT 存储区不交给 dhara避免被擦写 */
s_nand.num_blocks = gd5f_get_usable_blocks();
dhara_map_init(&s_map, &s_nand, s_page_buf, 4);
memset(s_cache_buf, 0, FTL_PAGE_SIZE);
s_cached_lpn = 0;
s_cached_lpn = (dhara_sector_t)-1; /* -1 表示缓存未加载LPN 0 合法,不能用 0 作哨兵 */
s_cache_dirty = 0;
{
@@ -227,6 +228,21 @@ DSTATUS disk_initialize(BYTE pdrv) {
return 0;
}
int nand_ftl_init(void)
{
DSTATUS sta = disk_initialize(0);
return (sta == 0) ? 0 : -1;
}
void nand_ftl_deinit(void)
{
/* 复位 FTL 状态,使下次 nand_ftl_init() 真正重新走 dhara_map_resume
用于测试"掉电恢复"与"坏块注入后重建"场景(模拟设备重启) */
s_initialized = 0;
s_cached_lpn = (dhara_sector_t)-1;
s_cache_dirty = 0;
}
DSTATUS disk_status(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
@@ -380,6 +396,7 @@ int nand_ftl_format(void) {
}
s_cache_dirty = 0;
s_cached_lpn = (dhara_sector_t)-1;
DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map));
return GD5F_OK;

View File

@@ -9,6 +9,7 @@ extern "C" {
int nand_ftl_init(void);
int nand_ftl_format(void);
void nand_ftl_deinit(void);
#ifdef __cplusplus
}