• u-boot的nand驱动写过程分析


    从命令说起,在u-boot输入下列命令:

    nand write 40008000 0 20000
    命令的意思是将内存0x40008000开始的部分写入nand,从nand地址0开始写,写入长度是0x200000

    回车之后,代码如何运行呢?命令的输入,执行之前都已经分析过了,初始化过程也分析了

    请参阅:

    http://blog.csdn.net/andy_wsj/article/details/9335755

    http://blog.csdn.net/andy_wsj/article/details/9339247

    http://blog.csdn.net/andy_wsj/article/details/8614905


    执行这条命令,将调用u-boot-sunxi-sunxicommoncmd_nand.c内的函数do_nand。

    int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])


    nand write 40008000 0 20000在参数argv中,而且
    argv[0] = "nand"
    argv[1] = "write"
    argv[2] = "40008000"
    argv[3] = "0"
    argv[4] = "20000"
    argc = 5 参数的个数


    分析一下do_nand函数的片段,篇幅关系,只保留写操作部分:
    nt do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
    {
    int i, ret = 0;
    ulong addr;
    loff_t off, size;
    char *cmd, *s;
    nand_info_t *nand;
    #ifdef CONFIG_SYS_NAND_QUIET
    int quiet = CONFIG_SYS_NAND_QUIET;
    #else
    int quiet = 0;
    #endif
    const char *quiet_str = getenv("quiet");
    int dev = nand_curr_device;                 //当前NAND芯片,如果板上有多个芯片,则不能直接赋值,大部分板子都是一个NAND
    int repeat = flag & CMD_FLAG_REPEAT;


    /* at least two arguments please */
    if (argc < 2)
    goto usage;


    if (quiet_str)
    quiet = simple_strtoul(quiet_str, NULL, 0) != 0;


    cmd = argv[1];   //cmd就指向命令“write”,


       ........判断是什么命令,多余判断删除了..............


    /* The following commands operate on the current device, unless
    * overridden by a partition specifier.  Note that if somehow the
    * current device is invalid, it will have to be changed to a valid
    * one before these commands can run, even if a partition specifier
    * for another device is to be used.
    */
    if (dev < 0 || dev >= CONFIG_SYS_MAX_NAND_DEVICE ||  //判断芯片是否存在或是否定义
       !nand_info[dev].name) {
    puts(" no devices available ");
    return 1;
    }
    nand = &nand_info[dev];   //获取定义的nand芯片信息
      
      ................
      
    if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {  //nand读写操作
    size_t rwsize;
    ulong pagecount = 1;
    int read;
    int raw;


    if (argc < 4)  
    goto usage;


    addr = (ulong)simple_strtoul(argv[2], NULL, 16);  //将argv[2] = "40008000"转换成16进制,0x40008000


    read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */  //判断读写操作类型
    printf(" NAND %s: ", read ? "read" : "write");


    nand = &nand_info[dev];


    s = strchr(cmd, '.');   //看看是否带有扩展命令,如write.raw, write.jffs2等等,输入是“write”,结果s = NULL;


    if (s && !strcmp(s, ".raw")) {
          ......省略.....
          
    } else {  //执行这里,计算地址偏移量,长度
    if (arg_off_size(argc - 3, argv + 3, &dev, 
    &off, &size) != 0)
    return 1;


    rwsize = size;
    }


    if (!s || !strcmp(s, ".jffs2") ||      //实际执行这里
       !strcmp(s, ".e") || !strcmp(s, ".i")) {
    if (read)
    ret = nand_read_skip_bad(nand, off, &rwsize,
    (u_char *)addr);
    else
    ret = nand_write_skip_bad(nand, off, &rwsize,   //执行函数nand_write_skip_bad
     (u_char *)addr, 0);


    } else if (......省略.....) {
    ......省略.....
    ......省略.....
    } else {
    printf("Unknown nand command suffix '%s'. ", s);
    return 1;
    }


    printf(" %zu bytes %s: %s ", rwsize,
          read ? "read" : "written", ret ? "ERROR" : "OK");


    return ret == 0 ? 0 : 1;
    }


    ..........

    return 0;
    }
    来看看函数nand_write_skip_bad,在文件u-boot-sunxi-sunxidriversmtd and and_util.c内:
    经过do_nand处理,可知参数就是输入命令的内容:
    offset   为  0
    *length  为 0x200000
    buffer   指向0x40008000


    int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
    u_char *buffer, int flags)
    {
    int rval = 0, blocksize;
    size_t left_to_write = *length;
    u_char *p_buffer = buffer;
    int need_skip;


    #ifdef CONFIG_CMD_NAND_YAFFS
    if (flags & WITH_YAFFS_OOB) {
    if (flags & ~WITH_YAFFS_OOB)
    return -EINVAL;


    int pages;
    pages = nand->erasesize / nand->writesize;
    blocksize = (pages * nand->oobsize) + nand->erasesize;
    if (*length % (nand->writesize + nand->oobsize)) {
    printf ("Attempt to write incomplete page"
    " in yaffs mode ");
    return -EINVAL;
    }
    } else
    #endif
    {
    blocksize = nand->erasesize;  //执行这里,nand的刷新都是以块为单位的,所以blocksize就是刷新的长度,对于cubieboard上的nand芯片,是1M+80K
    }


    /*
    * nand_write() handles unaligned, partial page writes.
    *
    * We allow length to be unaligned, for convenience in
    * using the $filesize variable.
    *
    * However, starting at an unaligned offset makes the
    * semantics of bad block skipping ambiguous (really,
    * you should only start a block skipping access at a
    * partition boundary).  So don't try to handle that.
    */
    if ((offset & (nand->writesize - 1)) != 0) {    //输入的偏移量要以块长度对齐
    printf ("Attempt to write non page aligned data ");
    *length = 0;
    return -EINVAL;
    }


    need_skip = check_skip_len(nand, offset, *length);  //判断是否需要越过坏块,这里需要坏块读取操作,nand驱动的一个功能
    if (need_skip < 0) {
    printf ("Attempt to write outside the flash area ");
    *length = 0;
    return -EINVAL;
    }


    if (!need_skip && !(flags & WITH_DROP_FFS)) {        //不需要,即写的部分没有坏块
    rval = nand_write (nand, offset, length, buffer);  //直接写
    if (rval == 0)
    return 0;


    *length = 0;
    printf ("NAND write to offset %llx failed %d ",
    offset, rval);
    return rval;
    }


    while (left_to_write > 0) {  // 剩下要写的字节数,开始就是命令输入的0x200000
    size_t block_offset = offset & (nand->erasesize - 1);
    size_t write_size, truncated_write_size;


    WATCHDOG_RESET ();


    if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { //从开始的位置往后找坏块,直到找到一个可写的为止
    printf ("Skip bad block 0x%08llx ",
    offset & ~(nand->erasesize - 1));
    offset += nand->erasesize - block_offset;
    continue;
    }


    if (left_to_write < (blocksize - block_offset))  //找到可写的块,判断写入的数据是不是小于一块,对于cubieboard,是1M
    write_size = left_to_write;                    //由于输入的是0x200000即2M,因此需要写两次
    else
    write_size = blocksize - block_offset;


    #ifdef CONFIG_CMD_NAND_YAFFS
    .......
    #endif
    {
    truncated_write_size = write_size;
    #ifdef CONFIG_CMD_NAND_TRIMFFS
     .......
    #endif


    rval = nand_write(nand, offset, &truncated_write_size,  //调用nand_write,写入数据
    p_buffer);
    offset += write_size;         //偏移量往后移动
    p_buffer += write_size;       //数据指针往后移动
    }


    if (rval != 0) {
    printf ("NAND write to offset %llx failed %d ",
    offset, rval);
    *length -= left_to_write;
    return rval;
    }


    left_to_write -= write_size;   //剩下的字节数,循环写的条件
    }


    return 0;
    }


    无论如何写,有没有坏块,最后都使用函数nand_write,接下来再看看这个函数
    在文件在文件u-boot-sunxi-sunxidriversmtd and and_base.c内:
    这个函数就是写的准备,这已经执行到驱动代码的逻辑层了


    static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
     size_t *retlen, const uint8_t *buf)
    {
    struct nand_chip *chip = mtd->priv;
    int ret;


    /* Do not allow writes past end of device */ 不能超过最大长度
    if ((to + len) > mtd->size)
    return -EINVAL;
    if (!len)
    return 0;


    nand_get_device(chip, mtd, FL_WRITING);  //获取设备,就是获取需要写的那个nand芯片的数据


    chip->ops.len = len;                     //写入的长度,按输入命令,第一次时这个就是一个块的长度
    chip->ops.datbuf = (uint8_t *)buf;       //数据所在的位置,第一次就是输入的内存地址0x40008000处
    chip->ops.oobbuf = NULL;


    ret = nand_do_write_ops(mtd, to, &chip->ops);   //执行写操作


    *retlen = chip->ops.retlen;


    nand_release_device(mtd);


    return ret;
    }
    再看看nand_do_write_ops函数,就在这个文件nand_base.c内,nand_write函数的上面:
    到了这里,其实已经接近硬件操作了,如果要写一个nand驱动,实现写操作,
    看看这个函数,就是知道需要实现的几个操作了。下面对几个关键的地方进行标记,说明写驱动需要实现的功能
    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;


    uint32_t oobwritelen = ops->ooblen;
    uint32_t oobmaxlen = ops->mode == MTD_OOB_AUTO ?
    mtd->oobavail : mtd->oobsize;


    uint8_t *oob = ops->oobbuf;
    uint8_t *buf = ops->datbuf;
    int ret, subpage;


    ops->retlen = 0;
    if (!writelen)
    return 0;


    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);          //芯片片选,由于各种CPU的片选方式或寄存器不同,或者板子电路不同,所以用户必须自己实现这个函数


    /* Check, if it is write protected */
    if (nand_check_wp(mtd)) {
    printk (KERN_NOTICE "nand_do_write_ops: Device is write protected ");
    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);


    /* Don't allow multipage oob writes with offset */
    if (oob && ops->ooboffs && (ops->ooboffs + ops->ooblen > oobmaxlen))
    return -EINVAL;


    while (1) {    输入的长度是块,只能一页一页的写,所以要循环写
    WATCHDOG_RESET();


    int bytes = mtd->writesize;
    int cached = writelen > bytes && page != blockmask;
    uint8_t *wbuf = buf;


    /* Partial page write ? */
    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)) {
    size_t len = min(oobwritelen, oobmaxlen);
    oob = nand_fill_oob(chip, oob, len, ops);
    oobwritelen -= len;
    }


    ret = chip->write_page(mtd, chip, wbuf, page, cached,  //写一页,这个函数有通用的实现,若不适合自己的芯片,则需要自己实现页写功能
          (ops->mode == MTD_OOB_RAW));
    if (ret)
    break;


    writelen -= bytes;
    if (!writelen)
    break;


    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;
    }


    再看看通用的页写函数
    在函数int nand_scan_tail(struct mtd_info *mtd)内有两句代码:
    ......
    if (!chip->write_page)
    chip->write_page = nand_write_page;
    ......


    如果用户没初始化页写函数,则使用默认函数nand_write_page,这就是需要分析的函数


    static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
      const uint8_t *buf, int page, int cached, int raw)
    {
    int status;


    chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);   //命令函数也有默认版本,对单次写地址的芯片如2440,6410,可以使用默认函数,但是不适合A10
                                                        //A10的地址是两个寄存器,每个32位,理论可以支持64位的地址宽度
                                                        //这里执行nand命令NAND_CMD_SEQIN,值是0x80
    if (unlikely(raw))                                //观察调用的地方,可以看出 raw = 2    ===>  ops->mode == MTD_OOB_RAW 
    chip->ecc.write_page_raw(mtd, chip, buf);       //ecc模块也有默认实现
    else
    chip->ecc.write_page(mtd, chip, buf);


    /*
    * Cached progamming disabled for now, Not sure if its worth the
    * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
    */
    cached = 0;


    if (!cached || !(chip->options & NAND_CACHEPRG)) {


    chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);   ////这里执行nand命令NAND_CMD_PAGEPROG,值是0x10
    status = chip->waitfunc(mtd, chip);               //等待写完成,这个需要用自己实现
    * See if operation failed and additional status checks are
    * available
    */
    if ((status & NAND_STATUS_FAIL) && (chip->errstat))
    status = chip->errstat(mtd, chip, FL_WRITING, status,
          page);


    if (status & NAND_STATUS_FAIL)
    return -EIO;
    } else {
    chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
    status = chip->waitfunc(mtd, chip);
    }


    #ifdef CONFIG_MTD_NAND_VERIFY_WRITE
    /* Send command to read back the data */
    chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);


    if (chip->verify_buf(mtd, buf, mtd->writesize))
    return -EIO;
    #endif
    return 0;
    }


    再看看默认的chip->ecc.write_page_raw函数干了什么事情
    在函数int nand_scan_tail(struct mtd_info *mtd)内有两句代码:
    ......
    if (!chip->ecc.write_page_raw)
    chip->ecc.write_page_raw = nand_write_page_raw;
    ......
    如果用户没初始化页写函数,则使用默认函数nand_write_page_raw,这就是需要分析的函数
    这个函数将数据写入,写入什么位置呢?还要看看它调用的函数chip->write_buf
    static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
    const uint8_t *buf)
    {
    chip->write_buf(mtd, buf, mtd->writesize); 
    chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
    }


    再看看默认的chip->write_buf函数
    在函数int nand_scan_tail(struct mtd_info *mtd)内有两句代码:
    ......
    if (!chip->write_buf)
    chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
    ......
    如果用户没初始化页写函数,8位操作则使用默认函数nand_write_buf,16位操作则使用默认函数nand_write_buf16,
    cubieboard使用的nand芯片是8位的,就看看nand_write_buf函数
    void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
    {
    int i;
    struct nand_chip *chip = mtd->priv;


    for (i = 0; i < len; i++)
    writeb(buf[i], chip->IO_ADDR_W);
    }
    将数据写入寄存器chip->IO_ADDR_W,即写到nand缓存
    从这里可以看出,上面的写操作过程就是:
    命令0x80-->写数据-->命令0x10-->等待完成
    查看cubieboard上面nand芯片K9GBG08U0A的数据手册,页写操作的过程真好相同,因此这个驱动可以使用
    使用的前提就是需要实现一下几个函数:


    片选函数:    chip->select_chip
    命令操作函数:chip->cmdfunc


    chip->waitfunc调用的两个函数:
    芯片就绪函数:chip->dev_ready
    字节读取函数:chip->read_byte


    到这里,我都没有分析数据结构,只描述了调用流程
    观察各个函数,贯穿整个过程的数据结构有两个
    struct mtd_info

    struct nand_chip

    这两个数据结构在初始化分析时已经讲过了








  • 相关阅读:
    交换机工作原理
    MyBatis框架的使用及源码分析(一) 配置与使用
    MySQL5.6安装步骤
    mysql创建用户,并授权
    命令行访问远程mysql数据库
    [mybatis] mybatis错误:Invalid bound statement (not found)
    【大数据】每秒十万笔交易的数据架构解读
    【mybaits】Mybatis中模糊查询的各种写法
    【redis】 linux 下redis 集群环境搭建
    [linux] linux下编译安装zlib
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3194139.html
Copyright © 2020-2023  润新知