天天看点

NAND FTL前言一、FTL是什么?二、功能实现二、相关代码测试总结

NAND FTL

前言

工作测试板用了H750核心板模块,模块上有一块512MB NAND Flash,提供的FTL参考比较混乱,使用中也不稳定,网上有一堆阐述FTL的文章,都没有可以直接使用的源代码,还是自己写一个吧,自己的比较香!

成熟的FTL都是商用收费的,免费的uffs又不兼容window fat,这样就不能模拟U盘,所以都促使了写一个适合自己使用的。

一、FTL是什么?

FTL是平台文件系统到NAND之间的中间层,因为NAND是页读取,块写入(如果写入区非FF),块的编程和擦除过程中,还会发生坏块,块本身的擦除次数还有寿命限制

所以,文件系统读写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~15,存储ECC

spare byte16~17,正常FFFF,如果是坏块,则不为FFFF

spare byte18~19,正常FFFF,如果是垃圾块,则不为FFFF

spare byte20~21,如果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
           

测试

NAND FTL前言一、FTL是什么?二、功能实现二、相关代码测试总结

总结

很多时候,除非你找到的是,商用的很成熟的代码和工具。

如果是不知名到第三方,总是比较被动,或者到处都是坑,一旦发现问题的时候,也很难去解决

所以,要学会耐心啃一些协议,多看一些芯片资料,搞懂里面的原理和机制,有了自己的理解之后,就可以写出一个适合需求,又放心使用的驱动了。

继续阅读