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-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
这两个数据结构在初始化分析时已经讲过了
u-boot的nand驱动写过程分析的更多相关文章
- 基于MTD的NAND驱动开发、K9F1G08 2K page、Yaffs2 Files System
转载:http://hi.baidu.com/cui1206/item/1d4119e376132513585dd886 基于MTD的NAND驱动(linux-2.6.22.10内核),目前已可以在该 ...
- MTD中的nand驱动初步分析---面向u-boot
之前提到nand驱动的初始化分析,有一个结构体 struct mtd_info始终贯穿这些代码 再来分析一下这个结构体的基本功能,如何初始化,如何使用 一.分析过程 看看结构体的出现和使用方式 第一次 ...
- MTD下的Nand驱动
目录 MTD下的Nand驱动 引入 平台设备资源文件 关键数据结构 平台框架 s3c24xx_nand_probe nand_scan s3c2410_nand_add_partition add_m ...
- NAND驱动
NAND FLASH是一个存储芯片 那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A" 问1. 原理图上NAND FLASH和S3C2440之间只有数据线, ...
- nand烧写分析/内核在启动过程中式如何将这个文件映射成/目录及各子目录的?
我用的是ramdisk.image.gz,烧写在flash的0x10140000处 我不太明白内核在启动过程中式如何将这个文件映射成/目录及各子目录的? 如果ramdisk.image.gz在flas ...
- LPC1788 nand驱动
Lpc 1788自带有emc接口用于驱动nandflash,norflash,sdram设备,对于nandflash驱动因为配置简单,时序也简单 首先,针对nandflash而言应当在系统中有三个地址 ...
- Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...
- 24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...
- ESP8266乐鑫版本的(支持云端升级 (Boot 模式)烧写方法,(V1.5.4官方介绍如下)(BOOT模式)
硬件平台: nodeMCU devkit核心板,带ch340g,应该是仿造的,官方是cp2102驱动,安信可科技有连接https://wiki.ai-thinker.com/esp8266/board ...
随机推荐
- vnc server配置、启动、重启与连接,图形管理linux系统
环境:RedHat Linux 5企业版.Xwindows:gnome (红帽默认安装的图形界面) 尽管我们可以使用SSH连接远程通过字符界面来操作Linux,但是对于更多熟悉图形人来说是很不方便的, ...
- java邮件小实例
新建一个包,名为mail 第一个类:MailSenderInfo.java ########################################### package com.util. ...
- 在cmd窗口中查询android的sqlite3数据库表之步骤
本文主要是写了一个android程序对sqlite3中数据库的employee表的插入.删除的操作,然后在cmd窗口中用sql命令查询employee表的操作过程. 1.第一步:首先把程序写好. 1. ...
- LVS:DR模式(Direct Routing)部署实验
本文介绍怎样在kvm的虚拟环境下,部署实验LVS的DR模式.包含网络结构图,怎样配置.以及使用tcpdump分析ip包. 网络结构图 kvm ...
- BZOJ 1499 NOI2005 瑰丽华尔兹 单调队列
题目大意:给定一个m*n的地图,一些点有障碍物,钢琴初始在一个点,每一个时间段能够选择向给定的方向移动一段距离,求最长路径长 朴素DP的话,我们有T个时间段,每一个时间段有m*n个点,n个时间,一定会 ...
- JFinal教程1——小白的第一个JFinal程序
为了使小白能够完全的按步骤创建第一个JFinal应用并运行,笔者将以Java界最流行的Eclipse平台为例,搭建出所有基础教程中喜欢的Hello world应用. 1. JFinal简介 2. 小白 ...
- javascript每日一练(十四)——弹性运动
一.弹性运动 运动原理:加速运动+减速运动+摩擦运动: <!doctype html> <html> <head> <meta charset="u ...
- NGINX服务器打开目录浏览功能
我们做文件服务器的时候,希望打开目录浏览的功能.但是Nginx默认是不允许列出目录功能的.若需要此功能,需要在配置文件中手动开启. 首先需要打开开关.autoindex on;autoindex_ex ...
- 判断程序是否在VMWare内运行
现在有许多用户都喜欢用虚拟机来测试他们的软件,以避免对真实机器环境造成损害.但是在虚拟机中,有些功能是受限,甚至不可能完成的,因此,需要在程序中判断虚拟机的环境,如果程序在虚拟机内运行,则就要把虚拟机 ...
- Qt实现不同Treewidget之间拖拽
拖拽是编程中经常要用到的,我这里主要是实习了Treewidget之间直接拖拽Item,按下Ctrl键的话是copy,不按Ctrl则是Move.以下是实现代码 class TreeItemMimeDat ...