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

来源:互联网 发布:淘宝卖什么虚拟产品好 编辑:程序博客网 时间:2024/05/05 17:11

从命令说起,在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-sunxi\common\cmd_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("\nno devices available\n"); 
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("\nNAND %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'.\n", s); 
return 1; 



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


return ret == 0 ? 0 : 1; 



.......... 

return 0; 

来看看函数nand_write_skip_bad,在文件\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_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\n"); 
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\n"); 
*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\n"); 
*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\n", 
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\n", 
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\n", 
offset, rval); 
*length -= left_to_write; 
return rval; 



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



return 0; 



无论如何写,有没有坏块,最后都使用函数nand_write,接下来再看看这个函数 
在文件在文件\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_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\n"); 
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

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

0 0
原创粉丝点击