Files
STM32F4-Base/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c
T

628 lines
19 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.
/*
* 模块名称:NAND FTL 适配层(dhara + FatFS diskio 胶水)
* 模块功能:实现 dhara NAND HAL 和 FatFS 磁盘 I/O,连接 GD5F2GQ5UE 底层驱动
* 适用平台:STM32F407ZGT6
* 作者:王建锋
* 创建日期:2026-07-20
* 修改记录:
* 2026-07-20 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <string.h>
#include "gd5f2gq5ue.h"
#include "ff.h"
#include "diskio.h"
#include "nand.h"
#include "map.h"
/* FTL 分区起始地址(块 0,偏移 0,占用全部 256MB) */
#define FTL_FATFS_OFFSET 0U
/* 调试输出配置 */
#define DBG_TAG "[NAND_FTL]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define FTL_PAGE_SIZE GD5F_PAGE_SIZE
#define FTL_SECTORS_PER_PAGE (FTL_PAGE_SIZE / 512)
#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE)
#define FTL_NUM_BLOCKS (GD5F_TOTAL_BLOCKS - FTL_START_BLOCK)
#define FTL_START_PAGE (FTL_START_BLOCK * GD5F_PAGES_PER_BLOCK)
/* dhara 逻辑地址到物理地址转换 */
static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLOCK; }
static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; }
/* ======================== 私有变量 ======================== */
/* dhara 核心数据结构 */
static struct dhara_nand s_nand;
static struct dhara_map s_map;
static uint8_t s_page_buf[FTL_PAGE_SIZE]; /* dhara 内部页缓冲区 */
/* 页面缓存(FatFS 读写缓存) */
static uint8_t s_cache_buf[FTL_PAGE_SIZE];
static dhara_sector_t s_cached_lpn;
static uint8_t s_cache_dirty;
/* 拷贝临时缓冲区 */
static uint8_t s_copy_buf[FTL_PAGE_SIZE];
/* 初始化状态 */
static uint8_t s_initialized;
/* resume 搜索探针日志窗口(诊断:逐探测记录判定输入) */
static uint8_t s_probe_log;
/* ======================== 私有函数声明 ======================== */
static int ftl_flush_cache(void);
static int ftl_read_page(dhara_sector_t lpn);
static void ftl_scan_checks_raw(void);
/* SPI 原语从 gd5f2gq5ue.h 获取 */
/* ======================== 页面缓存管理 ======================== */
static int ftl_flush_cache(void) {
if (!s_cache_dirty) {
return GD5F_OK;
}
dhara_error_t err;
if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err);
return GD5F_ERROR;
}
s_cache_dirty = 0;
return GD5F_OK;
}
static int ftl_read_page(dhara_sector_t lpn) {
dhara_error_t err;
if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err);
return GD5F_ERROR;
}
s_cached_lpn = lpn;
s_cache_dirty = 0;
return GD5F_OK;
}
/* ======================== dhara NAND HAL ======================== */
int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
const int bad = gd5f2gq5ue_is_block_bad(ftl_phy_block(b));
if (s_probe_log) {
DBG_INFO("probe blk %lu: bbt_bad=%u", (unsigned long)b,
(unsigned int)bad);
}
return bad;
}
void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
gd5f2gq5ue_mark_block_bad(ftl_phy_block(b));
}
int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) {
(void)n;
int ret = gd5f_block_erase(ftl_phy_block(b));
if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, b);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
return 0;
}
int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) {
int ret;
uint32_t phy_pg = ftl_phy_page(p);
gd5f_write_enable();
ret = gd5f_program_load(0, data, FTL_PAGE_SIZE);
if (ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
ret = gd5f_program_exec(phy_pg);
if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
/* 编程回读校验(2026-09-20 台架实证):供电低于标称(3.1V)时
* program 可"状态成功"但 cell 未可靠 latch(ECC 不可纠或数据错位)——
* 状态寄存器发现不了,下次复位 journal resume 因 ECC 门拒绝这些
* checkpoint 页 → 新写入整体静默丢失(重格式化循环的真根因)。
* 回读比对+ECC 检查,失败则标记坏块由 dhara 迁移,宁可丢块不丢卷。 */
if (gd5f_page_read(phy_pg) != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_ECC);
DBG_ERROR("program verify: readback fail, blk %lu",
(unsigned long)(p >> n->log2_ppb));
return -1;
}
if (gd5f_check_ecc() != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_ECC);
DBG_ERROR("program verify: ecc uncorrectable, blk %lu",
(unsigned long)(p >> n->log2_ppb));
return -1;
}
gd5f_read_from_cache(0, s_copy_buf, FTL_PAGE_SIZE);
if (memcmp(s_copy_buf, data, FTL_PAGE_SIZE) != 0) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
DBG_ERROR("program verify: data mismatch, blk %lu",
(unsigned long)(p >> n->log2_ppb));
return -1;
}
return 0;
}
int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) {
(void)n;
uint32_t i;
uint8_t buf[64];
uint32_t phy_pg = ftl_phy_page(p);
if (gd5f_page_read(phy_pg) != GD5F_OK) {
return 0;
}
gd5f_read_from_cache(0, buf, sizeof(buf));
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != 0xFF) {
return 0;
}
}
return 1;
}
int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) {
(void)n;
uint32_t phy_pg = ftl_phy_page(p);
int rret;
int eret;
if ((rret = gd5f_page_read(phy_pg)) != GD5F_OK) {
if (s_probe_log) {
DBG_INFO("probe pg %lu blk %lu: page_read=%d",
(unsigned long)phy_pg, (unsigned long)(p >> 6),
rret);
}
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
if ((eret = gd5f_check_ecc()) != GD5F_OK) {
if (s_probe_log) {
DBG_INFO("probe pg %lu blk %lu: ecc=%d",
(unsigned long)phy_pg, (unsigned long)(p >> 6),
eret);
}
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
gd5f_read_from_cache((uint16_t)offset, data, length);
if (s_probe_log && offset == 0 && length == FTL_PAGE_SIZE) {
/* 组末页(组内偏移 15)= checkpoint 页:记录 magic/epoch 判定 */
if ((p & 63u) == 15u) {
const uint8_t *m = data;
DBG_INFO("probe pg %lu blk %lu chk: magic=%u epoch=%u",
(unsigned long)phy_pg, (unsigned long)(p >> 6),
(unsigned int)((m[0] == 'D' && m[1] == 'h' &&
m[2] == 'a') ? 1 : 0),
(unsigned int)m[3]);
}
}
return 0;
}
int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) {
(void)n;
if (dhara_nand_read(n, src, 0, FTL_PAGE_SIZE, s_copy_buf, err) < 0) {
return -1;
}
if (dhara_nand_prog(n, dst, s_copy_buf, err) < 0) {
return -1;
}
return 0;
}
/* ======================== FatFS 磁盘 I/O ======================== */
DSTATUS disk_initialize(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
}
if (s_initialized) {
return 0;
}
DBG_INFO("Initializing NAND FTL...");
s_nand.log2_page_size = 11;
s_nand.log2_ppb = 6;
/* dhara 可见块数 = 总块数 - 保留块池(BBT 存储区不交给 dhara,避免被擦写) */
s_nand.num_blocks = gd5f_get_usable_blocks();
dhara_map_init(&s_map, &s_nand, s_page_buf, 4);
/* 诊断:resume 前的裸 NAND checkpoint 扫描(复位后地面真值,
* 不经 FTL 缓存)——判定上一开机的 checkpoint 页是否真正写入芯片 */
DBG_INFO("Pre-resume raw check scan start: blocks=%lu",
(unsigned long)s_nand.num_blocks);
ftl_scan_checks_raw();
memset(s_cache_buf, 0, FTL_PAGE_SIZE);
s_cached_lpn = (dhara_sector_t)-1; /* -1 表示缓存未加载;LPN 0 合法,不能用 0 作哨兵 */
s_cache_dirty = 0;
{
dhara_error_t err;
s_probe_log = 1;
if (dhara_map_resume(&s_map, &err) < 0) {
DBG_INFO("No valid map found, creating fresh (err=%d)", err);
dhara_map_clear(&s_map);
} else {
DBG_INFO("Map resumed: %lu/%lu sectors used",
dhara_map_size(&s_map), dhara_map_capacity(&s_map));
}
s_probe_log = 0;
}
s_initialized = 1;
DBG_INFO("NAND FTL ready (capacity: %lu pages = %lu MB)",
dhara_map_capacity(&s_map),
(dhara_map_capacity(&s_map) * FTL_PAGE_SIZE) / (1024 * 1024));
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;
}
if (!s_initialized) {
return STA_NOINIT;
}
return 0;
}
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(buff, s_cache_buf + offset, batch * 512);
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(s_cache_buf + offset, buff, batch * 512);
s_cache_dirty = 1;
if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
}
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
if (pdrv != 0) {
return RES_PARERR;
}
if (!s_initialized) {
return RES_NOTRDY;
}
switch (cmd) {
case CTRL_SYNC:
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
{
dhara_error_t err;
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync failed: %d", err);
return RES_ERROR;
}
}
return RES_OK;
case GET_SECTOR_COUNT: {
LBA_t *p_sectors = (LBA_t *)buff;
dhara_sector_t pages = dhara_map_capacity(&s_map);
*p_sectors = pages * FTL_SECTORS_PER_PAGE;
return RES_OK;
}
case GET_SECTOR_SIZE:
*(WORD *)buff = 512;
return RES_OK;
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
return RES_OK;
default:
return RES_PARERR;
}
}
/* ======================== 公共函数定义 ======================== */
int nand_ftl_format(void) {
dhara_error_t err;
if (!s_initialized) {
if (disk_initialize(0) != 0) {
return GD5F_ERROR;
}
}
if (ftl_flush_cache() != GD5F_OK) {
return GD5F_ERROR;
}
dhara_map_clear(&s_map);
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync after clear failed: %d", err);
return GD5F_ERROR;
}
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;
}
/* ======================== 诊断(跨复位日志持久性分析) ======================== */
/* 打印当前 journal/map 状态(epoch/head/tail/root/容量) */
void nand_ftl_log_state(const char *tag)
{
const struct dhara_journal *j = &s_map.journal;
if (!s_initialized) {
return;
}
DBG_INFO("FTL state %s: epoch=%u head=%lu tail=%lu root=%lu jsize=%lu map=%lu",
tag, (unsigned int)j->epoch,
(unsigned long)j->head, (unsigned long)j->tail,
(unsigned long)j->root,
(unsigned long)dhara_journal_size(j),
(unsigned long)dhara_map_size(&s_map));
}
/* 打印某块 checkpoint 页的 16 字节 header(magic/epoch/tail/bb),
* 用于区分"未落盘(全FF)"/"编程失败(花)"/"完整" 三种状态 */
static void ftl_dump_checkheader(uint32_t blk)
{
uint8_t hdr[16];
const uint32_t pg = ftl_phy_page((blk << s_nand.log2_ppb) |
((1u << s_map.journal.log2_ppc) - 1u));
if (gd5f_page_read(pg) != GD5F_OK) {
DBG_INFO("check hdr blk %lu: read fail", (unsigned long)blk);
return;
}
gd5f_read_from_cache(0, hdr, sizeof(hdr));
DBG_INFO("check hdr blk %lu: %02X %02X %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X %02X %02X",
(unsigned long)blk,
(unsigned int)hdr[0], (unsigned int)hdr[1], (unsigned int)hdr[2],
(unsigned int)hdr[3], (unsigned int)hdr[4], (unsigned int)hdr[5],
(unsigned int)hdr[6], (unsigned int)hdr[7],
(unsigned int)hdr[8], (unsigned int)hdr[9], (unsigned int)hdr[10],
(unsigned int)hdr[11], (unsigned int)hdr[12], (unsigned int)hdr[13],
(unsigned int)hdr[14], (unsigned int)hdr[15]);
}
/* 全扫每块首组 checkpoint 页(magic+epoch),打印连续区段分布,
* 用于识别 journal 环上的 epoch 分界与空洞。
* 裸读(gd5f 页读),不受 FTL 缓存影响 = NAND 真实内容。 */
static void ftl_scan_checks_raw(void)
{
uint32_t i;
uint8_t buf[8];
uint32_t total = 0;
uint32_t run_start = 0;
uint32_t run_prev = 0;
uint32_t run_len = 0;
uint32_t run_ecc_fail = 0;
uint32_t run_ecc_first = 0;
uint32_t run_ecc_last = 0;
uint32_t run_bbt_bad = 0;
uint32_t run_bbt_first = 0;
uint32_t run_bbt_last = 0;
uint8_t run_epoch = 0;
uint8_t run_open = 0;
for (i = 0; i < s_nand.num_blocks; i++) {
const uint32_t pg = ftl_phy_page((i << s_nand.log2_ppb) |
((1u << s_map.journal.log2_ppc) - 1u));
if (gd5f_page_read(pg) != GD5F_OK) {
continue;
}
/* ECC 状态在 0Bh 之前检查(与 dhara_nand_read 的读取顺序一致,
* 排除 read-from-cache 对状态的干扰) */
int ecc = gd5f_check_ecc();
gd5f_read_from_cache(0, buf, sizeof(buf));
if (buf[0] == 'D' && buf[1] == 'h' && buf[2] == 'a') {
const uint8_t ep = buf[3];
total++;
if (ecc != GD5F_OK) {
run_ecc_fail++;
if (!run_ecc_fail) {
run_ecc_first = i;
}
run_ecc_last = i;
}
if (gd5f2gq5ue_is_block_bad(ftl_phy_block(i))) {
run_bbt_bad++;
if (!run_bbt_bad) {
run_bbt_first = i;
}
run_bbt_last = i;
}
if (!run_open || (i != run_prev + 1) || (ep != run_epoch)) {
if (run_open) {
DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)",
(unsigned long)run_start, (unsigned long)run_prev,
(unsigned int)run_epoch, (unsigned long)run_len);
if (run_ecc_fail) {
DBG_INFO(" run ecc-fail: %lu blocks (blk %lu..%lu)",
(unsigned long)run_ecc_fail,
(unsigned long)run_ecc_first,
(unsigned long)run_ecc_last);
}
if (run_bbt_bad) {
DBG_INFO(" run bbt-bad: %lu blocks (blk %lu..%lu)",
(unsigned long)run_bbt_bad,
(unsigned long)run_bbt_first,
(unsigned long)run_bbt_last);
}
ftl_dump_checkheader(run_start);
}
run_start = i;
run_epoch = ep;
run_len = 0;
run_ecc_fail = 0;
run_bbt_bad = 0;
run_open = 1;
}
run_prev = i;
run_len++;
}
}
if (run_open) {
DBG_INFO("check run: blk %lu..%lu epoch=%u (%lu blocks)",
(unsigned long)run_start, (unsigned long)run_prev,
(unsigned int)run_epoch, (unsigned long)run_len);
if (run_ecc_fail) {
DBG_INFO(" run ecc-fail: %lu blocks (blk %lu..%lu)",
(unsigned long)run_ecc_fail,
(unsigned long)run_ecc_first,
(unsigned long)run_ecc_last);
}
if (run_bbt_bad) {
DBG_INFO(" run bbt-bad: %lu blocks (blk %lu..%lu)",
(unsigned long)run_bbt_bad,
(unsigned long)run_bbt_first,
(unsigned long)run_bbt_last);
}
ftl_dump_checkheader(run_start);
}
DBG_INFO("check scan done: %lu checkpoint blocks", (unsigned long)total);
}
void nand_ftl_scan_checks(void)
{
if (!s_initialized) {
return;
}
DBG_INFO("check scan start: blocks=%lu", (unsigned long)s_nand.num_blocks);
ftl_scan_checks_raw();
}