博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在应用程序中实现对NandFlash的操作
阅读量:6091 次
发布时间:2019-06-20

本文共 20098 字,大约阅读时间需要 66 分钟。

以 为例:

下面是它的一些物理参数:

图一

 

 

 

 

 

 

图二

 

 

 

 

 

 

 

 

 

 

 

图三

 

 

 

 

 

 

 

 

图四

 

 

 

 

 

 

 

 

 

 

 

 

 

图五

 

 

 

图6-0

图6-1

 

说明一下,在图6-1中中间的那个布局表可以看做是实际的NandFlash一页数据的布局,其中Data区域用于存放有效的数据,也就是我们可以通过类似read、write、pread、pwrite可以访问的区域,那每页中的64字节的OOB区域是无法通过前面的几个函数访问的,他们会自动跳过OOB区域,访问OOB区域需要借助特殊的命令。

简单说明一下:Data A(512B)对应的ECC校验码存放在ECC for Data A(4 byte)中,OOB A (8byte) 对应的ECC校验码存放在紧接着的下一个ECC for Data A(4 byte)中,虽然用4字节存放ECC,但是对于本例,ECC只占3个字节。在实际使用中如果解决方案中用不到OOB A/B/C/D,可以不用管他们对应的ECC,只需要关心Data区域对应的ECC。如果使能了硬件ECC,硬件会自动把计算生成的ECC写到OOB中。可以参考 。

读NandFlash需要按页读,即一次读一页;写NandFlash需要按页写,即每次写一页;擦除NandFlash需要按块擦,即每次要擦除一块。

对与NandFlash等块设备的访问操作,mtd-utils工具集中提供了非常好的支持(可以到进行了解),要使用mtd-utils工具集首先需要搞到mtd-utils的源码,并且使用目标设备上的交叉工具编译链进行编译,具体方法可以参考:,其中介绍了如何生成可以再目标板上运行的mtd-utils工具。关于mtd-utils工具的使用可以参考: 其中介绍了mtd-utils中常用的工具。

我们可以参考mtd-utils中工具的实现,从而完成在自己的应用程序中实现对NandFlash的操作。常用的命令如下:

#define MEMGETINFO        _IOR('M', 1, struct mtd_info_user)

#define MEMERASE        _IOW('M', 2, struct erase_info_user)
#define MEMWRITEOOB        _IOWR('M', 3, struct mtd_oob_buf)
#define MEMREADOOB        _IOWR('M', 4, struct mtd_oob_buf)
#define MEMLOCK            _IOW('M', 5, struct erase_info_user)
#define MEMUNLOCK        _IOW('M', 6, struct erase_info_user)
#define MEMGETREGIONCOUNT    _IOR('M', 7, int)
#define MEMGETREGIONINFO    _IOWR('M', 8, struct region_info_user)
#define MEMSETOOBSEL        _IOW('M', 9, struct nand_oobinfo)
#define MEMGETOOBSEL        _IOR('M', 10, struct nand_oobinfo)
#define MEMGETBADBLOCK        _IOW('M', 11, __kernel_loff_t)
#define MEMSETBADBLOCK        _IOW('M', 12, __kernel_loff_t)
#define OTPSELECT        _IOR('M', 13, int)
#define OTPGETREGIONCOUNT    _IOW('M', 14, int)
#define OTPGETREGIONINFO    _IOW('M', 15, struct otp_info)
#define OTPLOCK            _IOR('M', 16, struct otp_info)
#define ECCGETLAYOUT        _IOR('M', 17, struct nand_ecclayout_user)
#define ECCGETSTATS        _IOR('M', 18, struct mtd_ecc_stats)
#define MTDFILEMODE        _IO('M', 19)
#define MEMERASE64        _IOW('M', 20, struct erase_info_user64)
#define MEMWRITEOOB64        _IOWR('M', 21, struct mtd_oob_buf64)
#define MEMREADOOB64        _IOWR('M', 22, struct mtd_oob_buf64)
#define MEMISLOCKED        _IOR('M', 23, struct erase_info_user)

 

打开设备

这里需要注意的是,打开的设备结点是/dev/mtd?,而不是/dec/mtdblock?,原因可以参考:

,其中介绍了mtd与mtdblock的区别。

fd = open ("/dev/mtd0", O_SYNC | O_RDWR);

 

获取设备信息

#include <linux/types.h>
structmtd_info_user { __u8 type; __u32 flags; __u32 size; // Total size of the MTD
__u32 erasesize;    __u32 writesize;    __u32 oobsize;// Amount of OOB data per block (e.g. 16)    /*
The below two fields are obsolete and broken, do not use them * (TODO: remove at some point) */
__u32 ecctype;    __u32 eccsize;};struct mtd_info_user mtd;
ioctl(fd, MEMGETINFO,&mtd) ;
 
其中type可以用来区分是NorFlash还是NandFlash。

擦除NandFlash

#include 
#include
struct erase_info_user { __u32 start; __u32 length;};typedef struct erase_info_user erase_info_t;erase_info_t erase;int isNAND, bbtest = 1;erase.length = DevInfo->erasesize;// erase.length 表示的是擦除大小,也就是一块的大小,如128KB// DevInfo->size 为某个/dev/mtdx的大小// erasse.start应该是按块对齐递增isNAND = (DevInfo->typenum== MTD_NANDFLASH) ? 1 : 0;for (erase.start = 0; erase.start < DevInfo->size; erase.start += DevInfo->erasesize) { if (bbtest) { loff_t offset = erase.start; int ret = ioctl(DevInfo->fd, MEMGETBADBLOCK, &offset); //判断是不是坏块 if (ret > 0) { if (!quiet) DEBUG ("\nSkipping bad block at 0x%08x\n", erase.start); continue;//发现是坏块,应该跳过 } else if (ret < 0) { if (errno == EOPNOTSUPP) { bbtest = 0; if (isNAND) { fprintf(stderr, "%s: Bad block check not available\n", DevInfo->dir); return 1; } } else { fprintf(stderr, "\n%s: MTD get bad block failed: %s\n", DevInfo->dir, strerror(errno)); return 1; } } } if (!quiet) { fprintf(stderr, "\rErasing %d Kibyte @ %x -- %2llu %% complete.", \ (DevInfo->erasesize) / 1024, erase.start, (unsigned long long) erase.start * 100 / (DevInfo->size)); } if (ioctl(DevInfo->fd, MEMERASE, &erase) != 0) //执行擦除操作 { fprintf(stderr, "\n%s: MTD Erase failure: %s\n", DevInfo->dir,strerror(errno)); continue; }}

写NandFlash

这里分为写数据区和写OOB区

写数据区,对于本例一次要写一页,也就是2KB,写OOB区,对于本例可以操作的只有32字节,剩下的32字节用于存放ECC。

struct mtd_oob_buf {    __u32 start;    __u32 length;    unsigned char *ptr;};int nandwrite(DeviceInfo* meminfo){    int imglen = 0, pagelen;    bool baderaseblock = false;    int blockstart = -1;    loff_t offs;    int ret, readlen;    unsigned char tmp_oob[32];//OOB A/B/C/D,一共32字节    struct mtd_oob_buf OOB_INFO ;    sourceaddr = meminfo->head->file_offset;   //要读的部分在镜像文件中的偏移量    sourcelen = meminfo->head->size;           //要读的部分的大小    int num_to_read = 0;    OOB_INFO.start = 0;    OOB_INFO.length = meminfo->head->oob_usr_length;   //32字节,用户可以访问的OOB的大小,也就是OOB A/B/C/D    OOB_INFO.ptr = tmp_oob;    pagelen = meminfo->writesize; // 2KB    imglen = sourcelen;           // 镜像文件的长度    mtdoffset = meminfo->head->flash_offset;  //要写的部分在/dev/mtdx中的偏移量,以字节为单位        /* Determine if we are reading from standard input or from a file. */    if (0 == sourceaddr) {        DEBUG("Have no sourceaddr return ****************************\n");        return 1;    }    // Check, if length fits into device    if ( ((imglen / pagelen) * meminfo->writesize) > (meminfo->size - mtdoffset)) {        fprintf (stderr, "Image %d bytes, NAND page %d bytes, OOB area %u bytes, device size %u bytes\n",                imglen, pagelen, meminfo->writesize, meminfo->size);        perror ("Input file does not fit into device");        goto closeall;    }    while ((imglen>0) && (mtdoffset < meminfo->size))    {        //blockstart 将来存放的是正在写的那块的起始地址,并且是块对齐的        //mtdoffset 表示的是在某个mtd设备中的整体偏移量,可以按块递增,也可以按页递增        //设置blockstart的目的是:        // 假如检测到一个好的块,开始进行写操作,但是在写的过程中发生了写错误,可以认为这块已经        //是坏块了,需要重新再找一个好的块,然后重新写入之前的数据,因此需要知道刚才那个坏块的起始地址        // mtdoffset & (~meminfo->erasesize + 1) 这种获取块起始地址的算法值得借鉴        while (blockstart != (mtdoffset & (~meminfo->erasesize + 1)))        {            blockstart = mtdoffset & (~meminfo->erasesize + 1);            offs = blockstart;            baderaseblock = false;            if (!quiet)            {                fprintf (stderr, "\rWriting data to block %d at offset 0x%x", \                        blockstart / meminfo->erasesize, blockstart);            }            /* Check all the blocks in an erase block for bad blocks */            // meminfo->fd 是某个/dev/mtdx的文件描述符            do {                    if ((ret = ioctl(meminfo->fd, MEMGETBADBLOCK, &offs)) < 0)                    {                        perror("ioctl(MEMGETBADBLOCK)");                        goto closeall;                    }                    if (ret == 1)                    {                        baderaseblock = true;                        if (!quiet)                        {                            fprintf (stderr, "Bad block at %x block(s) "                                    "from %x will be skipped\n",                                    (int) offs, blockstart);                        }                    }                    if (baderaseblock)                    {                        mtdoffset = blockstart + meminfo->erasesize;                    }                    offs +=  meminfo->erasesize;                } while ( offs < blockstart + meminfo->erasesize );        }        readlen = meminfo->writesize;  // 2KB        if (0 != sourceaddr)        {            if((meminfo->head->imageType == YAFFS) || (meminfo->head->imageType == OOB_RAW))            {                writeoob = true;            }            else            {                writeoob = false;            }             memset(writebuf, 0xff, sizeof(writebuf));                        if(imglen < readlen)            {                num_to_read = imglen;            }            else            {                num_to_read = readlen;            }            // 从镜像文件中偏移量为sourceaddr处读取num_to_read个字节到writebuf中            // ALLIMAGEFD 为镜像文件的文件描述符            if(pread(ALLIMAGEFD, writebuf, num_to_read, sourceaddr) < 0)             {                perror("fail to pread\n");                return -1;            }            sourceaddr += num_to_read;            if(writeoob)            {                memset(tmp_oob, 0xff , OOB_FREE_MAX);                // 从镜像文件中偏移量为sourceaddr+meminfo->head->oob_usr_offset处读取meminfo->head->oob_usr_length个字节到tmp_oob中,其中meminfo->head->oob_usr_offset是OOB A相对与OOB区域的偏移量,meminfo->head->oob_usr_length 在本例中为32字节                if(pread(ALLIMAGEFD, tmp_oob, meminfo->head->oob_usr_length, sourceaddr+meminfo->head->oob_usr_offset) < 0)                {                    perror("fail to pread\n");                    return -1;                }                sourceaddr += meminfo->oobsize;            }        }    if(-1 == pwrite(meminfo->fd, writebuf, meminfo->writesize, mtdoffset)) //写NandFlash    {        /*            下面这段程序所完成的就是刚才所说的在写之前检测到是好块,但是在写的过程出现了写错误,            这个时候需要完成?如下流程:            1、计算已经在当前块上写入多少内容,比如下面的rewind_blocks是为了计算在当前块上已经写了多少页,                这里需要注意的是;rewind_bytes又加了一个readlen,也就是一页的大小,目的是保证sourceaddr的可以                回退到刚开始写当前块是sourceaddr的值,可以看到在上面的程序中每次将要写的内容读到writebuf后,                sourceaddr已经进行了自增操作,并没有保证刚读到writebuf中的内容可以成功写入。                但是mtdoffset进行自增的前提是偏移量为mtdoffset的页写成功。其实程序可以这么改进:                将sourceaddr的自增操作跟mtdoffset的自增操作放在一起,此时rewind_bytes就不需要再加readlen了。                对于oob,一般只有yaffs镜像中有oob,而向cramfs、jffs2、ubifs这没有,如果有oob也需要对                rewind_byte进行处理            2、对当前块进行擦除            3、如果需要进行坏块标记,则将当前块标记为坏块            4、将mtdoffset指向当前块的下一块起始地址            5、恢复imglen为刚开始处理当前块时的值,由于imglen也是保证当前页成功写入后才自减,所以只需要加                上rewind_blocks即可        */        int rewind_blocks;        off_t rewind_bytes;        erase_info_t erase;        perror("ioctl(MEMEWRITEPAGE)");        /* Must rewind to blockstart if we can */        rewind_blocks = (mtdoffset - blockstart) / meminfo->writesize; /* Not including the one we just attempted */        rewind_bytes = (rewind_blocks * meminfo->writesize) + readlen;        if (writeoob)        {            rewind_bytes += (rewind_blocks + 1) * meminfo->oobsize;        }        sourceaddr -= rewind_bytes;        erase.start = blockstart;        erase.length = meminfo->erasesize;        fprintf(stderr, "Erasing failed write from %08lx-%08lx\n",                (long)erase.start, (long)erase.start+erase.length-1);        if (ioctl(meminfo->fd, MEMERASE, &erase) != 0)        {            perror("MEMERASE");            goto closeall;        }        if (markbad)        {            loff_t bad_addr = mtdoffset & (~meminfo->erasesize + 1);            fprintf(stderr, "Marking block at %08lx bad\n", (long)bad_addr);            if (ioctl(meminfo->fd, MEMSETBADBLOCK, &bad_addr)) {                perror("MEMSETBADBLOCK");                /* But continue anyway */            }        }        mtdoffset = blockstart + meminfo->erasesize;        imglen += rewind_blocks * meminfo->writesize;        if(writeoob)        {            imglen += rewind_blocks * meminfo->oobsize;        }        continue;    }        imglen -= readlen;        if(writeoob)        {            imglen -= meminfo->oobsize;            OOB_INFO.start = mtdoffset;            if (ioctl(meminfo->fd, MEMWRITEOOB, &OOB_INFO))            {                 perror("fail to ioctl");            }        }        mtdoffset += meminfo->writesize;    }closeall:    if ((imglen > 0))    {        perror ("Data was only partially written due to error\n");        exit (EXIT_FAILURE);    }    return EXIT_SUCCESS;}

 

对于写NandFlash,有的设备支持一次性把data和oob一块写进去。代码如下:

struct mtd_info_user {    uint8_t type;    uint32_t flags;    uint32_t size;     // Total size of the MTD    uint32_t erasesize;    uint32_t writesize;    uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)    /* The below two fields are obsolete and broken, do not use them     * (TODO: remove at some point) */    uint32_t ecctype;    uint32_t eccsize;};struct mtd_epage_buf {    unsigned long long  start;    unsigned long       data_len;    unsigned long       oob_len;    unsigned char     * data_ptr;    unsigned char     * oob_ptr; };

#define MEMEWRITEPAGE _IOWR('M', 23, struct mtd_epage_buf)

 

#define MAX_PAGE_SIZE 8192

#define MAX_OOB_SIZE 512

 

/*

* Buffer array used for writing data
*/
unsigned char writebuf[MAX_PAGE_SIZE];
char oobbuf[MAX_OOB_SIZE];

int nandwrite(int argc, char * const argv[]){    /*int cnt = 0;*/    int fd = -1;    /*int ifd = -1;*/    int imglen = 0, pagelen;    bool baderaseblock = false;    int blockstart = -1;    struct mtd_info_user meminfo;    struct mtd_epage_buf eccbuf;    loff_t offs;    int ret, readlen;    /*process_options(argc, argv);*/    mtdoffset = 0; /* reinit */    erase_buffer(oobbuf, sizeof(oobbuf));    if (pad && writeoob) {        fprintf(stderr, "Can't pad when oob data is present.\n");        exit (EXIT_FAILURE);    }    /* Open the device */    if ((fd = open(mtd_device, O_RDWR)) == -1) {        perror(mtd_device);        exit (EXIT_FAILURE);    }    /* Fill in MTD device capability structure */    if (ioctl(fd, MEMGETINFO, &meminfo) != 0) {        perror("MEMGETINFO");        close(fd);        exit (EXIT_FAILURE);    }    /* Determine if we are reading from standard input or from a file. */    if (NULL == sourceaddr) {        DEBUG("Have no sourceaddr return ****************************\n");        return 0;    }    pagelen = meminfo.writesize + ((writeoob) ? meminfo.oobsize : 0);    /*     * For the standard input case, the input size is merely an     * invariant placeholder and is set to the write page     * size. Otherwise, just use the input file size.     *     * TODO: Add support for the -l,--length=length option (see     * previous discussion by Tommi Airikka 
at *
*/ imglen = sourcelen; // Check, if file is page-aligned if ((!pad) && ((imglen % pagelen) != 0)) { fprintf (stderr, "Input file is not page-aligned. Use the padding " "option.\n"); goto closeall; } // Check, if length fits into device if ( ((imglen / pagelen) * meminfo.writesize) > (meminfo.size - mtdoffset)) { fprintf (stderr, "Image %d bytes, NAND page %d bytes, OOB area %u bytes, device size %u bytes\n", imglen, pagelen, meminfo.writesize, meminfo.size); perror ("Input file does not fit into device"); goto closeall; } const int allSizeConst = imglen; /*DEBUG("file:%s, line:%d, imglen:%d, mtdoffset:%d, meminfo.size:%d\n", __FILE__, __LINE__, imglen, mtdoffset, meminfo.size);*/ /* * Get data from input and write to the device while there is * still input to read and we are still within the device * bounds. Note that in the case of standard input, the input * length is simply a quasi-boolean flag whose values are page * length or zero. */ while (imglen && (mtdoffset < meminfo.size)) { // new eraseblock , check for bad block(s) // Stay in the loop to be sure if the mtdoffset changes because // of a bad block, that the next block that will be written to // is also checked. Thus avoiding errors if the block(s) after the // skipped block(s) is also bad (number of blocks depending on // the blockalign while (blockstart != (mtdoffset & (~meminfo.erasesize + 1))) { blockstart = mtdoffset & (~meminfo.erasesize + 1); offs = blockstart; baderaseblock = false; if (!quiet) fprintf (stdout, "Writing data to block %d at offset 0x%x\n", blockstart / meminfo.erasesize, blockstart); /* Check all the blocks in an erase block for bad blocks */ do { if ((ret = ioctl(fd, MEMGETBADBLOCK, &offs)) < 0) { perror("ioctl(MEMGETBADBLOCK)"); goto closeall; } if (ret == 1) { baderaseblock = true; if (!quiet) fprintf (stderr, "Bad block at %x block(s) " "from %x will be skipped\n", (int) offs, blockstart); } if (baderaseblock) { mtdoffset = blockstart + meminfo.erasesize; } offs += meminfo.erasesize; } while ( offs < blockstart + meminfo.erasesize ); } readlen = meminfo.writesize; if (NULL != sourceaddr) { if (pad && (imglen < readlen)) { readlen = imglen; erase_buffer(writebuf + readlen, meminfo.writesize - readlen); } memcpy(writebuf, sourceaddr, readlen); sourceaddr += readlen; } if (writeoob) { memcpy(oobbuf, sourceaddr, meminfo.oobsize); sourceaddr += meminfo.oobsize; } eccbuf.data_ptr = writebuf; eccbuf.data_len = meminfo.writesize; eccbuf.oob_ptr = oobbuf; eccbuf.oob_len = meminfo.oobsize; eccbuf.start = mtdoffset; if (ioctl(fd, MEMEWRITEPAGE, &eccbuf) != 0) { int rewind_blocks; off_t rewind_bytes; erase_info_t erase; perror("ioctl(MEMEWRITEPAGE)"); /* Must rewind to blockstart if we can */ rewind_blocks = (mtdoffset - blockstart) / meminfo.writesize; /* Not including the one we just attempted */ rewind_bytes = (rewind_blocks * meminfo.writesize) + readlen; if (writeoob) rewind_bytes += (rewind_blocks + 1) * meminfo.oobsize; sourceaddr -= rewind_bytes; erase.start = blockstart; erase.length = meminfo.erasesize; fprintf(stderr, "Erasing failed write from %08lx-%08lx\n", (long)erase.start, (long)erase.start+erase.length-1); if (ioctl(fd, MEMERASE, &erase) != 0) { perror("MEMERASE"); goto closeall; } if (markbad) { loff_t bad_addr = mtdoffset & (~meminfo.erasesize + 1); fprintf(stderr, "Marking block at %08lx bad\n", (long)bad_addr); if (ioctl(fd, MEMSETBADBLOCK, &bad_addr)) { perror("MEMSETBADBLOCK"); /* But continue anyway */ } } mtdoffset = blockstart + meminfo.erasesize; imglen += rewind_blocks * meminfo.writesize; continue; } imglen -= (readlen + meminfo.oobsize); mtdoffset += meminfo.writesize; }closeall: close(fd); if ((imglen > 0)) { perror ("Data was only partially written due to error\n"); exit (EXIT_FAILURE); } /* Return happy */ return EXIT_SUCCESS;}

 

 

读OOB

读OOB跟写OOB类似,只不过使用的命令是MEMREADOOB。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#define N 32#define OFS (0)#define block_size (128*1024)#define page_size (2*1024)int main(int argc, const char *argv[]){ int fd; int i, j; unsigned char oob_data[32] = { 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff }; unsigned char oobbuf[N]; struct mtd_oob_buf oob = { 0, N, oobbuf}; struct mtd_oob_buf my_oob = { 0, N, oob_data}; fd = open("/dev/mtd0", O_RDWR); if(fd < 0) { perror("fail to open\n"); exit(-1); } if(ioctl(fd, MEMWRITEOOB, &my_oob)) { perror("fail to ioctl"); exit(-1); } memset(oobbuf, 0, sizeof(oobbuf)); oob.start = OFS; if (ioctl(fd, MEMREADOOB, &oob)) { perror("fail to ioctl"); exit(-1); } for(i=0; i

 

以上只是本人在工作中遇到的,仅供参考。

转载地址:http://sclwa.baihongyu.com/

你可能感兴趣的文章
需要学的东西
查看>>
Linux 获取文件夹下的所有文件
查看>>
对 Sea.js 进行配置(一) seajs.config
查看>>
第六周
查看>>
解释一下 P/NP/NP-Complete/NP-Hard 等问题
查看>>
javafx for android or ios ?
查看>>
微软职位内部推荐-Senior Software Engineer II-Sharepoint
查看>>
sql 字符串操作
查看>>
【转】Android布局优化之ViewStub
查看>>
网络安全管理技术作业-SNMP实验报告
查看>>
根据Uri获取文件的绝对路径
查看>>
Flutter 插件开发:以微信SDK为例
查看>>
.NET[C#]中NullReferenceException(未将对象引用到实例)是什么问题?如何修复处理?...
查看>>
边缘控制平面Ambassador全解读
查看>>
Windows Phone 7 利用计时器DispatcherTimer创建时钟
查看>>
程序员最喜爱的12个Android应用开发框架二(转)
查看>>
vim学习与理解
查看>>
DIRECTSHOW在VS2005中PVOID64问题和配置问题
查看>>
MapReduce的模式,算法以及用例
查看>>
《Advanced Linux Programming》读书笔记(1)
查看>>