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

969 lines
32 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;
/* L0 卷健康探针窗口(诊断:记录 FAT 读的 LPN 绑定 → 物理页路径) */
static uint8_t s_tree_probe_log;
/*
* 函数功能:打开/关闭 L0 卷健康探针日志窗口
* 入口参数:on - 1 打开,0 关闭
* 返回值:无
* 限定条件:FTL 已初始化
* 函数说明:窗口内的 dhara_nand_read/is_free/is_bad 调用逐条记录
* 物理页(= journal LPN),用于判定 map 绑定指向
*/
void nand_ftl_set_tree_probe(uint8_t on) {
s_tree_probe_log = on;
}
/* ======================== 私有函数声明 ======================== */
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;
if (s_tree_probe_log) {
/* 内容指纹:前 8 字节(BPB=33 52 / FAT32=F8 FF.. / 擦除=FF FF..)
* 半新半旧混合页在此直接现形 */
DBG_INFO("ftl t %lu read LPN=%lu: %02X %02X %02X %02X %02X %02X %02X %02X",
(unsigned long)HAL_GetTick(), (unsigned long)lpn,
(unsigned int)s_cache_buf[0], (unsigned int)s_cache_buf[1],
(unsigned int)s_cache_buf[2], (unsigned int)s_cache_buf[3],
(unsigned int)s_cache_buf[4], (unsigned int)s_cache_buf[5],
(unsigned int)s_cache_buf[6], (unsigned int)s_cache_buf[7]);
}
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 || s_tree_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);
if (s_probe_log || s_tree_probe_log) {
DBG_INFO("probe t %lu prog pg %lu blk %lu g%lu",
(unsigned long)HAL_GetTick(), (unsigned long)phy_pg,
(unsigned long)(p >> 6), (unsigned long)((p >> 4) & 3u));
}
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 phy_pg = ftl_phy_page(p);
int fre;
if (gd5f_page_read(phy_pg) != GD5F_OK) {
fre = 0;
} else {
uint8_t buf[64];
uint32_t i;
gd5f_read_from_cache(0, buf, sizeof(buf));
fre = 1;
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != 0xFF) {
fre = 0;
break;
}
}
}
if (s_probe_log || s_tree_probe_log) {
DBG_INFO("probe t %lu isfree pg %lu = %u",
(unsigned long)HAL_GetTick(), (unsigned long)phy_pg,
(unsigned int)fre);
}
return fre;
}
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 || s_tree_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 || s_tree_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 || s_tree_probe_log) &&
offset == 0 && length == FTL_PAGE_SIZE) {
/* 组末页(组内偏移 15:页号低 4 位 = 15)= checkpoint 页:
* 记录 header 前 4 字节 + meta 区 4 字节(偏移 944 = 槽 7,
* 树根 meta)+ 时间戳。header 新/meta 旧 = 半新半旧混合页
* (低 VDD 就绪信号提前竞态的直接证据) */
if ((p & 15u) == 15u) {
const uint8_t *m = data;
DBG_INFO("probe t %lu pg %lu blk %lu g%lu: %02X %02X %02X %02X"
" meta %02X %02X %02X %02X",
(unsigned long)HAL_GetTick(),
(unsigned long)phy_pg, (unsigned long)(p >> 6),
(unsigned long)((p >> 4) & 3u),
(unsigned int)m[0], (unsigned int)m[1],
(unsigned int)m[2], (unsigned int)m[3],
(unsigned int)m[944], (unsigned int)m[945],
(unsigned int)m[946], (unsigned int)m[947]);
}
}
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();
}
/* ============ 裸片读写擦自检(2026-09-20 诊断:驱动层 / FTL 层 / 电源定界) ============ */
/*
* 测试块选择:2040/2041(芯片顶部区,远离 journal 环头,数千次开机内
* 不会被环覆盖;不在 BBT 保留池 2044..2047)。使用块内页 1..4,
* 避开页 0(出厂坏块标记位 page0 spare[0],防 BBT 扫描误判)。
*/
#define RAW_T_BLK_A 2040u /* 数据块:先回读上一boot图案(持久性),再擦除写新图案 */
#define RAW_T_BLK_B 2041u /* 纯擦块:每次擦除后校验全 FF */
#define RAW_T_PAGE0 1u /* 块内起始使用页(跳过页 0) */
#define RAW_T_NPG 4u /* 测试页数 */
/*
* 函数功能:计算测试页第 i 字节的期望值(seq+page 的确定性函数)
* 入口参数:seq - 启动序号 uint32_t
* page - 逻辑页号 0..3 uint32_t
* i - 页内偏移 uint32_t
* 返回值:期望字节值 uint8_t
* 限定条件:无
* 函数说明:页头 10 字节 = magic "RAW1" + seq(LE32) + page(LE16),
* 其余 2038 字节 = (seq*31 + page*7 + i) 的伪随机填充。
*/
static uint8_t raw_t_byte(uint32_t seq, uint32_t page, uint32_t i) {
switch (i) {
case 0u: return (uint8_t)'R';
case 1u: return (uint8_t)'A';
case 2u: return (uint8_t)'W';
case 3u: return (uint8_t)'1';
case 4u: return (uint8_t)(seq & 0xFFu);
case 5u: return (uint8_t)((seq >> 8) & 0xFFu);
case 6u: return (uint8_t)((seq >> 16) & 0xFFu);
case 7u: return (uint8_t)((seq >> 24) & 0xFFu);
case 8u: return (uint8_t)(page & 0xFFu);
case 9u: return (uint8_t)((page >> 8) & 0xFFu);
default: return (uint8_t)(seq * 31u + page * 7u + i);
}
}
/*
* 函数功能:填充测试图案到缓冲区
* 入口参数:p_buf - 目标缓冲区 uint8_t* 长度 FTL_PAGE_SIZE
* seq - 启动序号 uint32_t
* page - 逻辑页号 0..3 uint32_t
* 返回值:无
* 限定条件:无
* 函数说明:图案内容见 raw_t_byte。
*/
static void raw_t_fill(uint8_t *p_buf, uint32_t seq, uint32_t page) {
uint32_t i;
for (i = 0; i < FTL_PAGE_SIZE; i++) {
p_buf[i] = raw_t_byte(seq, page, i);
}
}
/*
* 函数功能:缓冲区与期望图案逐字节比对
* 入口参数:p_buf - 待比对缓冲 const uint8_t* 长度 FTL_PAGE_SIZE
* seq - 期望启动序号 uint32_t
* page - 期望逻辑页号 uint32_t
* p_off - 差异偏移输出 uint32_t*(仅失配时有效)
* 返回值:0 - 全部一致,-1 - 存在差异
* 限定条件:无
* 函数说明:期望内容见 raw_t_byte。
*/
static int raw_t_cmp(const uint8_t *p_buf, uint32_t seq, uint32_t page,
uint32_t *p_off) {
uint32_t i;
for (i = 0; i < FTL_PAGE_SIZE; i++) {
if (p_buf[i] != raw_t_byte(seq, page, i)) {
*p_off = i;
return -1;
}
}
return 0;
}
/*
* 函数功能:读一个物理整页到 s_copy_buf
* 入口参数:page_addr - 物理页地址 uint32_t
* 返回值:0 - 成功,其它 - 错误码
* 限定条件:ECC 已使能;s_copy_buf 空闲
* 函数说明:13h 装载(驱动内含二次发令+沉降兜底)→ 0Bh 整页流出。
*/
static int raw_t_read(uint32_t page_addr) {
int ret = gd5f_page_read(page_addr);
if (ret != GD5F_OK) {
return ret;
}
return gd5f_read_from_cache(0, s_copy_buf, FTL_PAGE_SIZE);
}
/*
* 函数功能:裸 NAND 读 / 擦 / 写 / 掉电持久性自检(诊断用)
* 入口参数:无
* 返回值:无
* 限定条件:gd5f2gq5ue_init() 成功后、FTL 初始化前调用(内核前,
* 无并发;s_copy_buf 此时未被 FTL 占用,复用安全)
* 函数说明:每次开机顺序执行三步,三条日志合起来定界故障域:
* 1) persist — 块 A 回读"上一 boot"写入的 seq 图案(擦除前!)
* → 数据跨复位是否保得住;
* 2) erase — 擦块 A/B(返回码含 E_FAIL)+ 读回全 FF 校验
* → 擦除是否真实落盘;
* 3) write — 块 A 写新 seq 图案 + 立即回读逐字节比对(wv)
* → 编程当下是否落盘。
* 判读:wv 全过而 persist 丢失 ⇒ 编程未写入非易失单元(掉电丢);
* erase 返回 OK 而 ff 不满 ⇒ 擦除未落盘;wv 丢 ⇒ 驱动/命令通路问题。
*/
void nand_ftl_raw_persist_test(void) {
uint32_t k;
uint32_t pg;
uint32_t seq_prev = 0u;
uint8_t magic_ok = 0u;
uint32_t off = 0u;
uint32_t seq_new;
uint32_t wv = 0u;
int erase_a;
int erase_b;
uint32_t ff_a = 0u;
uint32_t ff_b = 0u;
/* ---- 1) 持久性检查:回读上一 boot 写入的图案(必须先于擦除) ---- */
pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0;
if (raw_t_read(pg) == GD5F_OK) {
if (s_copy_buf[0] == (uint8_t)'R' && s_copy_buf[1] == (uint8_t)'A' &&
s_copy_buf[2] == (uint8_t)'W' && s_copy_buf[3] == (uint8_t)'1') {
magic_ok = 1u;
seq_prev = (uint32_t)s_copy_buf[4] |
((uint32_t)s_copy_buf[5] << 8) |
((uint32_t)s_copy_buf[6] << 16) |
((uint32_t)s_copy_buf[7] << 24);
}
}
if (magic_ok != 0u) {
uint32_t persist_ok = 0u;
for (k = 0u; k < RAW_T_NPG; k++) {
pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k;
if (raw_t_read(pg) != GD5F_OK) {
break;
}
if (raw_t_cmp(s_copy_buf, seq_prev, k, &off) != 0) {
DBG_ERROR("[RAW] persist corrupt pg %lu off %lu exp %02X got %02X",
(unsigned long)(RAW_T_PAGE0 + k),
(unsigned long)off,
(unsigned int)raw_t_byte(seq_prev, k, off),
(unsigned int)s_copy_buf[off]);
break;
}
persist_ok++;
}
DBG_INFO("[RAW] persist: seq=%lu ok=%u/%lu (prev-boot data after reset)",
(unsigned long)seq_prev, (unsigned int)persist_ok,
(unsigned long)RAW_T_NPG);
} else {
DBG_INFO("[RAW] persist: no magic (first boot or lost), pg1 head: "
"%02X %02X %02X %02X %02X %02X %02X %02X",
(unsigned int)s_copy_buf[0], (unsigned int)s_copy_buf[1],
(unsigned int)s_copy_buf[2], (unsigned int)s_copy_buf[3],
(unsigned int)s_copy_buf[4], (unsigned int)s_copy_buf[5],
(unsigned int)s_copy_buf[6], (unsigned int)s_copy_buf[7]);
}
/* ---- 2) 擦除试验:擦 A/B 块 + 读回全 FF 校验 ---- */
erase_a = gd5f_block_erase(RAW_T_BLK_A);
erase_b = gd5f_block_erase(RAW_T_BLK_B);
for (k = 0u; k < RAW_T_NPG; k++) {
uint32_t i;
uint8_t all_ff = 1u;
pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k;
if (raw_t_read(pg) != GD5F_OK) {
break;
}
for (i = 0; i < FTL_PAGE_SIZE; i++) {
if (s_copy_buf[i] != 0xFFu) {
all_ff = 0u;
break;
}
}
if (all_ff != 0u) {
ff_a++;
}
}
for (k = 0u; k < RAW_T_NPG; k++) {
uint32_t i;
uint8_t all_ff = 1u;
pg = (RAW_T_BLK_B * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k;
if (raw_t_read(pg) != GD5F_OK) {
break;
}
for (i = 0; i < FTL_PAGE_SIZE; i++) {
if (s_copy_buf[i] != 0xFFu) {
all_ff = 0u;
break;
}
}
if (all_ff != 0u) {
ff_b++;
}
}
/* ---- 3) 编程试验:写新 seq 图案 + 立即回读比对 ---- */
seq_new = (magic_ok != 0u) ? (seq_prev + 1u) : 0u;
for (k = 0u; k < RAW_T_NPG; k++) {
int ret;
pg = (RAW_T_BLK_A * GD5F_PAGES_PER_BLOCK) + RAW_T_PAGE0 + k;
raw_t_fill(s_copy_buf, seq_new, k);
if (gd5f_write_enable() != GD5F_OK) {
break;
}
ret = gd5f_program_load(0, s_copy_buf, FTL_PAGE_SIZE);
if (ret != GD5F_OK) {
break;
}
ret = gd5f_program_exec(pg);
if (ret != GD5F_OK) {
DBG_INFO("[RAW] prog exec pg %lu rc %d",
(unsigned long)pg, ret);
break;
}
if (raw_t_read(pg) != GD5F_OK) {
break;
}
if (raw_t_cmp(s_copy_buf, seq_new, k, &off) == 0) {
wv++;
} else {
DBG_ERROR("[RAW] write verify mismatch pg %lu off %lu exp %02X got %02X",
(unsigned long)(RAW_T_PAGE0 + k),
(unsigned long)off,
(unsigned int)raw_t_byte(seq_new, k, off),
(unsigned int)s_copy_buf[off]);
}
}
/* ---- 4) 汇总(单行速读) ---- */
DBG_INFO("[RAW] erase A=%d B=%d; ffA=%u/%lu ffB=%u/%lu; write seq=%lu wv=%u/%lu",
erase_a, erase_b,
(unsigned int)ff_a, (unsigned long)RAW_T_NPG,
(unsigned int)ff_b, (unsigned long)RAW_T_NPG,
(unsigned long)seq_new, (unsigned int)wv, (unsigned long)RAW_T_NPG);
}
/*
* 函数功能:转储某 LPN 的 map 绑定与绑定页裸读内容(诊断:跨 boot 对比
* 绑定地址是否分歧、绑定页内容是否丢失/被擦)
* 入口参数:lpn - 逻辑页号(journal LPN = FatFs 页号)uint32_t
* tag - 场景标记(如 "fresh" / "postreset")const char*
* 返回值:无
* 限定条件:FTL 已初始化
* 函数说明:dhara_map_find 取当前树绑定 → 物理页号 → 绕开缓存的裸读
* 前 16 字节。两 boot 的同一 LPN:地址不同 = 树分歧;地址同
* 内容不同 = 数据页未落盘或被擦。
*/
void nand_ftl_dump_binding(uint32_t lpn, const char *tag) {
dhara_page_t loc = DHARA_PAGE_NONE;
dhara_error_t err;
uint8_t b[16];
uint32_t phy;
if (!s_initialized) {
return;
}
if (dhara_map_find(&s_map, lpn, &loc, &err) < 0) {
DBG_INFO("bind %s LPN=%lu: not found (err %d)",
tag, (unsigned long)lpn, (int)err);
return;
}
phy = ftl_phy_page(loc);
if (gd5f_page_read(phy) != GD5F_OK) {
DBG_INFO("bind %s LPN=%lu -> jpage %lu: raw read err",
tag, (unsigned long)lpn, (unsigned long)loc);
return;
}
gd5f_read_from_cache(0, b, sizeof(b));
DBG_INFO("bind %s LPN=%lu -> jpage %lu (phy %lu): "
"%02X %02X %02X %02X %02X %02X %02X %02X",
tag, (unsigned long)lpn, (unsigned long)loc,
(unsigned long)phy,
(unsigned int)b[0], (unsigned int)b[1],
(unsigned int)b[2], (unsigned int)b[3],
(unsigned int)b[4], (unsigned int)b[5],
(unsigned int)b[6], (unsigned int)b[7]);
}