u-boot_2010.6 nandflash驱动彻底分析


    2017年11月13日15:37:34   

    最近公司大裁员,闹的人心惶惶,不管怎么样,武装好自己才是硬道理,坚持学习,学会那些还没学会的。

    今天虚拟机突然打不开了,吓了我一跳,因为代码都还没备份,一定得养成备份代码的习惯!

    好了,下面开始进入正题吧,nandflash驱动彻底分析

    底层驱动移植完后,执行nand 命令:nand read 0x30000000 0 0x2000

    nand read 的命令格式是 nand read addr off | partition size ,总结起来就是 读到哪去? 从哪读? 读多大?

    返回的结果是:

        NAND read: device 0 offset 0x0, size 0x2000

        file is nand_util.c,fun is nand_read_skip_bad,line is 599,NAND read from offset 2000 failed -74

        0 bytes read: ERROR

    读失败,给出读失败错误码 -74

    要分析失败原因,先分析函数执行流程

    执行nand read 命令后,其实是执行了nand_read_skip_bad(nand, off, &size,(u_char *)addr);

    跳过坏块读函数的参数简单明了,从哪读,读到哪去,读多少,以及一个公共句柄(包含nand的信息,例如有多少个块,块大小等)

 1 int nand_read_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
 2                u_char *buffer)
 3 {
 4     int rval;
 5     size_t left_to_read = *length;
 6     size_t len_incl_bad;
 7     u_char *p_buffer = buffer;
 8 
 9     len_incl_bad = get_len_incl_bad (nand, offset, *length); /* 函数分析见2017.11.14笔记(往下翻) */
10 
11     if ((offset + len_incl_bad) > nand->size) {
12         printf ("Attempt to read outside the flash area\n");
13         return -EINVAL;
14     }
15 
16     if (len_incl_bad == *length) {
17         rval = nand_read (nand, offset, length, buffer);
18         if (!rval || rval == -EUCLEAN)
19             return 0;
20         printf ("NAND read from offset %llx failed %d\n",
21             offset, rval);
22         return rval;
23     }
24 
25     while (left_to_read > 0) {
26         size_t block_offset = offset & (nand->erasesize - 1);
27         size_t read_length;
28 
29         WATCHDOG_RESET ();
30 
31         if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) {
32             printf ("Skipping bad block 0x%08llx\n",
33                 offset & ~(nand->erasesize - 1));
34             offset += nand->erasesize - block_offset;
35             continue;
36         }
37 
38         if (left_to_read < (nand->erasesize - block_offset))
39             read_length = left_to_read;
40         else
41             read_length = nand->erasesize - block_offset;
42         /* read_length 最大不会超过 nand->erasesize (128K) */
43         /* nand_read 函数是一个按块读函数 */
44         rval = nand_read (nand, offset, &read_length, p_buffer);
45         if (rval && rval != -EUCLEAN) {
46             printf ("NAND read from offset %llx failed %d\n",
47                 offset, rval);
48             *length -= left_to_read;
49             return rval;
50         }
51 
52         left_to_read -= read_length;
53         offset       += read_length;
54         p_buffer     += read_length;
55     }
56 
57     return 0;
58 }

     先看get_len_incl_bad

 1 static size_t get_len_incl_bad (nand_info_t *nand, loff_t offset,
 2                 const size_t length)
 3 {
 4     size_t len_incl_bad = 0;
 5     size_t len_excl_bad = 0;
 6     size_t block_len;
 7 
 8     while (len_excl_bad < length) {
 9         block_len = nand->erasesize - (offset & (nand->erasesize - 1));
10 
11         if (!nand_block_isbad (nand, offset & ~(nand->erasesize - 1)))
12             len_excl_bad += block_len;
13 
14         len_incl_bad += block_len;
15         offset       += block_len;
16 
17         if (offset >= nand->size)
18             break;
19     }
20 
21     return len_incl_bad;
22 }

    想要看懂这个函数,先要理解offset的构成

   

    2017年11月14日10:00:40

    每天进步一点点

    nand->erasesize = 128K = 0x20000

    nand->erasesize - 1 = 0x1FFFF

    offset & (nand->erasesize - 1) 保留低17位,清高位,因为nand的特性,所以是按块来统计长度

    block_len = nand->erasesize - offset & (nand->erasesize - 1) 作用是统计当前块要读的长度

    然后判断当前块是不是坏块,如果不是坏块,则让len_excl_bad += block_len,判断是否循环的标准是len_excl_bad < length

    不论当前块是不是坏块,都让len_incl_bad += block_len,len_incl_bad 得到的是包含坏块的长度

    总结get_len_incl_bad函数的作用为:

    ①如果偏移offset是从整块开始的,返回的结果是块的整数倍(不用看length,比如length是0x20001,则读两块)

    ②如果偏移offset不是从整块开始的,返回的结果是块的整数倍+第一块要读的长度

    总之,返回的结果是按块补齐的

    再继续分析nand_read_skip_bad

1 if (len_incl_bad == *length) {
2         rval = nand_read (nand, offset, length, buffer);
3         if (!rval || rval == -EUCLEAN)
4             return 0;
5         
6         return rval;
7     }

    什么情况下,len_incl_bad == *length ?
    要读的内容里,没有坏块,且长度最后按块对齐(例如,从0x400开始读,读的长度为0x40000-0x400)

    如果不满足,则进入while循环读,在while里,按块读,先判断当前块是不是坏块,如果是则跳过,不是则读

    下面分析nand_read函数

 1 /* 1.从哪读 2.读多少 3.读到哪去 */
 2 static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
 3              size_t *retlen, uint8_t *buf)
 4 {
 5     struct nand_chip *chip = mtd->priv;
 6     int ret;
 7 
 8     /* Do not allow reads past end of device */
 9     if ((from + len) > mtd->size)
10         return -EINVAL;
11     if (!len)
12         return 0;
13     /* 选中芯片 */
14     nand_get_device(chip, mtd, FL_READING);
15 
16     chip->ops.len = len;
17     chip->ops.datbuf = buf;
18     chip->ops.oobbuf = NULL;
19 
20     ret = nand_do_read_ops(mtd, from, &chip->ops);
21 
22     *retlen = chip->ops.retlen;
23     /* 取消选中芯片 */
24     nand_release_device(mtd);
25 
26     return ret;
27 }

    其实真正的读函数是nand_do_read_ops,从哪去,读多少,读到哪去都被装载在chip->ops结构体中

    再看nand_do_read_ops函数

  1 /**
  2  * nand_do_read_ops - [Internal] Read data with ECC
  3  *
  4  * @mtd:    MTD device structure
  5  * @from:    offset to read from
  6  * @ops:    oob ops structure
  7  *
  8  * Internal function. Called with chip held.
  9  */
 10 static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
 11                 struct mtd_oob_ops *ops)
 12 {
 13     int chipnr, page, realpage, col, bytes, aligned;
 14     struct nand_chip *chip = mtd->priv;
 15     struct mtd_ecc_stats stats;
 16     int blkcheck = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
 17     int sndcmd = 1;
 18     int ret = 0;
 19     uint32_t readlen = ops->len;//128K 0x20000 根据输入的长度决定
 20     uint32_t oobreadlen = ops->ooblen; // oobreadlen = 0
 21     uint8_t *bufpoi, *oob, *buf;
 22 
 23     stats = mtd->ecc_stats;
 24 
 25     chipnr = (int)(from >> chip->chip_shift);
 26     chip->select_chip(mtd, chipnr);
 27     /* realpage 总页地址(高17位)  */
 28     realpage = (int)(from >> chip->page_shift);//pageshift is 11 
 29     page = realpage & chip->pagemask;//pagemask = 1ffff
 30     /* col 低11位 */
 31     col = (int)(from & (mtd->writesize - 1));//writesize = 2048, 2047 = 0x7ff
 32     /* 得到页地址和列地址 */
 33     buf = ops->datbuf;
 34     oob = ops->oobbuf;
 35 
 36     while(1) {
 37         bytes = min(mtd->writesize - col, readlen); //128K 0x20000 根据输入的长度决定
 38         aligned = (bytes == mtd->writesize);        //aligned = 1;
 39 
 40         /* Is the current page in the buffer ? */
 41         if (realpage != chip->pagebuf || oob) {
 42             bufpoi = aligned ? buf : chip->buffers->databuf;
 43             /* 对齐与不对齐读到的缓冲是不一样的 */
 44             if (likely(sndcmd)) {
 45                 chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
 46                 sndcmd = 0;
 47             }
 48 
 49             /* Now read the page into the buffer */
 50             if (unlikely(ops->mode == MTD_OOB_RAW))
 51                 ret = chip->ecc.read_page_raw(mtd, chip,//nand_read_page_raw
 52                         bufpoi, page);//从哪里读,读到哪里去,读多少
 53             else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
 54                 ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
 55                 /* nand_read_subpage */
 56             else
 57                 ret = chip->ecc.read_page(mtd, chip, bufpoi,//整页读 bufpoi = buf
 58                         page);  //nand_read_page_swecc
 59             if (ret < 0)
 60                 break;
 61 
 62             /* Transfer not aligned data */
 63             if (!aligned) {
 64                 if (!NAND_SUBPAGE_READ(chip) && !oob)
 65                     chip->pagebuf = realpage;
 66                 memcpy(buf, chip->buffers->databuf + col, bytes);
 67             }
 68 
 69             buf += bytes;
 70 
 71             if (unlikely(oob)) {
 72                 /* Raw mode does data:oob:data:oob */
 73                 if (ops->mode != MTD_OOB_RAW) {
 74                     int toread = min(oobreadlen,
 75                         chip->ecc.layout->oobavail);
 76                     if (toread) {
 77                         oob = nand_transfer_oob(chip,
 78                             oob, ops, toread);
 79                         oobreadlen -= toread;
 80                     }
 81                 } else
 82                     buf = nand_transfer_oob(chip,
 83                         buf, ops, mtd->oobsize);
 84             }
 85 
 86             if (!(chip->options & NAND_NO_READRDY)) {
 87                 /*
 88                  * Apply delay or wait for ready/busy pin. Do
 89                  * this before the AUTOINCR check, so no
 90                  * problems arise if a chip which does auto
 91                  * increment is marked as NOAUTOINCR by the
 92                  * board driver.
 93                  */
 94                 if (!chip->dev_ready)
 95                     udelay(chip->chip_delay);
 96                 else
 97                     nand_wait_ready(mtd);
 98             }
 99         } else {
100             memcpy(buf, chip->buffers->databuf + col, bytes);
101             buf += bytes;
102         }
103 
104         readlen -= bytes;
105 
106         if (!readlen)
107             break;
108 
109         /* For subsequent reads align to page boundary. */
110         col = 0;
111         /* Increment page address */
112         realpage++;
113 
114         page = realpage & chip->pagemask;
115         /* Check, if we cross a chip boundary */
116         if (!page) {
117             chipnr++;
118             chip->select_chip(mtd, -1);
119             chip->select_chip(mtd, chipnr);
120         }
121 
122         /* Check, if the chip supports auto page increment
123          * or if we have hit a block boundary.
124          */
125         if (!NAND_CANAUTOINCR(chip) || !(page & blkcheck))
126             sndcmd = 1;
127     }
128 
129     ops->retlen = ops->len - (size_t) readlen;
130     if (oob)
131         ops->oobretlen = ops->ooblen - oobreadlen;
132 
133     if (ret)
134         return ret;
135 
136     if (mtd->ecc_stats.failed - stats.failed)
137         return -EBADMSG;
138 
139     return  mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
140 }