NAND FTL
作者:互联网
NAND FTL
前言
工作测试板用了H750核心板模块,模块上有一块512MB NAND Flash,提供的FTL参考比较混乱,使用中也不稳定,网上有一堆阐述FTL的文章,都没有可以直接使用的源代码,还是自己写一个吧,自己的比较香!
成熟的FTL都是商用收费的,免费的uffs又不兼容window fat,这样就不能模拟U盘,所以都促使了写一个适合自己使用的。
一、FTL是什么?
FTL是平台文件系统到NAND之间的中间层,因为NAND是页读取,块写入,块的编程和擦除过程中,还会发生坏块,块本身的擦除次数还有寿命限制
所以,文件系统读写NAND时候,FTL在中间做地址转换,坏块管理等转换
完整的FTL功能通常包括如下:
1 - 坏块管理
nand block都有一定擦写次数,到达一定次数后,读写会不稳定,当发现写入或擦除失败时,应该标记为坏块,不再使用这些块
2 - 擦写平衡
原因同上,不能一直在固定块擦除写入,例如逻辑sector0数据,写入block0 page0,下一次sector再写入时,不是擦除block0后重新写入,而是在block2 page0写入,block0标记为垃圾块,block2标记为新的逻辑块,当找不到空闲块时,进行垃圾块擦除回收
3 - 掉电保护
只要在新块写入成功之后,再把原有块标记为垃圾块,一定程度上,防止了数据丢失
4 - 冷热数据
有些数据是频繁读取和写入的,例如系统LOG,称为热数据,有些数据是写入之后,很少变动的,列入UI图片等,称为冷数据,为了避免频繁擦写寻找空闲块,应该尽可能把热数据和冷数据,分别存在不同区块
二、功能实现
FTL整个流程还是有点复杂的,写了满满的注释,和LOG打印,目前测试下来基本可以用了。
没有做冷热数据,因为一方面主要做静态存储,另一方面能力有限,不知道怎么下手。
NAND CHIP是h27u4g8f,4096个block,每个block 64page,每个page main 2048byte,spare 64byte
spare byte0~16,存储ECC
spare byte16~17,正常FFFF,如果是坏块,则不为FFFF
spare byte17~18,正常FFFF,如果是垃圾块,则不为FFFF
spare byte19~20,如果FFFF,是空闲块,如果0 ~ 4095,是使用中逻辑块
上电初始化NAND,读取ID,然后读取block 0 ~ 4095的PAGE0的SPARE,判断块状态,生成LUT
写扇区:
逻辑转到物理,查找物理块,找到了,如果写入区都是FF,则直接写入,如果写入区有数据,重新查找一个空闲块,把之前块数据复制到新块,并写入新数据,新块标记为当前逻辑块,之前块标记为垃圾块
读扇区:
逻辑转到物理,查找物理块,找到了,读取扇区数据,如果没找到,找一块空闲块,标记为当前逻辑块,然后读取扇区数据,理论上返回应该全都是FF
以上两个动作,都会查找物理块,如果找不到需要的物理块,就查找全部的垃圾块,然后擦除垃圾块,重新标记为空闲块,达到了垃圾块回收的效果。垃圾块回收完成后,读写动作重新查找物理块,如果还没找到,返回NO FREE ERROR
二、相关代码
#include "stdint.h"
#include "rtthread.h"
#include "sram.h"
#include "vopftl.h"
#define DBG
#define DBG_TAG "ftl"
#include "log.h"
#ifndef NULL
#define NULL (0)
#endif
#define FTL_EOK 0
#define FTL_ERROR 1
#define FTL_NO_LIFE 2
#define FTL_NO_FREE 3
#define FTL_WRITE_ERR 4
#define FTL_COPY_ERR 5
#define PAGE_SIZE (2048)
#define PAGE_NUM (64)
#define BLOCK_NUM (4096)
#define OOB_SIZE 16
#define OOB_BAD_POS 0
#define OOB_GARBAGE_POS 2
#define OOB_LOGICAL_POS 4
#define OOB_GET_BAD (*(uint16_t *)&p_oobbuf[OOB_BAD_POS])
#define OOB_GET_GARBAGE (*(uint16_t *)&p_oobbuf[OOB_GARBAGE_POS])
#define OOB_GET_LOGICAL (*(uint16_t *)&p_oobbuf[OOB_LOGICAL_POS])
#define BLOCK_FREE 0xffff
#define BLOCK_BAD 0xfffe
#define BLOCK_GARBAGE 0xfffd
#define BLOCK_ACTIVE 0xfffc
#define BLOCK_ABNORMAL 0xfffb
static nand_drv_t *pdrv = NULL;
ALIGN(4)
static uint8_t oob_buf[OOB_SIZE];
static uint8_t *p_oobbuf = (uint8_t *)oob_buf;
ALIGN(4)
static uint8_t page_buf[PAGE_SIZE];
static uint8_t *p_pagebuf = (uint8_t *)page_buf;
ALIGN(4)
static uint16_t lut_buf[BLOCK_NUM];
static uint16_t *p_lutbuf = (uint16_t *)lut_buf;
static uint16_t cur_logical;
static uint16_t cur_physical;
static uint16_t cur_page;
static uint32_t sector_max = 0;
//标记坏块
static uint8_t block_mask_bad(uint16_t phyblock)
{
uint8_t buf[2] = {0x00, 0x00};
//写入坏块标记
LOG_D("block %d mask bad\r\n", phyblock);
if (pdrv->write_page(phyblock, 0, NULL, OOB_BAD_POS, buf, 2))
{
return FTL_ERROR;
}
//更新表格
p_lutbuf[phyblock] = BLOCK_BAD;
return FTL_EOK;
}
//标记垃圾块
static uint8_t block_mask_garbage(uint16_t phyblock)
{
uint8_t buf[2] = {0x00, 0x00};
//写入垃圾块标记
LOG_D("block %d mask garbage\r\n", phyblock);
if (pdrv->write_page(phyblock, 0, NULL, OOB_GARBAGE_POS, buf, 2))
{
return FTL_ERROR;
}
//更新表格
p_lutbuf[phyblock] = BLOCK_GARBAGE;
return FTL_EOK;
}
//标记逻辑块
static uint8_t block_mask_logical(uint16_t phyblock, uint16_t logblock)
{
uint8_t buf[2] = {0x00, 0x00};
buf[0] = logblock & 0xff;
buf[1] = logblock >> 8;
LOG_D("block %d mask logical %d\r\n", phyblock, logblock);
//写入逻辑块
if (pdrv->write_page(phyblock, 0, NULL, OOB_LOGICAL_POS, buf, 2))
{
return FTL_ERROR;
}
//更新表格
p_lutbuf[phyblock] = logblock;
return FTL_EOK;
}
//逻辑块转物理块地址
static uint8_t block_log2phy(uint16_t *phyblock, uint16_t logblock)
{ //搜寻表格
for (uint16_t i = 0; i < BLOCK_NUM; i++)
{
if (p_lutbuf[i] == logblock)
{ //返回实际物理块
*phyblock = i;
LOG_D("block logical %d to physical %d\r\n", logblock, i);
return FTL_EOK;
}
}
LOG_D("block logical %d to physical fail\r\n", logblock);
return FTL_ERROR;
}
//释放垃圾块
static uint8_t collect_garbage_block(uint16_t phyblock, uint8_t odd_even)
{
uint16_t odd,even;
odd = 0; even = 0;
for (uint16_t block = 0; block < BLOCK_NUM; block++)
{
if (p_lutbuf[block] == BLOCK_GARBAGE)
{ //找到垃圾块
if (pdrv->erase_block(block))
{ //擦除失败,标记坏块
block_mask_bad(block);
}
else
{ //擦除成功
if (block & 0x1){odd++;}else{even++;}
//更新表格
p_lutbuf[block] = BLOCK_FREE;
LOG_D("collect garbage block - %d\r\n", block);
}
}
}
LOG_D("collect garbage block number - odd %d, even %d\r\n", odd, even);
//需要判断奇偶
if (odd_even)
{
if (((phyblock & 0x1) == 1)&&(odd )){return FTL_EOK;}
if (((phyblock & 0x1) == 0)&&(even)){return FTL_EOK;}
}
else
{
if ((odd)||(even)){return FTL_EOK;}
}
return (FTL_NO_FREE);
}
//选址一个空闲块,选择是否需要奇偶判断
static uint8_t select_free_block(uint16_t *phyblock, uint16_t logblock, uint8_t odd_even)
{
LOG_D("select free block fot logic %d\r\n", logblock);
aa: //查找可用块
for (uint16_t block = 0; block < BLOCK_NUM; block++)
{ //块空闲,不判断奇偶或者判断奇偶
if ((p_lutbuf[block] == BLOCK_FREE)&&
((odd_even == 0)||((odd_even > 0)&&((block & 0x1)==(*phyblock & 0x1)))))
{ //找到一个可用的空闲块,并且在同一PLANE
bb: //先做一遍擦除测试
LOG_D("select free block - %d\r\n", block);
LOG_D("erase physical block %d\r\n", block);
if (pdrv->erase_block(block))
{ //擦除失败,标记坏块,更新表格,重新查找
block_mask_bad(block);
goto aa;
}
//是一个好块,标记逻辑块
if (block_mask_logical(block, logblock))
{ //标记失败,进行擦除测试
goto bb;
}
//返回新的物理块
*phyblock = block;
LOG_D("select free block - %d, pass\r\n", block);
return FTL_EOK;
}
}
//释放垃圾块
LOG_D("select free block fail, so collect garbage block\r\n");
if (collect_garbage_block(*phyblock, odd_even))
{ //释放失败
LOG_D("not any garbage block can be free\r\n");
return FTL_NO_FREE;
}
//释放成功,重新开始
goto aa;
}
//数据复制到新块并写入新的数据
static uint8_t writecopy_block(uint16_t *phyblock, uint16_t logblock, uint16_t page, uint8_t *buf)
{
uint8_t rst = FTL_EOK;
uint16_t sblock = *phyblock;
uint16_t tblock = *phyblock;
aa:
//找一块可用的复制块, 判断奇偶
if (select_free_block(&tblock, logblock, 1))
{
return FTL_NO_FREE;
}
//复制块数据
for (uint16_t i = 0; i < PAGE_NUM; i++)
{
rst = FTL_EOK;
if (i == page)
{ //写入数据
if (pdrv->write_page(tblock, i, buf, 0, NULL, 0)){rst = FTL_WRITE_ERR;}
}
else
{ //复制数据
if (pdrv->copyback_page(sblock, i, tblock, i)){rst = FTL_COPY_ERR;}
}
if (rst != FTL_EOK)
{ //写入失败,标记当前块为垃圾块,重新开始查找空闲块
block_mask_garbage(tblock);
goto aa;
}
}
//操作成功,标记之前块为垃圾块
block_mask_garbage(sblock);
//更新当前物理块
*phyblock = tblock;
return FTL_EOK;
}
//读写打开
uint8_t ftl_open(uint32_t sector)
{
//超出操作范围
if (sector >= sector_max)
{
LOG_D("ftl open sector fail, %d / %d\r\n", sector, sector_max);
return FTL_ERROR;
}
//逻辑块位置
cur_logical = sector / PAGE_NUM;
cur_page = sector % PAGE_NUM;
cur_physical = 0xffff;
//转成实际物理块
if (block_log2phy(&cur_physical, cur_logical))
{ //没有找到,申请一个新块
if (select_free_block(&cur_physical, cur_logical, 0))
{ //申请失败
return FTL_ERROR;
}
}
LOG_D("ftl open sector pass, logical %d, physical %d, page %d\r\n", cur_logical, cur_physical, cur_page);
return FTL_EOK;
}
//读写关闭
uint8_t ftl_close(void)
{
LOG_D("ftl close, logical %d, physical %d, page %d\r\n", cur_logical, cur_physical, cur_page);
cur_logical = 0xffff;
cur_page = 0;
cur_physical = 0xffff;
return FTL_EOK;
}
//写入一个扇区
uint8_t ftl_write_sector(uint8_t *buf, uint32_t count)
{
uint16_t i;
uint8_t *pdat = buf;
while (count > 0)
{
//参数异常
if (cur_physical >= BLOCK_NUM){return FTL_ERROR;}
//先读出写入区域的数据
pdrv->read_page(cur_physical, cur_page, p_pagebuf, NULL);
for (i = 0; i < PAGE_SIZE; i++)
{
if (p_pagebuf[i] != 0xff){break;}
}
//数据写入
if (i == PAGE_SIZE)
{ //区域全空,可以直接写入
if (pdrv->write_page(cur_physical, cur_page, pdat, 0, NULL, 0))
{ //写入失败, 复制写入到新的物理块
if (writecopy_block(&cur_physical, cur_logical, cur_page, pdat))
{ //复制失败
return FTL_ERROR;
}
}
}
else
{ //区域非空,写入到新的块
if (writecopy_block(&cur_physical, cur_logical, cur_page, pdat))
{ //复制失败
return FTL_ERROR;
}
}
LOG_D("ftl write pass, logical %d, physical %d, page %d\r\n", cur_logical, cur_physical, cur_page);
count--;
//还有后续数据需要写入
if (count > 0)
{ //已经是物理块最后一页
if ((cur_page + 1) >= PAGE_NUM)
{ //逻辑块+1, 转成实际物理块
cur_logical++;
if (block_log2phy(&cur_physical, cur_logical))
{ //没有找到,申请一个新块
if (select_free_block(&cur_physical, cur_logical, 0))
{ //申请失败
return FTL_NO_FREE;
}
}
}
else
{ //到下一页
cur_page++;
}
//数据地址增加
pdat += PAGE_SIZE;
}
}
return FTL_EOK;
}
//读出一个扇区
uint8_t ftl_read_sector(uint8_t *buf, uint32_t count)
{
uint8_t *pdat = buf;
while (count > 0)
{
//参数异常
if (cur_physical >= BLOCK_NUM){return FTL_ERROR;}
//读出区域数据
pdrv->read_page(cur_physical, cur_page, pdat, NULL);
LOG_D("ftl read pass, logical %d, physical %d, page %d\r\n", cur_logical, cur_physical, cur_page);
count--;
//还有后续数据需要读出
if (count > 0)
{ //已经是物理块最后一页
if ((cur_page + 1) >= PAGE_NUM)
{ //逻辑块+1, 转成实际物理块
cur_logical++;
if (block_log2phy(&cur_physical, cur_logical))
{ //没有找到,申请一个新块
if (select_free_block(&cur_physical, cur_logical, 0))
{ //申请失败
return FTL_ERROR;
}
}
}
else
{ //到下一页
cur_page++;
}
//数据地址增加
pdat += PAGE_SIZE;
}
}
return FTL_EOK;
}
uint32_t ftl_get_sector_count(void)
{
return (sector_max);
}
uint32_t ftl_get_sector_size(void)
{
return (PAGE_SIZE);
}
uint32_t ftl_is_init(void)
{
return 0;
}
#if 0
uint32_t fatfs_ftl_lock(void)
{
}
uint32_t fatfs_ftl_unlock(void)
{
}
uint32_t fatfs_ftl_ready(void)
{
}
uint32_t udisk_ftl_lock(void)
{
}
uint32_t udisk_ftl_unlock(void)
{
}
uint32_t udisk_ftl_ready(void)
{
}
#endif
//扫描全部物理块
static void scan_block(void)
{
uint16_t v_bad, v_garbage, v_free, v_active, v_abnormal;
uint32_t readerr = 0;
//扫描全部BLOCK,产生转换表
v_bad = 0;
v_garbage = 0;
v_free = 0;
v_active = 0;
v_abnormal = 0;
for (uint16_t i = 0; i < BLOCK_NUM; i++)
{ //读取OOB信息
if (pdrv->read_page(i, 0, NULL, p_oobbuf))
{ //读取错误
readerr++;
}
if (OOB_GET_BAD != 0xffff)
{ //损坏块
p_lutbuf[i] = BLOCK_BAD;
v_bad++;
}
else if (OOB_GET_GARBAGE != 0xffff)
{ //垃圾块
p_lutbuf[i] = BLOCK_GARBAGE;
v_garbage++;
}
else if (OOB_GET_LOGICAL == 0xffff)
{ //空闲块
p_lutbuf[i] = BLOCK_FREE;
v_free++;
}
else if (OOB_GET_LOGICAL < BLOCK_NUM)
{ //活动块
p_lutbuf[i] = OOB_GET_LOGICAL;
v_active++;
}
else
{ //异常块
p_lutbuf[i] = BLOCK_ABNORMAL;
v_abnormal++;
}
}
LOG_D("ftl nand scan complete, read error - %d\r\n", readerr);
LOG_D("block bad: %d\r\n", v_bad);
LOG_D("block garbage: %d\r\n", v_garbage);
LOG_D("block free: %d\r\n", v_free);
LOG_D("block active: %d\r\n", v_active);
LOG_D("block abnormal: %d\r\n", v_abnormal);
sector_max = (v_garbage + v_free + v_active) * PAGE_NUM;
uint32_t tmp1 = (v_garbage + v_free) * PAGE_NUM * PAGE_SIZE / (1024 * 1024);
uint32_t tmp2 = (v_garbage + v_free + v_active) * PAGE_NUM * PAGE_SIZE / (1024 * 1024);
LOG_D("ftl total sector %d, free %dMByte, capacity %dMByte\r\n", sector_max, tmp1, tmp2);
}
//擦除全部物理块
static void format_block(void)
{
uint16_t i;
LOG_D("ftl format:\r\n");
for (i = 0; i < BLOCK_NUM; i++)
{
if (pdrv->erase_block(i))
{
LOG_D("ftl format block %d fail\r\n", i);
block_mask_bad(i);
}
}
}
uint8_t ftl_init(void)
{
pdrv = (nand_drv_t *)&nand_h27u4g8f;
//芯片初始化
if (pdrv->chip_init()){return 1;}
LOG_D("chip init pass - %08x\r\n", pdrv->id);
scan_block();
return FTL_EOK;
}
#ifdef FINSH_USING_MSH
#include "stdlib.h"
#include "ll_random.h"
static void ftl_msh_function(int argc, char **argv)
{
uint16_t i,j;
uint8_t *buf;
if ((argc == 2)&&(rt_strcmp(argv[1], "lut") == 0))
{
rt_kprintf("ftl lut:\r\n");
for (i = 0; i < BLOCK_NUM / 32; i++)
{
for (j = 0; j < 32; j++)
{
rt_kprintf("%04x ", lut_buf[i * 32 + j]);
}
rt_kprintf("\r\n");
}
}
if ((argc == 2)&&(rt_strcmp(argv[1], "format") == 0))
{
format_block();
}
if ((argc == 2)&&(rt_strcmp(argv[1], "scan") == 0))
{
scan_block();
}
if ((argc == 2)&&(rt_strcmp(argv[1], "garbage") == 0))
{
collect_garbage_block(0, 0);
}
if ((argc == 3)&&(rt_strcmp(argv[1], "read") == 0))
{
buf = rt_malloc_align(PAGE_SIZE, 4);
rt_memset(buf, 0xff, PAGE_SIZE);
if (ftl_open(atoi(argv[2])) == FTL_EOK)
{
ftl_read_sector(buf, 1);
ftl_close();
}
for (i = 0; i < PAGE_SIZE / 64; i++)
{
for (j = 0; j < 64; j++)
{
rt_kprintf("%02x ", buf[i * 64 + j]);
}
rt_kprintf("\r\n");
}
rt_free_align(buf);
}
if ((argc == 3)&&(rt_strcmp(argv[1], "write") == 0))
{
buf = rt_malloc_align(PAGE_SIZE, 4);
rt_memset(buf, 0xff, PAGE_SIZE);
ll_random_generate((uint32_t *)buf, PAGE_SIZE / 4);
if (ftl_open(atoi(argv[2])) == FTL_EOK)
{
ftl_write_sector(buf, 1);
ftl_close();
}
rt_free_align(buf);
}
}
MSH_CMD_EXPORT_ALIAS(ftl_msh_function, ftl, ftl msh function);
#endif
测试
总结
很多时候,除非你找到的是,商用的很成熟的代码和工具。
如果是不知名到第三方,总是比较被动,或者到处都是坑,一旦发现问题的时候,也很难去解决
所以,要学会耐心啃一些协议,多看一些芯片资料,搞懂里面的原理和机制,有了自己的理解之后,就可以写出一个适合需求,又放心使用的驱动了。
标签:return,cur,NAND,page,FTL,ftl,block 来源: https://blog.csdn.net/sainty07/article/details/115802855