先分析linux最上文件系统到底层硬件nandflash的框架图:
1.1. 硬件驱动层:Flash硬件驱动层负责Flash硬件设备的读、写、擦除,Linux MTD设备的norFlash芯片驱动位于driver/mtd/chips子目录,nandflash的驱动位于drivers/mtd/nand子目录。
1.2. MTD原始设备层:MTD原始设备层由两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定Flash的数据,如分区。
1.3. MTD设备层:基于MTD原始设备,Linux系统可以定义出MTD的块设备的结构和字符设备,构成MTD设备层,MTD字符设备定义。
1.4. 设备节点:通过mknod在/dev子目录下建立MTD字符设备节点和块设备节点,用户通过访问此设备节点。
关于1.1、1.2、1.3可以参考之前写的一篇博客: 点击打开链接,这里不在赘述!本文只描述从应用层到底层驱动读写、访问流程。
我们在更新linux的分区,如内核、uboot、文件系统(yaffs2不能更新,cramfs可以)时,会用到几个系统工具: 分区擦除flash_eraseall、分区写nand_write、分区读nand_dump,这里分析这些工具是如何操作底层硬件驱动。
busybox源码路径:busybox-1.20.2\miscutils\flash_eraseall.c
假设内核位于第二个分区,即mtd2,在擦除该分区时使用的命令是:./flash_eraseall /dev/mtd2,回到源码,
int flash_eraseall_main(int argc UNUSED_PARAM, char **argv)
{
struct jffs2_unknown_node cleanmarker;
mtd_info_t meminfo;
int fd, clmpos, clmlen;
erase_info_t erase;
struct stat st;
unsigned int flags;
char *mtd_name;
opt_complementary = "=1";
flags = BBTEST | getopt32(argv, "jq");
mtd_name = argv[optind];
//打开/dev/mtd2设备
fd = xopen(mtd_name, O_RDWR);
//判定是否是字符设备,详见下①
fstat(fd, &st);
if (!S_ISCHR(st.st_mode))
bb_error_msg_and_die("%s: not a char device", mtd_name);
//获取内存信息,详见②
xioctl(fd, MEMGETINFO, &meminfo);
erase.length = meminfo.erasesize;
if (meminfo.type == MTD_NANDFLASH)
flags |= IS_NAND;
clmpos = 0;
clmlen = 8;
if (flags & OPTION_J) { //jffs2格式化分区,我们这里是yaffs2文件系统,跳过
uint32_t *crc32_table;
crc32_table = crc32_filltable(NULL, 0);
cleanmarker.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK);
cleanmarker.nodetype = cpu_to_je16(JFFS2_NODETYPE_CLEANMARKER);
if (!(flags & IS_NAND))
cleanmarker.totlen = cpu_to_je32(sizeof(struct jffs2_unknown_node));
else {
struct nand_oobinfo oobinfo;
xioctl(fd, MEMGETOOBSEL, &oobinfo);
/* Check for autoplacement */
if (oobinfo.useecc == MTD_NANDECC_AUTOPLACE) {
/* Get the position of the free bytes */
clmpos = oobinfo.oobfree[0][0];
clmlen = oobinfo.oobfree[0][1];
if (clmlen > 8)
clmlen = 8;
if (clmlen == 0)
bb_error_msg_and_die("autoplacement selected and no empty space in oob");
} else {
/* Legacy mode */
switch (meminfo.oobsize) {
case 8:
clmpos = 6;
clmlen = 2;
break;
case 16:
clmpos = 8;
/*clmlen = 8;*/
break;
case 64:
clmpos = 16;
/*clmlen = 8;*/
break;
}
}
cleanmarker.totlen = cpu_to_je32(8);
}
cleanmarker.hdr_crc = cpu_to_je32(
crc32_block_endian0(0, &cleanmarker, sizeof(struct jffs2_unknown_node) - 4, crc32_table)
);
}
/* Don't want to destroy progress indicator by bb_error_msg's */
applet_name = xasprintf("\n%s: %s", applet_name, mtd_name);
for (erase.start = 0; erase.start < meminfo.size;
erase.start += meminfo.erasesize) {
if (flags & BBTEST) {
int ret;
loff_t offset = erase.start;
//通过offset,判定该偏移处是否是坏块,详见③
ret = ioctl(fd, MEMGETBADBLOCK, &offset);
if (ret > 0) {
if (!(flags & OPTION_Q))
bb_info_msg("\nSkipping bad block at 0x%08x", erase.start);
continue;
}
if (ret < 0) {
/* Black block table is not available on certain flash
* types e.g. NOR
*/
if (errno == EOPNOTSUPP) {
flags &= ~BBTEST;
if (flags & IS_NAND)
bb_error_msg_and_die("bad block check not available");
} else {
bb_perror_msg_and_die("MEMGETBADBLOCK error");
}
}
}
//更新擦除进度条
if (!(flags & OPTION_Q))
show_progress(&meminfo, &erase);
//块擦除操作,详见④
xioctl(fd, MEMERASE, &erase);
/* format for JFFS2 ? */
if (!(flags & OPTION_J))
continue;
/* write cleanmarker */
if (flags & IS_NAND) {
struct mtd_oob_buf oob;
oob.ptr = (unsigned char *) &cleanmarker;
oob.start = erase.start + clmpos;
oob.length = clmlen;
xioctl(fd, MEMWRITEOOB, &oob);
} else {
xlseek(fd, erase.start, SEEK_SET);
/* if (lseek(fd, erase.start, SEEK_SET) < 0) {
bb_perror_msg("MTD %s failure", "seek");
continue;
} */
xwrite(fd, &cleanmarker, sizeof(cleanmarker));
/* if (write(fd, &cleanmarker, sizeof(cleanmarker)) != sizeof(cleanmarker)) {
bb_perror_msg("MTD %s failure", "write");
continue;
} */
}
if (!(flags & OPTION_Q))
printf(" Cleanmarker written at %x.", erase.start);
}
if (!(flags & OPTION_Q)) {
show_progress(&meminfo, &erase);
bb_putchar('\n');
}
if (ENABLE_FEATURE_CLEAN_UP)
close(fd);
return EXIT_SUCCESS;
}
以上的①、②、③、④对应内核文件mtd通用层,源码路径:linux-3.10.x\drivers\mtd\mtdchar.c
//打开/dev/mtd2设备
fd = xopen(mtd_name, O_RDWR);
对应mtd通用层代码mtdchar.c
static int mtdchar_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
int devnum = minor >> 1;
int ret = 0;
struct mtd_info *mtd;
struct mtd_file_info *mfi;
struct inode *mtd_ino;
pr_debug("MTD_open\n");
/* You can't open the RO devices RW */
if ((file->f_mode & FMODE_WRITE) && (minor & 1))
return -EACCES;
ret = simple_pin_fs(&mtd_inodefs_type, &mnt, &count);
if (ret)
return ret;
mutex_lock(&mtd_mutex);
mtd = get_mtd_device(NULL, devnum);
if (IS_ERR(mtd)) {
ret = PTR_ERR(mtd);
goto out;
}
if (mtd->type == MTD_ABSENT) {
ret = -ENODEV;
goto out1;
}
mtd_ino = iget_locked(mnt->mnt_sb, devnum);
if (!mtd_ino) {
ret = -ENOMEM;
goto out1;
}
if (mtd_ino->i_state & I_NEW) {
mtd_ino->i_private = mtd;
mtd_ino->i_mode = S_IFCHR;
mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info;
unlock_new_inode(mtd_ino);
}
file->f_mapping = mtd_ino->i_mapping;
/* You can't open it RW if it's not a writeable device */
if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
ret = -EACCES;
goto out2;
}
mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
if (!mfi) {
ret = -ENOMEM;
goto out2;
}
mfi->ino = mtd_ino;
mfi->mtd = mtd;
file->private_data = mfi;
mutex_unlock(&mtd_mutex);
return 0;
out2:
iput(mtd_ino);
out1:
put_mtd_device(mtd);
out:
mutex_unlock(&mtd_mutex);
simple_release_fs(&mnt, &count);
return ret;
} /* mtdchar_open */
//获取内存信息,详见②
xioctl(fd, MEMGETINFO, &meminfo);
//......
//通过offset,判定该偏移处是否是坏块,详见③
ret = ioctl(fd, MEMGETBADBLOCK, &offset);
//......
//块擦除操作,详见④
xioctl(fd, MEMERASE, &erase);
//......
对应mtd通用层代码mtdchar.c
static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
{
//......
switch (cmd) {
case MEMGETINFO:
memset(&info, 0, sizeof(info));
info.type = mtd->type; //flash类型,MTD_NANDFLASH MTD_NORFLASH
info.flags = mtd->flags; //MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等
info.size = mtd->size; //MTD设备的大小
info.erasesize = mtd->erasesize; //MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小
info.writesize = mtd->writesize; //写大小, 对于norFlash是字节,对nandFlash为一页
info.oobsize = mtd->oobsize; //OOB字节数
/* The below field is obsolete */
info.padding = 0;
if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
return -EFAULT;
break;
}
case MEMERASE:
case MEMERASE64:
{
struct erase_info *erase;
if(!(file->f_mode & FMODE_WRITE))
return -EPERM;
erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
if (!erase)
ret = -ENOMEM;
else {
wait_queue_head_t waitq;
DECLARE_WAITQUEUE(wait, current);
init_waitqueue_head(&waitq);
if (cmd == MEMERASE64) {
struct erase_info_user64 einfo64;
if (copy_from_user(&einfo64, argp,
sizeof(struct erase_info_user64))) {
kfree(erase);
return -EFAULT;
}
erase->addr = einfo64.start;
erase->len = einfo64.length;
} else {
struct erase_info_user einfo32;
if (copy_from_user(&einfo32, argp,
sizeof(struct erase_info_user))) {
kfree(erase);
return -EFAULT;
}
erase->addr = einfo32.start;
erase->len = einfo32.length;
}
erase->mtd = mtd;
erase->callback = mtdchar_erase_callback;
erase->priv = (unsigned long)&waitq;
//擦除操作
/*
FIXME: Allow INTERRUPTIBLE. Which means
not having the wait_queue head on the stack.
If the wq_head is on the stack, and we
leave because we got interrupted, then the
wq_head is no longer there when the
callback routine tries to wake us up.
*/
ret = mtd_erase(mtd, erase);
if (!ret) {
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(&waitq, &wait);
if (erase->state != MTD_ERASE_DONE &&
erase->state != MTD_ERASE_FAILED)
schedule();
remove_wait_queue(&waitq, &wait);
set_current_state(TASK_RUNNING);
ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
}
kfree(erase);
}
break;
}
case MEMGETBADBLOCK:
{
loff_t offs;
if (copy_from_user(&offs, argp, sizeof(loff_t)))
return -EFAULT;
return mtd_block_isbad(mtd, offs); //调用是否是坏块接口,这个在之前的一篇文章分析过。
break;
}
//....
}
这里没有详细的分析mtdchar.c中函数对底层驱动操作,如mtd_block_isbad(...), mtd_erase(...)...原因是这些函数内部都会调用底层驱动接口函数,这些内容在我之前的一篇博客有提到,详见:点击打开链接
这两个工具的分析过程和flash_eraseall一样,这里不再赘述。
nandwrite源码路径:busybox-1.20.2\miscutils\nandwrite.c
nanddump源码路径:busybox中未发现,貌似用的是nandwrite的源码