yaffs2裸机移植

yaffs 文件系统

概述

Yaffs(Yet Another Flash File System)是专门为NAND Flash设计的文件系统,在GPL协议下发布,适用于大容量的存储设备的嵌入式系统,主要由Aleph One公司的Charles Manning开发。目前Yaffs文件系统有两个版本Yaffs1和Yaffs2。

一般来说对于small page的NAND Flash芯片,Yaffs1支持更好,对于现代大部分都是big page 的NAND Flash芯片的情况下,Yaffs2更加常用。

Yaffs文件系统针对NAND Flash特性做了很多针对性的优化,提高性能,延长芯片的使用寿命。

文件系统在整个应用体系中属于中间层,对下和操作系统、Flash对接,对上和应用程序对接。

其移植性非常好,对于没有操作系统的设备上,可以直接使用。

移植裸机

这里的实现平台是am335x,采用的驱动是am335x的ti-startware的NAND驱动程序,编译环境是CCS 9.3,有参考:Yaffs2 文件系统裸机移植

源码获取

在其官网页面获取yaffs源码

  1. 通过git直接下载clone,下载成功文件夹是yaffs2
  2. 选择 access our Git repository over the web 选择最新版master的snapshot下载,下载成功文件夹是yaffs-xxxxxxxxx

在官网也可以下载一个指导文件,Yaffs Direct Interface

进入下载好的文件夹,根目录下就有许多.c和.h文件,都很重要,在Yaffs Direct Interface中会有部分说明。

需要的源码文件

在direct文件夹下,执行handle_common.sh文件,参数为copy,这一点在该文件夹下的README文件有说明,

cd yaffs2/direct    #如果不是clone下来的,通过snapshot下载的,则不是yaffs2
./handle_common.sh copy

基本上把根目录下的.c和对应的.h文件都复制了过来,这些在裸机工程中都需要,

额外需要的是测试文件夹下的 yaffs_osglue.c 、yaffs_nand_drv.c、yaffs_nand_drv.h,但是需要修改。

修改
  1. ydirectenv.h

    #define yaffs_trace(msk, fmt, ...) do { \
        if (yaffs_trace_mask & (msk)) \
            printf("yaffs: " fmt "\n", ##__VA_ARGS__); \
    } while (0)
    //需要将printf替换成裸机所能支持的打印信息函数
    //这个yaffs_trace_mask是需要打印的跟踪类型选择,
    //可以选择的类型的宏定义在yaffs_trace.h中
    
  2. yaffs_osglue.c

    这个文件在test测试文件夹中,实现操作系统交互接口,官方给出的内部有选择的,例如yaffsfs_Lock函数的选择宏定义就很简单粗暴,直接 #if 1,那么这里直接将选择宏定义删除除掉,给出裸机的函数配置。yaffs_trace_mask 全局变量就定义在这里

    #include "yaffscfg.h"
    #include "yaffs_guts.h"
    #include "yaffsfs.h"
    #include "yaffs_trace.h"
    #include <assert.h>
    
    #include <errno.h>
    
    
    void *yaffsfs_malloc(size_t size)
    {
        return malloc(size);
    }
    
    void yaffsfs_free(void *ptr)
    {
        free(ptr);
    }
    
    unsigned yaffs_trace_mask =
    
        YAFFS_TRACE_SCAN |
        YAFFS_TRACE_GC |
        YAFFS_TRACE_ERASE |
        YAFFS_TRACE_ERROR |
        YAFFS_TRACE_TRACING |
        YAFFS_TRACE_ALLOCATE |
        YAFFS_TRACE_BAD_BLOCKS |
        YAFFS_TRACE_VERIFY |
    
        0;
    
    /*
     * yaffsfs_SetError() and yaffsfs_GetError()
     * Do whatever to set the system error.
     * yaffsfs_GetError() just fetches the last error.
     */
    
    static int yaffsfs_lastError;
    
    void yaffsfs_SetError(int err)
    {
        //Do whatever to set error
        yaffsfs_lastError = err;
        UARTprintf("yaffs error detected %d\n",
                   err);
    }
    //这里添加了错误报错信息打印,注意打印函数替换
    int yaffsfs_GetLastError(void)
    {
        return yaffsfs_lastError;
    }
    
    
    /*
     * yaffsfs_CheckMemRegion()
     * Check that access to an address is valid.
     * This can check memory is in bounds and is writable etc.
     *
     * Returns 0 if ok, negative if not.
     */
    int yaffsfs_CheckMemRegion(const void *addr, size_t size, int write_request)
    {
        (void) size;
        (void) write_request;
    
        if(!addr)
            return -1;
        return 0;
    }
    
    u32 yaffsfs_CurrentTime(void)
    {
        //return SysTick_GetMS();
        return time(NULL);
    }
    
    
    void yaffsfs_OSInitialisation(void)
    {
    
    }
    
    void yaffsfs_Lock(void)
    {
    
    }
    
    void yaffsfs_Unlock(void)
    {
    
    }
    
    //#if defined(__CC_ARM)  /* ARMCC compiler */
    int strnlen(const char *Str, int MaxLen)
    {
        int i;
        for (i=0;i<MaxLen; i++) {
            if(Str[i] == 0) {
                break;
            }
        }
        return i;
    }
    //#endif
    
    void yaffs_bug_fn(const char *file_name, int line_no)
    {
        /*plus_log(PLUS_LOG_ERR, "yaffs bug detected %s:%d",
            file_name, line_no);
        while(1);*/
        UARTprintf("yaffs bug detected %s:%d\n",
            file_name, line_no);
        assert(0);
    }
    //yaffs_bug_fn函数中的UARTprintf打印信息函数依旧需要替换成裸机支持的。
    
  3. yportenv.h

    //添加选择宏定义
    #define CONFIG_YAFFS_DIRECT  //使用直接接口
    #define CONFIG_YAFFSFS_PROVIDE_VALUES  //一些报错类型、文件创建打开类型
    #define CONFIG_YAFFS_DEFINES_TYPES     //需要的变量类型声明缩写u8、u16等
    #define CONFIG_YAFFS_PROVIDE_DEFS  //一些文件类型、分配标签的定义
    
    //在 CONFIG_YAFFS_DEFINES_TYPES 变量类型声明下添加
    #ifdef CONFIG_YAFFS_DEFINES_TYPES
    
    typedef long          loff_t;  //维护当前读写位置
    typedef long          off_t;   //偏移地址
    typedef unsigned long mode_t;  //文件打开权限
    typedef unsigned long dev_t;   //设备号
    //以上四个是在linux系统中定义使用的,裸机中也要补充定义
    
    
    //这个文件后面有个 BUG 的选择定义宏,视情况,在我的移植中,跟踪和报错都有显示了,够用(这个报错可以直接定位到某一语句)
    
  4. yaffs_nand_drv.c

    这个文件非常重要!!!移植yaffs文件处理到裸机工程上,说白了就是要完成yaffs的读写与NAND驱动读写的对接,至于yaffs完成的其他,例如分区、标签处理,内存分配等任务,都在yaffs源码内部完成,可以不用修改,原理用户也不需要完全理解,就可以移植使用,而实现基础就是完成这文件,让yaffs可以对NAND Flash进行操作。

    #include "yaffs_guts.h"
    //#include "yaffs_nand_drv.h"
    #include "yaffs_trace.h"
    //#include "yportenv.h"
    #include "yaffs_guts.h"
    #include "yaffs_ecc.h"
    #include "yaffsfs.h"
    
    #include "nandlib.h"
    #include "include.h"
    
    extern NandInfo_t    nandInfo;
    extern unsigned char eccData[]; //ecc校验缓冲区FIFO或者Buffer
    
    
    #define BAD_BLOCK_INFO_BITS 2 //坏块检测,这里实际没有用到
    
    //每一页的大小(字节数)
    #define NANDFLASH_RW_PAGE_SIZE (NAND_PAGESIZE_4096BYTES)
    #define NANDFLASH_PAGE_PER_BLOCK 64 //每一块的页数
    #define NANDFLASH_SPARE_SIZE 224 //每一页的Spare区域
    

    yaffs访问NAND的接口,下面这个函数是其中的写操作。yaffs2非常灵活,在《Yaffs Direct Interface》的9.3节有描述,其不需要一个标准的spare区域分割规定和ECC校验方式,可以由NAND驱动提供,也可以在这个函数中使用yaffs的ECC处理函数。

    spare中就有三个分割区,坏块标识、ECC、剩余的标签区,就是这个函数中的oob

    static int yaffs_nand_drv_WriteChunk(struct yaffs_dev *dev, int nand_chunk,
    				   const u8 *data, int data_len,
    				   const u8 *oob, int oob_len)
    {
        unsigned int retVal;
        //yaffs上层函数传递的nand_chunk参数,就是要操作的页数,
        //这个页数是相对NAND总页数来讲的,不是每块的第几页,所以需要计算一下
        u32 blkNum = nand_chunk / NANDFLASH_PAGE_PER_BLOCK;
        u32 pageNum = nand_chunk % NANDFLASH_PAGE_PER_BLOCK;
    	
        u32 byteCnt;
    
    	//检查是否坏块,NANDBadBlockCheck就是nand驱动里的坏块检测函数,直接使用
        retVal = NANDBadBlockCheck(&nandInfo, blkNum);
        if( retVal == NAND_BLOCK_BAD )
            return YAFFS_FAIL;
    	//将ecc清零,ecc的计算也是在nand驱动中完成的,传入一个空的缓冲数组即可
        for(byteCnt = 0; byteCnt < 128; byteCnt++)
            eccData[byteCnt] = 0;
        
    	//nand驱动中写操作,稍微修改了一下原版的驱动,
        //在后面具体介绍,这里存在大坑,如果不能良好对接,包括参数传递和
        //返回值的判定等,那么会造成根本性错误,而且移植的错误大概率是这里
        //造成的
        retVal= NANDPageWrite_Yaffs(&nandInfo, blkNum, pageNum, data,
                                   &eccData[0], oob, oob_len);
    
    	//NAND_STATUS_PASSED这是ti-startware的nand驱动里的返回值
        if( (retVal & NAND_STATUS_PASSED) )
        {
            return YAFFS_OK;
        }
        else
            return YAFFS_FAIL;
    }
    
    

    读操作接口函数

    static int yaffs_nand_drv_ReadChunk(struct yaffs_dev *dev, int nand_chunk,
    				   u8 *data, int data_len,
    				   u8 *oob, int oob_len,
    				   enum yaffs_ecc_result *ecc_result_out)
    {
        unsigned int retVal;
        u32 blkNum = nand_chunk / NANDFLASH_PAGE_PER_BLOCK;
        u32 pageNum = nand_chunk % NANDFLASH_PAGE_PER_BLOCK;
        u32 byteCnt;
    
        retVal = NANDBadBlockCheck(&nandInfo, blkNum);
        if( retVal == NAND_BLOCK_BAD )
            return YAFFS_FAIL;
    
        for(byteCnt = 0; byteCnt < 128; byteCnt++)
            eccData[byteCnt] = 0;
    	//调用NAND驱动,这里也修改了
        retVal= NANDPageRead_Yaffs(&nandInfo, blkNum, pageNum, data,
                                   &eccData[0],oob,oob_len);
    	//超时读取超时反馈,ecc处理为未知错误
        if( (retVal & NAND_STATUS_WAITTIMEOUT) )
        {
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
            return YAFFS_FAIL;
        }
        else if( (retVal & NAND_STATUS_READWRITE_DMA_FAIL) )
        {//NAND驱动采用DMA传输。传输失败,ecc处理为未知错误
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
            return YAFFS_FAIL;
        }
    
        if( (retVal & NAND_STATUS_READ_ECC_UNCORRECTABLE_ERROR) )
        {
            //ECC校验错误且更改失败,但这里问题,在后面问题总结中具体描述
            *ecc_result_out = YAFFS_ECC_RESULT_UNFIXED;
            //ECC检验是内部的GPMC的校验,不能区别出空块(全是0xff,没有写
            //入内容的块),在这里检查一下,是否是空块,空块也会反馈
            //NAND_STATUS_READ_ECC_UNCORRECTABLE_ERROR的错误
           	//参考的文档中采用的是yaffs的ecc校验,没有这个问题
            for(byteCnt = 0; byteCnt < 10; byteCnt++)
                if((eccData[byteCnt] != 0xff) || (eccData[byteCnt] != data[byteCnt]))
                    return YAFFS_OK;
            //首先循环检测前十个读到的ECC和data,如果不全是0xff,那么就不是空块
            if(yaffs_check_ff(data, NANDFLASH_RW_PAGE_SIZE))
                *ecc_result_out = YAFFS_ECC_RESULT_NO_ERROR;
            //接着检测全部整块的值,是否是0xff,这里使用了yaffs_guts.c中的函数
        }
        else if( (retVal & NAND_STATUS_READ_ECC_ERROR_CORRECTED) )
            *ecc_result_out = YAFFS_ECC_RESULT_FIXED;
    
        if( (retVal & NAND_STATUS_PASSED) )
            *ecc_result_out = YAFFS_ECC_RESULT_NO_ERROR;
    
    	return YAFFS_OK;
    }
    
    

    下面是块擦除、坏块检测、坏块标记函数,都可以直接使用nand驱动中对应的函数

    static int yaffs_nand_drv_EraseBlock(struct yaffs_dev *dev, int block_no)
    {
        unsigned int retVal;
    	//struct nand_chip *chip = dev_to_chip(dev);
    
    	//if(nanddrv_erase(chip, block_no) == 0)
            //return YAFFS_OK;
        retVal = NANDBlockErase(&nandInfo, (unsigned int)block_no);
        if( retVal == NAND_STATUS_PASSED )
        {
    		return YAFFS_OK;
        }
    	else
    		return YAFFS_FAIL;
    }
    
    static int yaffs_nand_drv_MarkBad(struct yaffs_dev *dev, int block_no)
    {
        unsigned int retVal;
    
        retVal = NANDMarkBlockAsBad(&nandInfo, (unsigned int)block_no);
        if( retVal == NAND_STATUS_PASSED )
    		return YAFFS_OK;
    	else
    		return YAFFS_FAIL;
    }
    
    static int yaffs_nand_drv_CheckBad(struct yaffs_dev *dev, int block_no)
    {
        unsigned int retVal;
    
        retVal = NANDBadBlockCheck(&nandInfo, (unsigned int)block_no);
        if( retVal == NAND_BLOCK_GOOD )
            return YAFFS_OK;
        else
    		return YAFFS_FAIL;
    
    }
    

    初始化和反初始化函数,并不需要yaffs完成这个操作,所以都直接返回成功YAFFS_OK

    //初始化接口
    static int yaffs_nand_drv_Initialise(struct yaffs_dev *dev)
    {
    	
    	return YAFFS_OK;
    }
    //反初始化接口
    static int yaffs_nand_drv_Deinitialise(struct yaffs_dev *dev)
    {
    	
    	return YAFFS_OK;
    }
    

    注册函数,在许多参考移植中是没有这个函数的,都是yaffs_StartUp函数,现在替换为了这个 yaffs_nand_install_drv 或者其他类似的设备注册函数(在direct\test-framework\yaffscfg2k.c中类似的,在《Yaffs Direct Interface》的10节有描述),他需要指明一些参数类型,参考配置如下。

    struct yaffs_dev *yaffs_nand_install_drv(const YCHAR *dev_name,
            u32 dev_id, u32 blocks, u32 start_block, u32 end_block)
    {
        struct yaffs_dev *dev = NULL;
        struct yaffs_driver *drv = NULL;
        struct yaffs_param *param = NULL;
    
        dev = yaffsfs_malloc(sizeof(struct yaffs_dev));
        if(dev == NULL) {
            yaffsfs_free(dev);
            return dev;
        }
    
        if((end_block == 0) || (end_block >= (start_block + blocks)))
        end_block = (start_block + blocks) - 1;
    
        memset(dev, 0, sizeof(struct yaffs_dev));
        param = &(dev->param);
        drv = &(dev->drv);
    
        param->name = dev_name;
        param->total_bytes_per_chunk = NANDFLASH_RW_PAGE_SIZE;
        param->chunks_per_block = NANDFLASH_PAGE_PER_BLOCK;
        param->spare_bytes_per_chunk = NANDFLASH_SPARE_SIZE;
        param->n_reserved_blocks = 5;
        param->start_block = start_block;
        param->end_block = end_block;
        param->is_yaffs2 = 1;
        param->n_caches = 10;
    
        drv->drv_write_chunk_fn = yaffs_nand_drv_WriteChunk;
        drv->drv_read_chunk_fn = yaffs_nand_drv_ReadChunk;
        drv->drv_erase_fn = yaffs_nand_drv_EraseBlock;
        drv->drv_mark_bad_fn = yaffs_nand_drv_MarkBad;
        drv->drv_check_bad_fn = yaffs_nand_drv_CheckBad;
        drv->drv_initialise_fn = yaffs_nand_drv_Initialise;
        drv->drv_deinitialise_fn = yaffs_nand_drv_Deinitialise;
    
        yaffs_add_device(dev);
        return dev;
    }
    

应用

首先将NAND初始化配置好。

添加设备

添加设备,并定义设备名,这个设备名和下面的挂载路径、文件创建路径都要一致,

这里两个分区可以存在重复的区域,只要没有写入,就不会报错

yaffs_nand_install_drv("C:", 0, 1024, 10, 0);
yaffs_nand_install_drv("D:", 0, 1024, 1050, 0);
格式化分区

yaffs_format函数格式化分区,就是清空分区,后面三个参数分别是:
unmount_flag;force_unmount_flag;remount_flag
如果首个参数,即路径名没有错误,设备也添加了,则会检测是否有挂载,

状态unmountforce_unmountremount返回值描述
挂载000-1,失败设备繁忙
100-1,失败
0,成功
-1:因为设备有处理项目,不能取消挂载,
0:设备没有处理项目,可以取消挂载
101-1,失败
0,成功
-1:因为设备有处理项目,不能取消挂载,如果能取消挂载,则是在重新挂载时出错,报错没有内存空间
0:设备没有处理项目,可以取消挂载,且重新挂载成功。
110-1,失败
0,成功
0,格式化成功。注意:此时取消挂载了
-1,设备未添加,路径名出错等
111-1,失败
0,成功
1,不管有没有处理项目,强迫取消挂载,在重新挂载时出错,报错没有内存空间
0,重新挂载成功。
非挂载xxx0:成功
-1,失败
0,格式化成功。
-1,设备未添加,路径名出错等
    y=yaffs_format("C:",0,0,0);
    if(y!=0)
    {
        UARTPuts("\r\nyaffs2 format error\r\n",-1);
        while(1);
    }
    else
        UARTPuts("\r\nyaffs2 format nand\r\n",-1);
挂载分区

挂载分区之后就可以就可以进行各种操作了。注意:如果想要挂载创建的D:分区,需要先取消挂载C:分区,否则,报错,内存不足

    y=yaffs_mount("C:");
    if(y<0)
        UARTPuts("\r\nyaffs2 Mount C:/ error\r\n",-1);
    else
        UARTPuts("\r\nyaffs2 Mount C:/ succeeded\r\n",-1);
空闲区域大小
    y=yaffs_freespace("C:");//读取分区中的空余空间大小(字节数)
    if(y<0)
        UARTPuts("\r\nyaffs2 freespace error\r\n",-1);
    else
        UARTprintf("\r\nyaffs2 freespace %x,%d\r\n",y,y);

除了上面这个函数,还有两个空间计算函数:yaffs_totalspace(path) 和 yaffs_inodecount(path) ,后者返回的是路径下的文件数

创建文件夹

yaffs_mkdir函数创建文件夹,第二个参数是mode,创建文件夹一定是S_IFDIR,这个参数有超多选择,将在后面Yaffs Direct Interface中重要内容记录一部分。

    y=yaffs_mkdir("C:/test",S_IFDIR);
    if(y<0)
        UARTPuts("\r\nyaffs2 make directory C:/test error\r\n",-1);
    else
        UARTPuts("\r\nyaffs2 make directory C:/test succeeded\r\n",-1);
打开文件

yaffs_open函数打开文件,yaffs_open函数返回的就是handle,具体在后面的Yaffs Direct Interface记录解释。

第二个参数是oflag,访问文件的类型

oflag描述
O_CREAT如果文件不存在,创建一个文件,返回成功;如果存在,则不操作,且返回成功
O_EXCL仅仅配合O_CREAT标志使用,如果文件存在,则返回失败
O_TRUNC如果文件存在,打开并且写访问,然后删减文件到0字节长( then truncate the file to zero bytes long)
O_APPEND不管handle的位置,总是要写到文件的末尾,handle有保存文件的读写位置指针。
O_RDWR打开文件进行读写操作
O_WRONLY打开文件进行写操作,不能读
0(ZERO)如果O_RDWR和O_WRONLY没有设置,那么这个文件将会打开并只读访问

有比较典型的应用:

  1. O_CREAT | O_TRUNC | O_RDWR :

    创建一个文件如果它不存在,如果它存在,则删减到0长度,通常被用来覆盖一个文件或者创建一个不存在的文件。

  2. O_CREAT | OEXCL | O_WRONLY :

    创建一个新文件,仅仅为了写操作打开它,如果文件存在,则失败

第三个参数是mode,在文件打开这个操作中有三个mode可以控制或者设置,上面文件夹建立有用到一个S_IFDIR。

mode描述
S_IREAD文件可能打开为了进行读操作
S_IWRITE文件可能打开为了进行写操作
S_IEXEC文件可能打开为了执行,这个选项没有被yaffs强制执行
    handle = yaffs_open("C:/test/test.txt", O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE);
    if(handle<0)
        UARTPuts("\r\nyaffs2 open C:/test/test.txt failed \r\n",-1);
    else
        UARTPuts("\r\nyaffs2 open C:/test/test.txt succeeded\r\n",-1);
读写文件

有了打开文件产生的handle,就可以进行yaffs_close(handle) 、yaffs_read(handle, buffer, nbytes)、yaffs_write(handle, buffer, nbytes)等函数操作,其他部分在Yaffs Direct Interface记录解释。

    uint8 yaffsReadFIFO[17];
    uint8 yaffsWriteFIFO[17];

    for(i=0;i<17;i++)
    {
        yaffsReadFIFO[i]=0;
        yaffsWriteFIFO[i]=i;
    }    

	UARTPuts("\r\nyaffs2 write",-1);
    yaffs_write(handle,yaffsWriteFIFO,17);
    UARTPuts("\r\nyaffs2 read:",-1);
    yaffs_pread(handle,yaffsReadFIFO,17,0);
    for(i=0;i<17;i++)
        UARTprintf("  %x  ",yaffsReadFIFO[i]);
关闭文件
y=yaffs_close(handle);
if(y==0)
    UARTPuts("\r\nyaffs2 close C:/test/test1.txt succeeded \r\n",-1);
else
    UARTPuts("\r\nyaffs2 close C:/test/test1.txt failed \r\n",-1);
删除文件

yaffs_unlink函数可以删除文件,注意如果在“关闭文件”之前调用,那么文件打开的handle还存在,那么即使删除文件,依旧能够访问文件通过这个handle,读写都没问题。在文件关闭之后,handle就被关闭,文件被真正删除。

    y=yaffs_unlink("C:/test/test.txt");
    if(y<0)
        UARTPuts("\r\nyaffs2 remove C:/test/test.txt failed \r\n",-1);
    else
        UARTPuts("\r\nyaffs2 remove C:/test/test.txt succeeded\r\n",-1);
    
    handle = yaffs_open("C:/test/test.txt", O_RDWR | O_APPEND, S_IREAD | S_IWRITE);
    if(handle<0)
        UARTPuts("\r\nyaffs2 RE open C:/test/test.txt failed \r\n",-1);
    else
        UARTPuts("\r\nyaffs2 RE open C:/test/test.txt succeeded\r\n",-1);

还有其他的操作可以自行了解,部分记录在Yaffs Direct Interface中,但文中的解释也不是很全,还需要自己实际操作理解。

问题:

内存堆(heap)不足

在挂载时,有两个地方会使用到分配内存函数yaffsfs_malloc()->malloc(),

  1. 首先是yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_guts_ll_init-> yaffs_init_tmp_buffers 函数,这个函数暂时分配了操作缓存区,要能成功分配出6个临时内存操作区,

    	memset(dev->temp_buffer, 0, sizeof(dev->temp_buffer));
    //yaffs设置的YAFFS_N_TEMP_BUFFERS=6
    //配置的设备total_bytes_per_chunk,每一页4096字节即0x1000
    	for (i = 0; buf && i < YAFFS_N_TEMP_BUFFERS; i++) {
    		dev->temp_buffer[i].in_use = 0;
    		buf = kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS);
    		dev->temp_buffer[i].buffer = buf;
    	}
    

    我采用的CCS运行环境,heap分配是在工程里的CMD文件中

       -stack  0x0008                   /* SOFTWARE STACK SIZE  0x0008   */
       -heap   0x2000                   /* HEAP AREA SIZE       0x2000   */
       -e Entry
    

    那么,分配6个临时内存操作区,需要堆大小是0x6000,配置稍微扩大一点0x8000,那么这个函数里的内存分配不在出错。(我这里基础需要0x2000的堆大小,其他函数需要)

    这里记录一个编译器的警告:缺少.sysmem段,在CMD文件中添加了

    .sysmem : load > DDR_MEM

    但是报错位置改变,依旧内存不够。

  2. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_cache_init

    u32 cache_bytes = mgr->n_caches * sizeof(struct yaffs_cache);
    mgr->cache = kmalloc(cache_bytes, GFP_NOFS);
    
    
    for (i = 0; i < mgr->n_caches && buf; i++) {
    	struct yaffs_cache *cache = &mgr->cache[i];
    
    	cache->object = NULL;
    	cache->last_use = 0;
    	cache->dirty = 0;
    	cache->data = buf =
    	kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS);
    }
    

    这个结构体yaffs_cache有点复杂,嵌套了结构体还有选择宏,这里直接读出它的大小是28字节,n_caches是初始化分区时设置的值为10

    则是10*28=0x118

    后面的是10个4096=40960=0xA000,一共是A118,

    加上之前的0x8000,一共是0x12118,跟踪报错地方改变

  3. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_init_blocks

    这个函数里首先分配了n_blocks * sizeof(struct yaffs_block_info)大小的区域

    n_blocks就是定义分区的末端块和起始块的差,就是分区的总块数,这里是最大可以分配4096,为了充分使用NAND Flash,我们就按照最大来配置heap的大小。

    结构体 yaffs_block_info 是8字节,所以总共所需要的大小是

    4096*8=0x8000

    yaffs_block_info 是10个32位的变量,应该是40字节,但是这里yaffs使用了位域定义,就是结构体变量后面添加了冒号:

    u32 block_state:4

    对应的实际只有4位,4bit

    如果内存空间够大,分配没有出错,紧接着又分配了dev->chunk_bit_stride * n_blocks

    chunk_bit_stride =(chunks_per_block+7)/8=(64+7)/8=8

    4096*8=0x8000

    /* If the first allocation strategy fails, thry the alternate one */
    	dev->block_info =
    		kmalloc(n_blocks * sizeof(struct yaffs_block_info), GFP_NOFS);
    
    	/* Set up dynamic blockinfo stuff. Round up bytes. */
    	dev->chunk_bit_stride = (dev->param.chunks_per_block + 7) / 8;
    	dev->chunk_bits =
    		kmalloc(dev->chunk_bit_stride * n_blocks, GFP_NOFS);
    

    一共加起来是0x10000,

    加上之前的0x12118,一共是0x22118,跟踪报错地方再次改变。

  4. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_init_tnodes_and_objs

    allocator = kmalloc(sizeof(struct yaffs_allocator), GFP_NOFS);
    

    yaffs_allocator这个结构体也比较复杂,嵌套结构体,直接sizeof计算出大小是36,0x24

    加上之前的0x22118,是0x2213c

  5. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_create_initial_dir -> yaffs_create_fake_dir (这个函数进行了4次) -> yaffs_new_obj -> yaffs_get_tnode-> yaffs_alloc_raw_tnode -> yaffs_create_tnodes

    这个地方也会有创建tnode

    new_tnodes = kmalloc(n_tnodes * dev->tnode_size, GFP_NOFS);
    

    但是根据yaffs_new_obj 传递参数,yaffs_create_fake_dir执行的四次当中,会有执行yaffs_get_tnode1次

    yaffs_create_fake_dir (这个函数进行了4次) -> yaffs_new_obj -> yaffs_alloc_empty_obj -> yaffs_alloc_raw_obj -> yaffs_create_free_objs

    这里会报错 :Could not allocate more objects 4次

    new_objs = kmalloc(n_obj * sizeof(struct yaffs_obj), GFP_NOFS);
    list = kmalloc(sizeof(struct yaffs_obj_list), GFP_NOFS);
    //n_obj=YAFFS_ALLOCATION_NOBJECTS=100
    //sizeof(struct yaffs_obj)=144
    //sizeof(struct yaffs_obj_list)=8
    

    (100*144+8) *1=14408 *1=0x3848 *1

    加上之前的0x2213c,一共是0x25984

    这里为什么是乘1,且执行yaffs_get_tnode1次所申请的内存堆空间也没有计算,因为第四项allocator里面有一个tnode和obj空间,所以只需要额外再申请3个obj空间。但是,执行一次yaffs_create_free_objs,会申请100个obj的内存空间,后面两个也就不需要申请了。

  6. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise ->yaffs_summary_init

    sum_bytes = dev->param.chunks_per_block *
    		sizeof(struct yaffs_summary_tags);
    //64*3*4=768  yaffs_summary_tags结构体是3个unsign
    chunks_used = (sum_bytes + dev->data_bytes_per_chunk - 1)/
    		(dev->data_bytes_per_chunk -
    			sizeof(struct yaffs_summary_header));
    //(768+4096-1)/{4096-(4*4)}=1  yaffs_summary_header结构体是4个unsign
    dev->chunks_per_summary = dev->param.chunks_per_block - chunks_used;
    //64-1=63
    sum_tags_bytes = sizeof(struct yaffs_summary_tags) *
    				dev->chunks_per_summary;
    //3*4*63=756
    dev->sum_tags = kmalloc(sum_tags_bytes, GFP_NOFS);
    dev->gc_sum_tags = kmalloc(sum_tags_bytes, GFP_NOFS);
    

    这里两个是分配756 *2=0x2F4 *2=0x5E8

    加上之前的0x25984,一共是0x25F6C

  7. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs2_checkpt_restore -> yaffs2_rd_checkpt_data -> yaffs2_checkpt_open

    if (!dev->checkpt_buffer)
    	dev->checkpt_buffer =
            kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS);
    //4096=0x1000
    if (!dev->checkpt_block_list)
    	dev->checkpt_block_list =
            kmalloc(sizeof(int) * dev->checkpt_max_blocks, GFP_NOFS);
    //4*257=1028=0x404
    

    一共是0x1404,

    加上之前的0x25F6C,一共是0x27370

  8. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_deinit_blocks

    这里释放了前面的第三项:block_info和chunk_bits,一共是0x10000

    if (dev->block_info_alt && dev->block_info)
    	vfree(dev->block_info);
    else
    	kfree(dev->block_info);
    if (dev->chunk_bits_alt && dev->chunk_bits)
    	vfree(dev->chunk_bits);
    else
    	kfree(dev->chunk_bits);
    
  9. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_deinit_tnodes_and_objs

    这里释放了tnodes和objs,前面的第4项: allocator,是 0x24,还有第五项的tnodes和objs

  10. 接下来,又运行了3-5项yaffs_init_blocks、yaffs_init_tnodes_and_objs和yaffs_create_initial_dir,重复申请3-5项内存堆空间。

  11. yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs2_scan_backwards

    这个函数中是,n_blocks * sizeof(struct yaffs_block_index)

    yaffs_block_index是一个结构体有2个32为变量,就是8字节

    4096*8=0x8000

    block_index =
    		kmalloc(n_blocks * sizeof(struct yaffs_block_index), GFP_NOFS);
    

    之后的yaffs_release_temp_buffer函数释放了第一项创建的临时缓冲区(部分)

    那么这第11项,是先要求0x8000的,后面释放的空间并不能减少总体需求(如果是先释放,再要求申请0x8000则可以减少总体需求)。

加上之前的0x27370,一共是0x2F370,

最终测试0x2F300也可以,因为起始点没有确定(0x2000),当然不排除中间还可能有其他的分配。这些分配的意义也似懂非懂…,而且实际工程中和其他功能搭配时也需要更多的内存堆,比如lwip网络。省事情的话其实直接调大就行了。

刚开始调的时候这些问题还是挺麻烦的,因为yaffs本身的跟踪打印(tracing)和报错处理会很重复,某个错误可能会有许多问题造成的,需要在额外添加打印信息定位。

如果更加理解yaffs的分配内存原理,就可以更快定位问题并解决,添加打印信息来定位问题,也行也很快,哪一个更好的呢?如果官方提供指导文件更详细更好,自己看代码理解无疑会很耗费时间。

驱动

NAND驱动采用的是ti-startware的,略作修改,是能支持yaffs2的oob存储区,整个spare区域分配的策略是:坏块检测位(BBM,2位)+oob(长度oob_len)+ECC,后两个的位置也可以改变。如果要改变BBM,那么对应的坏块检测和标记函数也要更改。

有两个问题:

  1. ti-startware的NAND驱动不识别地址为空(NULL)的数据缓存RX_Buffer和TX_Buffer数组,传进来空的,NAND读写命令会直接卡死。此时yaffs不会报错,直接停止运行,需要添加识别是否为空数组,如果为空,则就读写数据,仅读写ECC和oob
  2. ti-startware的NAND驱动对未写入的块进行读操作时(根据NAND Flash的特性,这些块的内容全是0xFF),会出现返回是ECC报错且无法纠正。但是,yaffs中需要读未写入的块,那么解决办法有两个,一是采用yaffs的ECC校验方法,它可以识别;二是在出现返回是“ECC报错且无法纠正”时,附加识别是否是块内容全部为0xFF,来确定是否要更改返回值。

还有就是,我依旧觉得单单看源码是不能够看出这种问题的,或者可能是需要看的特别细致,上下文全部参透才能够完全掌握它的能力,它能完成什么,不能完成什么。官方完整的注意事项也是需要的。

YAFFS2(Yet Another Flash File System 2)是一种针对嵌入式系统设计的开源嵌入式文件系统。移植YAFFS2单片机上涉及以下步骤: 1. 了解单片机的硬件和存储器:单片机的硬件架构和存储器类型对文件系统的移植至关重要。需要了解单片机的处理器类型、存储器大小和类型,例如闪存或EEPROM等。 2. 下载和配置YAFFS2:从官方网站或源代码仓库下载YAFFS2的最新版本。根据单片机的硬件和存储器特性,修改YAFFS2的配置文件,例如设置闪存大小、页大小和块大小等。 3. 移植文件操作接口:单片机的操作系统可能不支持标准的文件操作接口,需要根据单片机的特性实现文件系统的操作接口。这些接口包括读写文件、创建删除文件、目录遍历等。 4. 适配存储器驱动程序:YAFFS2需要针对单片机的存储器类型进行适配。根据单片机的存储器接口和特性,修改YAFFS2的底层驱动程序,确保与单片机的存储器正常交互。 5. 编译和链接YAFFS2:使用适当的交叉编译工具链,将YAFFS2编译为单片机可执行的格式。确保编译选项与单片机的体系结构和操作系统匹配。 6. 测试和调试:将移植后的YAFFS2文件系统部署到单片机上,并进行详细的功能测试和性能测试。通过检查文件系统的功能和性能是否符合预期,进行必要的调试和优化。 总之,移植YAFFS2单片机需要对单片机硬件和存储器有深入的了解,并进行文件操作接口适配和存储器驱动程序适配。合理配置和编译YAFFS2,最后进行测试和调试,确保其正常运行。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值