从命令说起,在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

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

转载于:https://www.cnblogs.com/dyllove98/p/3194139.html

u-boot的nand驱动写过程分析相关推荐

  1. 基于MTD的NAND驱动开发(二)

    基于MTD的NAND驱动开发(二) 基于MTD的NAND驱动开发(三) http://blog.csdn.net/leibniz_zsu/article/details/4977853 http:// ...

  2. SylixOS下基于NUC970的NAND驱动

    开发环境 开发环境 宿主机: Windows7 64bits 系统 开发板: 安米MDK972 软件环境: RealEvo-IDE3.0 NAND Flash: S34ML02G100TF100 S3 ...

  3. NAND驱动分析--(二)

    在上一篇nand驱动分析中,大概描述了nand flash驱动加载时的初始化流程,接下来对其调用的一些函数进行进一步的阐述. 首先,上一篇说到调用了fsl_elbc_chip_init()函数,此函数 ...

  4. 分享自清客 《基于MTD的NAND驱动开发(完) (转)》

    url:http://blog.csdn.net/mianyy/article/details/6712631 六.NAND驱动中的坏块管理 由 于NAND Flash的现有工艺不能保证NAND的Me ...

  5. 详谈调用winpcap驱动写arp多功能工具

    一.winpcap驱动简介 winpcap(windows packet capture)是windows平台下一个免费,公共的网络访问系统. (编者注:WinpCap开发包可以到以下两个网址下载: ...

  6. Linux内核的Nand驱动流程分析

    最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务.内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了 ...

  7. linux NAND驱动之三:6410平台上的NAND驱动加载

    1,platform_driver 的定义和注册 在s3c_nand.c中, static struct platform_driver s3c6410_nand_driver = {         ...

  8. linux NAND驱动之一:内核中的NAND代码布局

    在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips .devices .maps .nand .onenand 和ubi 六个子目录.其中只有nand 和o ...

  9. 【SemiDrive源码分析】【MailBox核间通信】45 - Android侧 RPMSG_IPCC_RPC驱动分析(上) 之 RPMSG设备 与 RPMSG驱动 匹配过程分析

    [SemiDrive源码分析][MailBox核间通信]45 - Android侧 RPMSG_IPCC_RPC驱动分析(上) 之 RPMSG设备 与 RPMSG驱动 匹配过程分析 一. IPCC_R ...

最新文章

  1. 前后端分离的探索(二)
  2. 计算机图形软件---图形功能
  3. hdu 5505(GT and numbers)
  4. 【WebRTC---进阶篇】(五)mediasoup的信令系统
  5. 做一个软件工程师是什么感觉,工作过程中是否觉得开心?
  6. 数据文件shrink_SQL Server中的Shrink TempDB数据库概述
  7. 【vue】【element】el-table列表中设计一个颜色块
  8. 自建git服务器 ssh,搭建基于SSH的Git服务器
  9. Format - Numeric
  10. 囧。。。不知不觉破解了IDMan。。。木有注意最后一步咋破的。。。
  11. Spring源码下载并导入Idea
  12. R语言——相关系数图
  13. CodeForces 595A Vitaly and Night
  14. js使用在指定数据前面或后面插入数据,对List数据排序
  15. 领带的10种打法图解
  16. performance API 中什么指标可以衡量首屏时间
  17. 用一条SQL 语句 查询出每门课都大于80 分的学生姓名
  18. java服务内存占用过高
  19. Word行距无法修改问题(本人亲自实测)
  20. 分析java程序在运行中卡顿

热门文章

  1. pc双网卡实现路由转发_路由器配置骨干网设备MPLS本地会话功能实现数据在MPLS网络中转发...
  2. 这些基础协议,你懂吗?
  3. linux升级ipv6协议栈,IPv6技术及基于Linux平台IPv6协议栈的实现
  4. mysql 事务 数量_如何知道数据库创建以来并发事务的最大数量
  5. shell命令获取按键值_linux shell获取键盘输入
  6. 交换机怎么使用vtp
  7. java服务器缓存_Java服务器缓存溢出有哪些呢、?
  8. MySQL各部门求最值_mysql 求分组最大值的十个解法
  9. fguillot json rpc_Hyperf 框架创建 JSON-rpc 服务
  10. gpu云服务器运行游戏_GPU云服务器可以挂载大型游戏吗?