1、参考自带的nand flash驱动,位于drivers/mtd/nand/s3c2410.c中
1.1 为什么nand在mtd目录下?
因为mtd(memory technology device 存储 技术设备 ) 是用于访问 memory 设备( ROM 、 flash )的Linux 的子系统。 MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。
1.2首先来看s3c2410.c的入口函数:
static int __init s3c2410_nand_init(void)
{
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
platform_driver_register(&s3c2412_nand_driver);
platform_driver_register(&s3c2440_nand_driver);
return platform_driver_register(&s3c2410_nand_driver);
}
在入口函数中,注册了一个platform平台设备驱动,也是说当与nandflash设备匹配时,就会调用s3c2440_nand_driver ->probe来初始化。
我们进入probe函数中,看看是如何初始化:
static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type)
{
... ...
err = s3c2410_nand_inithw(info, pdev); //初始化硬件hardware,设置TACLS 、TWRPH0、TWRPH1通信时序等
s3c2410_nand_init_chip(info, nmtd, sets); //初始化芯片
nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); //3.扫描nandflash
... ...
s3c2410_nand_add_partition(info, nmtd, sets); //4.调用add_mtd_partitions()来添加mtd分区
... ...
}
通过上面代码和注释,得出:驱动主要调用内核的nand_scan()函数,add_mtd_partitions()函数,来完成注册nand flash。
2、上面probe()里的 nand_scan()扫描函数 位于/drivers/mtd/nand/nand_base.c
它会调用nand_scan()->nand_scan_ident()->nand_get_flash_type()来获取flash存储器的类型。
以及nand_scan()->nand_scan_ident()->nand_scan_tail()来构造mtd设备的成员(实现对nandflash的读,写,擦除等)。
2.1 其中nand_get_flash_type()函数如下所示:
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,struct nand_chip *chip,int busw, int *maf_id)
{
struct nand_flash_dev *type = NULL;
int i, dev_id, maf_idx;
chip->select_chip(mtd, 0); //调用nand_chip结构体的成员select_chip使能flash片选
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); //3.2调用nand_chip结构体的成员cmdfunc,发送读id命令,最后数据保存在mtd结构体里 *maf_id = chip->read_byte(mtd); // 获取厂家ID,
dev_id = chip->read_byte(mtd); //获取设备ID
/* 3.3for循环匹配nand_flash_ids[]数组,找到对应的nandflash信息*/
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (dev_id == nand_flash_ids[i].id) //匹配设备ID {type = &nand_flash_ids[i];
break;} }
... ...
/* 3.4 匹配成功,便打印nandflash参数 */
printk(KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,
dev_id, nand_manuf_ids[maf_idx].name, mtd->name);
... ...
}
从上面代码和注释得出, nand_chip结构体就是保存与硬件相关的函数(后面会讲这个结构体)。
2.2 NAND_CMD_READID定义为0x90,也就是发送0X90命令,和0x00地址来读id,最后放到mtd中
2.3 nand_flash_ids[]数组是个全局变量,通过匹配设备ID,来确定我们的nand flash是多大存储
如下图所示,在芯片手册中,看到nand flash的设备ID=0XDA
所以就匹配到nand_flash_ids[]里的0XDA:
2.4 然后打印出nand flash参数,我们启动内核就可以看到:
3、probe()里的s3c2410_nand_add_partition()函数主要是注册mtd设备的nand flash
最终它调用了s3c2410_nand_add_partition()->add_mtd_partitions() -> add_mtd_device()
其中add_mtd_partitions()函数主要实现多个分区创建,也就是多次调用add_mtd_device()
当只设置nand_flash为一个分区时,就直接调用add_mtd_device()即可。
4、add_mtd_partitions()函数原型如下:
int add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts,int nbparts); //创建多个分区mtd设备
//函 数 成 员 介 绍 : //master:就是要创建的mtd设备
//parts:分区信息的数组,它的结构体是mtd_partition,该结构体如下所示:
/*
struct mtd_partition {
char *name; //分区名,比如bootloader、params、kernel、root
u_int32_t size; //分区大小
u_int32_t offset; //分区所在的偏移值
u_int32_t mask_flags; //掩码标志
struct nand_ecclayout *ecclayout; //OOB布局
struct mtd_info **mtdp; //MTD的指针,不常用
};
*///nbparts:等于分区信息的数组个数,表示要创建分区的个数
比如我们启动内核时,也能找到内核自带的nandflash的分区信息:
3.2 其中add_mtd_device()函数如下所示:
int add_mtd_device(struct mtd_info *mtd) //创建一个mtd设备
{
struct list_head *this;
... ...
list_for_each(this, &mtd_notifiers) //4.3找mtd_notifiers链表里的list_head结构体 {
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); //通过list_head找到struct mtd_notifier *not
not->add(mtd); //最后调用mtd_notifier 的add()函数
}
... ...
}
3.3 我们搜索上面函数里的mtd_notifiers链表
看看里面的list_head结构体,在哪里放入的,就能找到执行的add()是什么了。
3.4 如下图,发现list_head在register_mtd_user()里放到mtd_notifiers链表中:
3.5 继续搜索register_mtd_user(),被哪个调用:
如上图,找到被drivers/mtd/mtdchar.c、drivers/mtd/mtd_blkdevs.c调用(3.6节和3.7节会分析)
因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)
我们在控制台输入ls -l /dev/mtd*,也能找到块MTD设备节点和字符MTD设备节点,如下图所示:
上图中,可以看到共创了4个分区的设备,每个分区都包含了两个字符设备(mtd%d,mtd%dro)、一个块设备(mtdblock0).其中MTD的块设备的主设备号为31,MTD的字符设备的主设备号为90 (后面会讲到在哪被创建)。
3.6 我们进入上面搜到的drivers/mtd/mtdchar.c, 找到它的入口函数是init_mtdchar():
static void mtd_notify_add(struct mtd_info* mtd)
{
class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
NULL, "mtd%d", mtd->index);
class_device_create(mtd_class, NULL,
MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
NULL, "mtd%dro", mtd->index);
}
static struct mtd_notifier notifier = {
.add = mtd_notify_add,
.remove = mtd_notify_remove,
};
static int __init init_mtdchar(void)
{
/*创建字符设备mtd,主设备号为90 ,cat /proc/devices 可以看到 */
if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {
printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",MTD_CHAR_MAJOR);
return -EAGAIN;
}
mtd_class = class_create(THIS_MODULE, "mtd"); //创建类
if (IS_ERR(mtd_class)) {
printk(KERN_ERR "Error creating mtd class.\n");
unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
return PTR_ERR(mtd_class);
}
register_mtd_user(¬ifier); //调用register_mtd_user(),将notifier添加mtd_notifiers链表中
return 0;
}
所以在mtdchar.c这个分支,not->add(mtd) 即 :mtd_notify_add(mtd)
该函数创建了两个字符设备(mtd%d, mtd%dro ),其中ro的字符设备表示为只读
总结出:
mtdchar.c的入口函数 将notifie添加到mtd_notifiers链表中,
然后在add_mtd_device()函数中当查找到mtd字符设备的list_head时,就调用mtd_notifiers->add()来创建两个字符设备(mtd%d,mtd%dro)
3.7 同样,我们也进入mtd_blkdevs.c (MTD块设备)中,找到注册到mtd_notifiers链表的是blktrans_notifier变量:
3.7.1 然后进入blktrans_notifier变量的blktrans_notify_add ()函数:
static void blktrans_notify_add(struct mtd_info *mtd)
{
struct list_head *this;
if (mtd->type == MTD_ABSENT)
return;
list_for_each(this, &blktrans_majors) //找blktrans_majors链表里的list_head结构体
{
struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
tr->add_mtd(tr, mtd); // 执行mtd_blktrans_ops结构体的add_mtd()
}
}
从上面的代码和注释得出:块设备的add()是查找blktrans_majors链表,然后执行mtd_blktrans_ops结构体的add_mtd()
3.7.2 我们搜索blktrans_majors链表,看看mtd_blktrans_ops结构体在哪里添加进去的
找到该链表在register_mtd_blktrans()函数中:
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
... ...
ret = register_blkdev(tr->major, tr->name); //注册块设备
tr->blkcore_priv->rq=blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
//分配一个请求队列
... ...
list_add(&tr->list, &blktrans_majors); //将tr->list 添加到blktrans_majors链表
}
继续搜索register_mtd_blktrans(),如下图,找到被drivers/mtd/Mtdblock.c、Mtdblock_ro.c调用
3.7.3 我们进入drivers/mtd/Mtdblock.c函数中,如下图所示:
找到执行mtd_blktrans_ops结构体的add_mtd()函数,就是上图的mtdblock_add_mtd()函数
在mtdblock_add_mtd()函数中最终会调用add_mtd_blktrans_dev()
3.7.4 add_mtd_blktrans_dev()函数如下所示:
int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
{
... ...
gd = alloc_disk(1 << tr->part_bits); //分配一个gendisk结构体
gd->major = tr->major; //设置gendisk的主设备号
gd->first_minor = (new->devnum) << tr->part_bits; //设置gendisk的起始此设备号
gd->fops = &mtd_blktrans_ops; //设置操作函数
... ...
gd->queue = tr->blkcore_priv->rq; //设置请求队列
add_disk(gd); //向内核注册gendisk结构体
}
void add_disk(struct gendisk *disk)
{
disk->flags |= GENHD_FL_UP;
blk_register_region(MKDEV(disk->major, disk->first_minor),
disk->minors, NULL, exact_match, exact_lock, disk);
register_disk(disk);
blk_register_queue(disk);
}
总结出:
mtd_blkdevs()块设备的入口函数将blktrans_notifier添加到mtd_notifiers链表中
然后在add_mtd_device()函数中,当查找到有blktrans_notifier时,就调用blktrans_notifier->add()来分配设置注册gendisk结构体,并创建块设备,请求队列。
从这里我们也可以知道,mtd比gendisk更高一级;我们知道一个块设备需要用一个gendisk 结构体来描述,一章用ram来模拟块设备,就是直接分配了一个gendisk,然后设置注册他;这里我们也就理解了本文第一句话:MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,它在硬件和上层之间提供了一个抽象的接口。
4、显然在内核中,mtd已经帮我们做了整个框架,而我们的nand flash驱动只需要以下几步即可:
1)设置mtd_info结构体成员
2)设置nand_chip结构体成员
3)设置硬件相关(设置nand控制器时序等)
4)通过nand_scan()来扫描nandflash
5)通过add_mtd_partitions()来添加分区(一个分区对应一个块设备gendisk),创建MTD字符/块设备
4.1 mtd_info结构体介绍:
主要是实现对nand flash的read()、write()、read_oob()、write_oob()、erase()等操作,属于软件的部分,它会通过它的成员priv来找到对应的nand_chip结构体,来调用与硬件相关的操作.
4.2 nand_chip结构体介绍:
它是mtd_info结构体的priv成员,主要是对MTD设备中的nandflash硬件相关的描述.
当我们不设置nand_chip的成员时,以下的成员就会被mtd自动设为默认值,代码位于: nand_scan()->nand_scan_ident()->nand_set_defaults()。
if (!chip->select_chip)
chip->select_chip = nand_select_chip; // 默认值不适用
if (chip->cmdfunc == NULL) cmdfunc名字上知是发命令的函数。若为空就给一个默认的nand_command/
chip->cmdfunc = nand_command;
-->chip->cmd_ctrl(mtd, command, ctrl);
if (!chip->read_byte)
chip->read_byte = nand_read_byte;
-->readb(chip->IO_ADDR_R);
if (chip->waitfunc == NULL)
chip->waitfunc = nand_wait;
-->chip->dev_ready
显然。默认值有些不适合我们,我们应该自己实现。