Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1038161
  • 博文数量: 123
  • 博客积分: 1029
  • 博客等级: 准尉
  • 技术积分: 1167
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-13 00:15
文章分类

全部博文(123)

文章存档

2014年(24)

2013年(45)

2012年(54)

分类: LINUX

2013-11-19 20:51:11

这段时间一直在做NAND Flash相关的一些调试。发现之前很少写block层相关的总结。于是抽出时间将block层相关的东西总结一下。这一节分析一下MTD驱动中NAND Flash的硬件底层驱动的实现。

 

MTD底层驱动需要给MTD层主要提供三个常用的接口,分别是:

Writereaderase。对于这三个接口,你可以选择内核提供的标准实现,也可自己驱动函数来实现它。这里主要分析一下内核中的标准实现。

 

首先分析Write函数,在内核中的标准实现为nand_write,函数调用关系如下图所示。

实际上关于Nand Flash写操作的主要逻辑都是在中间nand_do_write_ops函数中来实现的,这个函数会判断传入的buf是不是页对齐,并将buf中的内容分为页对齐的一个一个部分。Buf中不对齐的部分存在于buf头和buf尾两个位置,如下图所示。这两个部分的内容需要做特殊处理,处理的方法就是分配一个page大小的缓冲区,将buffer的内容全部初始化为ff,将buf头不对齐的部分拷贝到缓冲区中,再将页大小的这个缓冲区写入到nand flash。这里用到了一个技巧,就是像nand flash中写1不改变原来存储颗粒的状态,原来是1还是1原来是0还是0。同理buf尾部不对齐的数据也要做相同的处理。

 

 /** 
 * nand_do_write_ops - [Internal] NAND write with ECC 
 * @mtd:    MTD device structure 
 * @to:        offset to write to 
 * @ops:    oob operations description structure 
 * 
 * NAND write with ECC 
 */ 
static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, 
                             struct mtd_oob_ops *ops) 

    int chipnr, realpage, page, blockmask, column; 
    struct nand_chip *chip = mtd->priv; 
    uint32_t writelen = ops->len; 
    uint8_t *oob = ops->oobbuf; 
    uint8_t *buf = ops->datbuf; 
    int ret, subpage; 
 
    ops->retlen = 0
    if (!writelen) 
        return 0
 
    /* reject writes, which are not page aligned */ 
    if (NOTALIGNED(to) || NOTALIGNED(ops->len)) 
    { 
        printk(KERN_NOTICE "nand_write: " 
               "Attempt to write not page aligned data\n"); 
        return -EINVAL; 
    } 
 
    column = to & (mtd->writesize - 1); 
    subpage = column || (writelen & (mtd->writesize - 1)); 
 
    if (subpage && oob) 
        return -EINVAL; 
 
    chipnr = (int)(to >> chip->chip_shift); 
    chip->select_chip(mtd, chipnr); 
 
    /* Check, if it is write protected */ 
    if (nand_check_wp(mtd)) 
        return -EIO; 
 
    realpage = (int)(to >> chip->page_shift); 
    page = realpage & chip->pagemask; 
    blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1
 
    /* Invalidate the page cache, when we write to the cached page */ 
    if (to <= (chip->pagebuf << chip->page_shift) && 
            (chip->pagebuf << chip->page_shift) < (to + ops->len)) 
        chip->pagebuf = -1
 
    /* If we're not given explicit OOB data, let it be 0xFF */ 
    if (likely(!oob)) 
        memset(chip->oob_poi, 0xff, mtd->oobsize); 
 
    while(1
    { 
        int bytes = mtd->writesize; 
        int cached = writelen > bytes && page != blockmask; 
        uint8_t *wbuf = buf; 
 
        /* Partial page write ? */ 
        /*如果是buf开始部分和结尾部分不是整个page的数据,就必须 
          制造一个整个的page,将这部分数据填入合适的位置,并将 
          其它部分数据全部设置为ff*/ 
        if (unlikely(column || writelen < (mtd->writesize - 1))) 
        { 
            cached = 0
            bytes = min_t(int, bytes - column, (int) writelen); 
            chip->pagebuf = -1
            memset(chip->buffers->databuf, 0xff, mtd->writesize); 
            memcpy(&chip->buffers->databuf[column], buf, bytes); 
            wbuf = chip->buffers->databuf; 
        } 
 
        if (unlikely(oob)) 
            oob = nand_fill_oob(chip, oob, ops); 
        /*实际的写操作*/ 
        ret = chip->write_page(mtd, chip, wbuf, page, cached, 
                               (ops->mode == MTD_OOB_RAW)); 
        if (ret) 
            break
 
        writelen -= bytes; 
        if (!writelen) 
            break
         
        /*只有buf的头部colnum不为0,自增buf和写入page数目*/ 
        column = 0
        buf += bytes; 
        realpage++; 
 
        page = realpage & chip->pagemask; 
        /* Check, if we cross a chip boundary */ 
        if (!page) 
        { 
            chipnr++; 
            chip->select_chip(mtd, -1); 
            chip->select_chip(mtd, chipnr); 
        } 
    } 
 
    ops->retlen = ops->len - writelen; 
    if (unlikely(oob)) 
        ops->oobretlen = ops->ooblen; 
    return ret; 

 

接下来分析nand_read函数

Nand_read函数会调用到nand_do_read_ops函数,后者完成了读Nand Flash操作的主要逻辑。这个过程主要是判断读取数据是不是page对齐的。要读取的这段数据在位置往往在开始和结尾部分不是对齐的。对于这种不是page对齐的情况,函数首先分配一个page大小的缓冲区,一次把一个page读取上来,再将实际要读取的page中的部分拷贝出来。

 /** 
 * nand_do_read_ops - [Internal] Read data with ECC 
 * 
 * @mtd:    MTD device structure 
 * @from:    offset to read from 
 * @ops:    oob ops structure 
 * 
 * Internal function. Called with chip held. 
 */ 
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, 
                            struct mtd_oob_ops *ops) 

    int chipnr, page, realpage, col, bytes, aligned; 
    struct nand_chip *chip = mtd->priv; 
    struct mtd_ecc_stats stats; 
    int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1
    int sndcmd = 1
    int ret = 0
    uint32_t readlen = ops->len; 
    uint32_t oobreadlen = ops->ooblen; 
    uint8_t *bufpoi, *oob, *buf; 
 
    stats = mtd->ecc_stats; 
 
    chipnr = (int)(from >> chip->chip_shift); 
    chip->select_chip(mtd, chipnr); 
 
    realpage = (int)(from >> chip->page_shift); 
    page = realpage & chip->pagemask; 
 
    col = (int)(from & (mtd->writesize - 1)); 
 
    buf = ops->datbuf; 
    oob = ops->oobbuf; 
 
    while(1
    { 
        bytes = min(mtd->writesize - col, readlen); 
        aligned = (bytes == mtd->writesize); 
 
        /* Is the current page in the buffer ? */ 
        if (realpage != chip->pagebuf || oob) 
        { 
            /*如果不是page对齐的读取,使用额外的databuf来读取整个page*/ 
            bufpoi = aligned ? buf : chip->buffers->databuf; 
 
            if (likely(sndcmd)) 
            { 
                chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); 
                sndcmd = 0
            } 
 
            /* Now read the page into the buffer */ 
            if (unlikely(ops->mode == MTD_OOB_RAW)) 
                ret = chip->ecc.read_page_raw(mtd, chip, bufpoi); 
            else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob) 
                ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi); 
            else 
                ret = chip->ecc.read_page(mtd, chip, bufpoi); 
            if (ret < 0
                break
 
            /* Transfer not aligned data */ 
            /*非对齐的读取需要将读出的整页内容*/ 
            if (!aligned) 
            { 
                if (!NAND_SUBPAGE_READ(chip) && !oob) 
                    chip->pagebuf = realpage; 
                memcpy(buf, chip->buffers->databuf + col, bytes); 
            } 
            /*读入buf后,重新改变buf位置*/ 
            buf += bytes; 
 
            if (unlikely(oob)) 
            { 
                /* Raw mode does data:oob:data:oob */ 
                if (ops->mode != MTD_OOB_RAW) 
                { 
                    int toread = min(oobreadlen, 
                                     chip->ecc.layout->oobavail); 
                    if (toread) 
                    { 
                        oob = nand_transfer_oob(chip, 
                                                oob, ops, toread); 
                        oobreadlen -= toread; 
                    } 
                } 
                else 
                    buf = nand_transfer_oob(chip, 
                                            buf, ops, mtd->oobsize); 
            } 
 
            if (!(chip->options & NAND_NO_READRDY)) 
            { 
                /* 
                 * Apply delay or wait for ready/busy pin. Do 
                 * this before the AUTOINCR check, so no 
                 * problems arise if a chip which does auto 
                 * increment is marked as NOAUTOINCR by the 
                 * board driver. 
                 */ 
                if (!chip->dev_ready) 
                    udelay(chip->chip_delay); 
                else 
                    nand_wait_ready(mtd); 
            } 
        } 
        else 
        { 
            memcpy(buf, chip->buffers->databuf + col, bytes); 
            buf += bytes; 
        } 
         
        /* 减去已经读取的长度*/ 
        readlen -= bytes; 
 
        if (!readlen) 
            break
 
        /* For subsequent reads align to page boundary. */ 
        col = 0
        /* Increment page address */ 
        realpage++; 
 
        page = realpage & chip->pagemask; 
        /* Check, if we cross a chip boundary */ 
        if (!page) 
        { 
            chipnr++; 
            chip->select_chip(mtd, -1); 
            chip->select_chip(mtd, chipnr); 
        } 
 
        /* Check, if the chip supports auto page increment 
         * or if we have hit a block boundary. 
         */ 
        if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck)) 
            sndcmd = 1
    } 
 
    ops->retlen = ops->len - (size_t) readlen; 
    if (oob) 
        ops->oobretlen = ops->ooblen - oobreadlen; 
 
    if (ret) 
        return ret; 
 
    if (mtd->ecc_stats.failed - stats.failed) 
        return -EBADMSG; 
 
    return  mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0

 

Nand_erase函数实现比较简单,主要是按block大小进行擦除。这里不再进行仔细分析。

阅读(1479) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~