初学嵌入式,初学uboot。之前移植过uboot一两次。第三次在实现yaffs2功能时,总是verify错误,我3天硬是没检查出来,
所以才深入学习了uboot实现yaffs2的过程。这是我的总结,画了nand处理yaffs2的流程图,供以后自己查阅,同时供入门网友们参考。
由于我没学到驱动编写,mtd结构还很多不懂,写的很肤浅,大家勿见怪,请多多指教,先谢! :D
uboot中yaffs2移植时代码的更改,参见hbhuanggang老师:
http://blog.chinaunix.net/space.php?do=serial&id=3018&uid=22174347
-------------------------------------------------------------------------------------------------------------------------------
在uboot输入例如 nand write.yaffs2 30008000 8000000 18c0的写yaffs2命令后.将进入do_nand()函数;
首先从argv[1]提取cmd=write.yaffs2;
内存地址addr=30008000; nand地址off=800 0000; yaffs2文件大小 size=18c0;
修改rw_oob=1 表示写nand时要写入oob数据.
skipfirstblk 表示跳过第一个好块.现在off=800 0000 +blocksize=802 0000.
然后进入第一个调用函数nand_write_skip_bad();
总结:do_nand()函数只简单读取输入参数,判断操作类型,调用对应函数;
进入nand_write_skip_bad():
首先计算数据将占用nand页数(datapage),然后是data长度(length).判断给入的地址是否合法.
计算跳过坏快后,数据实际会占用的长度len_incl_bad,这个变量加上offset检测写数据是否将超出nand容量.
然后进入写数据的循环,这里循环一次,写完一个block;
循环内:
首先判断将要操作的下一块是否坏块,若是坏快,继续判断之后一块. 若是好块,则开始写这一块;
第一次要跳过一个好块,修改offset,指向下一块(这里我图画得不好,skipfirstblk只会执行一次,而我画得每次都执行.注意);
下一步计算此次要写的数据量write_size,这里分两种情况,一是数据还很多,这次要写满一块.
另一种是数据快结束了,不足一块,此时write_size等于剩下的数据left_to_write;
然后将write_size传给下一个函数nand_write();
写完这一块之后,更新参数,off += write_size; left_to_write -=write_size; P_buffer +=write_size+(write_size/pagesize*oob_size);
(这里为什么p_buffer要多加上一部份呢,我们不是只要求写了write_size个数据,而且给下一个函数传参也是write_size,为什么指针地址要
改变这么多呢.这是因为,我们是yaffs2数据,写模式为raw,后续的函数,会从buf里取出oob数据,一起写进页里. 所以虽然值要求写write_size,
但却多写了几个oob数据. 同时,以后要记住,如果我们要写oob数据,传参的长度,也应该只是data长度,不是总长度.)
本次写一块结束,开始下一次循环.....
总结:nand_write_skip_bad寻找好的块,把数据以块为单位,一块一块地写入nand flash中.
从nand_write_skip_bad进入到nand_write:
首先计算出此次要求写入的数据,将占用多少页.即1~64页.
然后整理此次传入的数据,因为yaffs2的数据里,含有oob数据,现在要把oob数据分离出来,集中到最后边.
2048+64 |2048+64 |2048+64 |2048+64|整理成:
2048+2048+2048+2048 |64+64+64+64|.
然后将NAND现在的结构情况写入struct nand_chip *chip, 这个结构体是用来描述NAND的;
主要更新了chip->ops这个结构体的内容:
struct mtd_oob_ops {
mtd_oob_mode_t mode; = MTD_OOB_RAW;// 是在这里设置了模式
size_t len; = len //data长度 (1~64)&2048
size_t retlen; 空 //data区写入的长度,完成写时候,返回值存在这里;
size_t ooblen; = oobsize //oob长度 0~64
size_t oobretlen; 空 //oob区写入的长度,写oob函数返回这个值;
uint32_t ooboffs; 0x0 //但是没找到在哪赋值的,
uint8_t *datbuf; = buf //data指针
uint8_t *oobbuf; = buf+len //oob数据的指针,因为上边整理了oob,就是这个位置了;
};
进入nand_do_write_ops();进行写数据;
总结:nand_write()还没真正的写nand,只是整理oob,更新data,oob数据指针,data区,oob区长度.即为下个函数准备参数.
来到nand_do_write_ops:首先定义几个变量,存储几个熟悉的值.
oob是oob指针, buf是buf指针, writelen是data长度(1~64)&2048, 已写data长度ops->retlen归零.
检查to, writelen是否对齐, 用的是这个宏来检测:
#define NOTALIGNED(x) (x & (chip->subpagesize - 1)) != 0
开头例子:to等于802 0000&(512-1)=0,writelen也对齐,因为正确的yaffs2格式数据是2048+64=2112的倍数(multiple of 2112),
其中writelen是data长度,由page*2048算出,所以 page*2048 & (511-1) = 0;
//subpagesize是什么意思?留一下.
计算,column = to & (mtd->writesize - 1) = 802 0000 & (2048-1) = 0;
subpage = column||(writelen & (mtd->writesize - 1)) = 0 || (page*2048&(2048-1)) = 0; 这是或者|| 不是位或.
这里yaffs2 subpage肯定为0,因为yaffs2长度是2048+64,column恒0,subpage恒0;
if(subpage&oob)其他数据格式应该才有用.
chipnr=to>>28; 为0;执行nand_select_chip() 在320行.但是chipnr=0,什么都没做?;
realpage=802 0000>>11 为0x10040页; page=10040&1FFFF=10040;确定数据要写在哪一页.
blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1= 1<<(17-11)-1= 3F;
下面进入又一个while循环,每次循环写一页!(前面第一个while循环,把数据分成块来书写,这里再分成页来书写)
循环变量:
byte=mtd->writesize(2048), wbuf=buf.
cached = writelen > bytes && page != blockmask = 1 && 0 //变量意义不明.
if(partial wirite 写部分页),yaffs2不存在这种情况。
调用nand_fill_oob(),很简单,把当前要写这页的oob数据memcpy到chip->oob_poi+obboffs位置;
我不是很清楚为什么要这么个位置,chip->ops->oobbuf现成的不能用吗?
下面是google的一句话:
@oob_poi: poison value buffer
It seems like the oob_poi buffer is meant to be an intermediary(中间媒介) bufferfor
storing the laid-out OOB data; it's used by the NAND subsystem,especially in mode
MTD_OOB_AUTO, to layout data to be written. But this"documentation" means
absolutely nothing to me.
把oob放到相应位置后,chip->nand_write(),这个函数真正开始写data+oob;
一页写完之后,更新变量,writelen -= bytes,buf += bytes, column = 0,realpage++;
判断数据写完没,写完则返回,没完就要判断下一个页号page,判断是否当前nand设备写完,取消始能,
再始能下一片nand.继续写下一页...直到写完0~64页,nand_dowrite_ops()函数返回.
总结:nand_do_write_ops()函数首先始能nand,并把数据分成一页一页大小,把此页oob拷贝到chip->oob_opi处,供
下面的函数调用这个地址;
总于到了最内层一个函数,nand_write_page(),真正的写页;
第一,向NFCMD寄存器发送写页命令0x80,并提供地址;
nand准备好后,从cmdfunc函数返回,现在可以开始发送数据到NFDATA寄存器了;
调用ecc.write_page_raw(),可以看到就是先写data,再写oob,比较简单;
写完之后,发送命令0x10,表示写结束,然后马上进行校验是否写成功,读出此次写的数据,用READ0指令.
然后读出数据,与buf里的内容循环比较; if (buf[i] != readb(chip->IO_ADDR_R)),
不同就返回verify错误,正确就返回0;
总结:nand_write_page(),先后写一页的data,oob,然后校验. 结束.
我这次移植结束,每次写yaffs2文件,总是遇到verify错误,也就是一直没写成功。直到把一个block写成坏块,跳到下一个好块去写数据,
就成功了。正是形成的这个坏块,我检查出了问题。
我把我的操作写出来,大家看看哪里错了。
tftp 30008000 testyaffs2.img
-ok,长度0x18c0;
nand erase 8000000 20000;
-ok;
nand write.yaffs2 30008000 8000000 18c0;
-fail;
对,就错在那个20000,虽然数据只有18c0,不足一块,我擦出了一块.
但是第一块,被skipfirstblk跳过了,数据写在了下一块.由于没擦除,所以写不上,所以verify错误;
所以以后数据校验错误,首先检查擦出成功没。
遗留问题:
0:nand_write()函数有5个参数,怎么调用的时候只给了4个???我添加了printf语句,程序的确跑到那里了,说明函数没找错,
这里是直接调用函数名,少一个参数不知到怎么处理的.
解答: static inline int nand_write(nand_info_t *info, loff_t ofs, size_t *len, u_char *buf)
{
return info->write(info, ofs, *len, (size_t *)len, buf);
}
1:subpagesize,if(subpage&oob)什么含义。
2:chip->cmdfunc向NFCMD发送命令函数对0x80的处理没看到,函数可能找错了;
解答:nand_get_flash_type()函数查找id,进而决定chip->cmdfunc函数指针,大页指向:nand_command_lp(),小页指向:nand_command();
3:chip->select_chip(mtd,chipnr); 函数实现里,chipnr -1,表示取消始能,0却没操作;是不是函数又找错了...
解答:对nand的始能在nand_command()函数操作的,这里select_chip是空操作.这个函数只能传-1,取消片选;
4:do_nand_write_ops对nand始能后,没见取消片选.
解答:在上级函数nand_write()最后,用下边这个函数实现;
static void nand_release_device (struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
this->select_chip(mtd, -1); /* De-select the NAND device */
}
联系客服