天天看點

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是什麼?二、功能實作二、相關代碼測試總結

總結

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

如果是不知名到第三方,總是比較被動,或者到處都是坑,一旦發現問題的時候,也很難去解決

是以,要學會耐心啃一些協定,多看一些晶片資料,搞懂裡面的原理和機制,有了自己的了解之後,就可以寫出一個适合需求,又放心使用的驅動了。

繼續閱讀