前言
前面我们已经写过一个块设备驱动程序,用内存来模拟块设备。假如我们想写一个NAND FLASH驱动程序,又应该怎么做呢?我们先看一下kernel代码中别人现成的驱动程序,分析一下流程框架,总结出一个套路,我们就能开始写自己的驱动程序了。
之前写过一个裸板的nand flash程序:NAND FLASH的读操作及原理,可以参考一下。
正文
我参考的是这个驱动程序:drivers\mtd\nand\at91_nand.c。在调用platform_driver_register()平台注册函数后,如果找到device的name同样为"at91_nand",就会调用driver的probe函数(为什么会自动调用probe函数?可以参考我之前写的文章:设备、驱动、总线模型简介)。
static struct platform_driver at91_nand_driver = {
.probe = at91_nand_probe,
.remove = at91_nand_remove,
.driver = {
.name = "at91_nand",
.owner = THIS_MODULE,
},
};
static int __init at91_nand_init(void)
{
return platform_driver_register(&at91_nand_driver);
}
这里也顺便看一下相关设备资源的注册,其实就是NAND flash的需要设置的寄存器的首地址和结束地址
static struct resource nand_resources[] = {
{
.start = NAND_BASE,
.end = NAND_BASE + SZ_8M - 1,
.flags = IORESOURCE_MEM,
}
};
static struct platform_device at91rm9200_nand_device = {
.name = "at91_nand",
.id = -1,
.dev = {
.platform_data = &nand_data,
},
.resource = nand_resources,
.num_resources = ARRAY_SIZE(nand_resources),
};
void __init at91_add_device_nand(struct at91_nand_data *data)
{
...
platform_device_register(&at91rm9200_nand_device);
}
假如已经调用到了probe函数,我们看一下注册函数里面大概做了什么
static int __init at91_nand_probe(struct platform_device *pdev)
{
struct at91_nand_host *host;
struct mtd_info *mtd;
struct nand_chip *nand_chip; //分配nand_chip结构体
mtd->priv = nand_chip;
mtd = &host->mtd;
nand_chip = &host->nand_chip;
... //设置nand_chip结构体
if (host->board->bus_width_16) /* 16-bit bus width */
nand_chip->options |= NAND_BUSWIDTH_16;
nand_scan(mtd, 1)
add_mtd_partitions(mtd, partitions, num_partitions);
}
由上面的代码可以看出
(1)首先是分配了三个结构体:其中我们重点关注一下mtd_info和nand_chip结构体
(2)然后就是设置nand_chip结构体中的参数等等
(3)最后就是调用了nand_scan()和add_mtd_parttions()函数
下面我们先看一下nand_scan()中具体做了什么。
int nand_scan(struct mtd_info *mtd, int maxchips)
{
int ret;
...
ret = nand_scan_ident(mtd, maxchips);
if (!ret)
ret = nand_scan_tail(mtd);
return ret;
}
继续看一下nand_scan_ident()和nand_scan_tail()里面做了什么
int nand_scan_ident(struct mtd_info *mtd, int maxchips)
{
struct nand_chip *chip = mtd->priv;
/* Get buswidth to select the correct functions */
busw = chip->options & NAND_BUSWIDTH_16; //总线的宽度
/* Set the default functions */
nand_set_defaults(chip, busw); //设置一些默认的函数,比如选中芯片的函数、发命令函数、读取寄存器值的函数等等
/* Read the flash type */
type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);
}
int nand_scan_tail(struct mtd_info *mtd)
{
//内容比较多,但是总体就是设置mtd结构体中的读、写、擦除nand flash的操作函数等等
mtd->erase = nand_erase;
mtd->read = nand_read;
mtd->write = nand_write;
}
真正跟到代码里面的话,内容非常多,总结起来就是调用nand_scan()函数,完成一些读、写或者擦除nand flash等等的一些操作函数。设置完后,应该就是注册我们的设置的结构体了,下面继续看一下probe函数最后的add_mtd_partitions()里面的各种调用关系。
add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts, int nbparts)
//设置slave->mtd,比如读、写、擦除函数等等
/* register our partition */
add_mtd_device(&slave->mtd); //注册设置过的slave->mtd
list_for_each(this, &mtd_notifiers) {// mtd_notifiers在哪设置?drivers/mtd/mtdchar.c,mtd_blkdev.c调用register_mtd_user
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
not->add(mtd);
}
/* 其中,not->add函数可以分为两部分:
* mtd_notify_add -- 字符设备部分
* blktrans_notify_add -- 块设备部分
*/
先看一下字符设备部分:
static void mtd_notify_add(struct mtd_info* mtd)
{
if (!mtd)
return;
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);
}
是我们之前写字符设备常见的class_device_create函数,在/sys/class目录创建了相应的设备类目录/sys/class/mtd/
再看一下块设备部分:
static void blktrans_notify_add(struct mtd_info *mtd)
list_for_each(this, &blktrans_majors) {
struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
tr->add_mtd(tr, mtd); //调用到了drivers\mtd\mtdblock.c的mtdblock_add_mtd函数
}
最后再进到mtdblock_add_mtd()函数里面的各种调用关系
mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
add_mtd_blktrans_dev(dev);
//下面就是我们熟悉的,块设备书写的流程了
struct gendisk *gd;
gd = alloc_disk(1 << tr->part_bits);
add_disk(gd);
可以看出来,还是我们熟悉的块设备驱动程序的书写套路。
结语
虽然前面写了很多的内容,而且kernel中代码的流程也非常的复杂,但其实很大一部分是前人为我们抽象出来的一套框架,我们需要做的主要是底层硬件的操作。
在分析流程的时候,我们不难看出,我们要书写一个新的驱动程序,主要就参考probe函数中的部分就好了。下面给出一个别人总结的驱动框架流程图: