最近几天为了熟悉linux的驱动开发,我选择了其MTD驱动做了一些研究。我能找到的文章中我觉得有些部分不够细致,所以我还是自己写了一部分分析,希望对别人也能有所帮助,也做为自己的一个备忘,。蓝色文字的部分是从网络上摘录的。
一个嵌入式系统经常会使用NOR flash 或NAND flash来存放bootload,内核和文件系统等等。
下面是网络上找到的linux下的mtd驱动的分析:
一、Flash硬件驱动层:硬件驱动层负责在init时驱动Flash硬件,Linux MTD设备的NORFlash芯片驱动遵循CFI接口标准,其驱动程序位于drivers/mtd/chips子目录下。NAND型Flash的驱动程序则位于/drivers/mtd/nand子目录下
二、MTD原始设备:原始设备层有两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定的Flash的数据,例如分区。
用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量的关于MTD的数据和操作函数。mtd_table(mtdcore.c)则是所 有MTD原始设备的列表,mtd_part(mtd_part.c)是用于表示MTD原始设备分区的结构,其中包含了mtd_info,因为每一个分区都 是被看成一个MTD原始设备加在mtd_table中的,mtd_part.mtd_info中的大部分数据都从该分区的主分区mtd_part- >master中获得。
在drivers/mtd/maps/子目录下存放的是特定的flash的数据,每一个文件都描述了一块板子上的flash。其中调用 add_mtd_device()、del_mtd_device()建立/删除mtd_info结构并将其加入/删除mtd_table(或者调用 add_mtd_partition()、del_mtd_partition()(mtdpart.c)建立/删除mtd_part结构并将 mtd_part.mtd_info加入/删除mtd_table 中)。
三、MTD设备层: 基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。MTD字符设备的定义在mtdchar.c中实 现,通过注册一系列file operation函数(lseek、open、close、read、write)。MTD块设备则是定义了一个描述MTD块设备的结构 mtdblk_dev,并声明了一个名为mtdblks的指针数组,这数组中的每一个mtdblk_dev和mtd_table中的每一个 mtd_info一一对应。
四、设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和MTD块设备节点(主设备号为31),通过访问此设备节点即可访问MTD字符设备和块设备。
五、根文件系统:在Bootloader中将JFFS(或JFFS2)的文件系统映像jffs.image(或jffs2.img)烧到flash的某一个分区中,在/arch/arm/mach-your/arch.c文件的your_fixup函数中将该分区作为根文件系统挂载。
六、文件系统:内核启动后,通过mount 命令可以将flash中的其余分区作为文件系统挂载到mountpoint上。
NOR型Flash芯片驱动与MTD原始设备
所有的NOR型Flash的驱动(探测probe)程序都放在drivers/mtd/chips下,一个MTD原始设备可以由一块或者数块相同的 Flash芯片组成。假设由4块devicetype为x8的Flash,每块大小为8M,interleave为2,起始地址为0x01000000, 地址相连,则构成一个MTD原始设备(0x01000000-0x03000000),其中两块interleave成一个chip,其地址从 0x01000000到0x02000000,另两块interleave成一个chip,其地址从0x02000000到0x03000000。
请注意,所有组成一个MTD原始设备的Flash芯片必须是同类型的(无论是interleave还是地址相连),在描述MTD原始设备的数据结构中也只是采用了同一个结构来描述组成它的Flash芯片。
每个MTD原始设备都有一个mtd_info结构,其中的priv指针指向一个map_info结构,map_info结构中的fldrv_priv指向一个cfi_private结构,cfi_private结构的cfiq指针指向一个cfi_ident结构,chips指针指向一个flchip结 构的数组。其中mtd_info、map_info和cfi_private结构用于描述MTD原始设备;因为组成MTD原始设备的NOR型Flash相 同,cfi_ident结构用于描述Flash芯片的信息;而flchip结构用于描述每个Flash芯片的专有信息(比如说起始地址)
总 的来说,嵌入式系统中一般来说会有一块或多块连续的NOR flash或NAND flash空间(每一个可能是多块相同的芯片来构成)每一个这样的空间被看成一个MTD原始设备(我不知道这个名字谁起的,我也这么用吧)根据一些文章和 代码中使用的变量名,我后面称呼它为主分区。你可以按照自己的需要把主分区分成几个区,我的开发板用的分区信息如下:
来自alchemy_flash.c:
static struct mtd_partition alchemy_partitions[] = {
{
.name = "User FS", //这里给根文件系统
.size = BOARD_FLASH_SIZE - 0x00400000,
.offset = 0x0000000
},{
.name = "YAMON",//这块给bootloader
.size = 0x0100000,
.offset = MTDPART_OFS_APPEND, //表示接着上一个分区
.mask_flags = MTD_WRITEABLE
},{
.name = "raw kernel",
.size = (0x300000 - 0x40000), /* last 256KB is yamon env */ //这块给自解压 //的压缩内核,最后留了点给booterloader的环境变量,它没有被设备驱动使用,而是由booterloader以自己的方式访问。
.offset = MTDPART_OFS_APPEND,
}
};
如果你增加或者是减少了你的flash空间(通过增加或减少flash芯片)或则你想调整几个分区的大小,你只需要修改这个表就可以了。
如果你还有一块NAND区,那么你可能有如下的分区表(au1550nd.c):
const static struct mtd_partition partition_info[] = {
{
.name = "NAND FS 0",
.offset = 0,
.size = 8*1024*1024
},
{
.name = "NAND FS 1",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL
}
};
整个alchemy_flash.c就两个函数: alchemy_mtd_init(void)和 alchemy_mtd_cleanup()。
int __init alchemy_mtd_init(void)
{
struct mtd_partition *parts;
int nb_parts = 0;
unsigned long window_addr;
unsigned long window_size;
/* Default flash buswidth */
alchemy_map.bankwidth = BOARD_FLASH_WIDTH;
window_addr = 0x20000000 - BOARD_FLASH_SIZE;
window_size = BOARD_FLASH_SIZE;
#ifdef CONFIG_MIPS_MIRAGE_WHY
/* Boot ROM flash bank only; no user bank */
window_addr = 0x1C000000;
window_size = 0x04000000;
/* USERFS from 0x1C00 0000 to 0x1FC00000 */
alchemy_partitions[0].size = 0x03C00000;
#endif
/*
* Static partition definition selection
*/
parts = alchemy_partitions;
nb_parts = NB_OF(alchemy_partitions);
alchemy_map.size = window_size;
/*
* Now let's probe for the actual flash. Do it here since
* specific machine settings might have been set above.
*/
printk(KERN_NOTICE BOARD_MAP_NAME ": probing %d-bit flash bus\n",
alchemy_map.bankwidth*8);
alchemy_map.virt = ioremap(window_addr, window_size);
mymtd = do_map_probe("cfi_probe", &alchemy_map);
if (!mymtd) {
iounmap(alchemy_map.virt);
return -ENXIO;
}
mymtd->owner = THIS_MODULE;
add_mtd_partitions(mymtd, parts, nb_parts);
return 0;
}
看看红色的区域,do_map_probe返回了一个mtd_info结构指针。那么表明这个函数在正确找到你的驱动(cfi驱动)后会填好这个表把其中的读写函数等设置到正确的值,具体的实现放到以后分析吧,其中的一个map_info参数,我暂时没有完全读懂,因为我以前不曾研究flash的底层驱动,你需要设置好bankwidth和基地址,和驱动名字,我只能通过一些信息猜测他用来管理这个主分区并给底层驱动使用的,如坏块信息就保存在这里。
最 后是调用add_mtd_partitions根据你设置的分区表和主分区的mtd_info来分区。其实如果你不打算给这个主分区分成几个区,你可以直 接把这个mtd_info加到mtd_table而不需要mtdpart.c中的处理。这是后话,先沿着这条路分析一下:这个函数跳到了文件 mtdpart.c。当你的把主分区分成几个区以后这几个分区的mtd_info和你的主分区是大部分是一样的,只是在size,name等方面不一样。 add_mtd_partitions函数太大了点,全部贴出来不合适,我挑几行吧。
slave->mtd.type = master->type; //复制了主分区的信息
slave->mtd.size = parts[i].size; //改了size
slave->mtd.oobblock = master->oobblock; //复制了主分区的信息
slave->mtd.name = parts[i].name; //改了名字
slave->mtd.bank_size = master->bank_size; //复制了主分区的信息
slave->mtd.read = part_read; //这个是自己的,下面分析看看
if(parts[i].mtdp)Hj6Linux联盟
{ /* store the object pointer (caller may or may not register it */Hj6Linux联盟
*parts[i].mtdp = &slave->mtd;Hj6Linux联盟
slave->registered = 0;//你也可以不加进mtd_table中Hj6Linux联盟
}Hj6Linux联盟
elseHj6Linux联盟
{Hj6Linux联盟
/* register our partition */Hj6Linux联盟
add_mtd_device(&slave->mtd););//最后是这个,加进mtd_table中的是而不是那个包含它的结构mtd_partHj6Linux联盟
slave->registered = 1;Hj6Linux联盟
}Hj6Linux联盟
Hj6Linux联盟
static int part_read (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct mtd_part *part = PART(mtd);
if (from >= mtd->size)
len = 0;
else if (from + len > mtd->size)
len = mtd->size - from;
if (part->master->read_ecc == NULL)
return part->master->read (part->master, from + part->offset,
len, retlen, buf);
else
return part->master->read_ecc (part->master, from + part->offset,
len, retlen, buf, NULL, &mtd->oobinfo);
}
这下清楚了:还是调用主分区的读函数,就是加上了偏移量而已。
add_mtd_partitions 的最后调用add_mtd_device(&slave->mtd);把每个分区加进了mtd_table。这个函数在mtdcore.c 中,前面提到的如果不分区,你可以直接把主分区做为参数加到mtd_table中。
Mtdcore.c 如其名,它叫core是有原因的:它不调用mtd驱动各层次的任何外部函数(除了注册的回调函数)而只是输出函数,它管理着mtd_table。底层通过 add_mtd_device和del_mtd_device来添加和删除原始设备(分区)。上层字符设备和块设备部分通过get_mtd_device 和put_mtd_device来申请和释放分区等等。
int add_mtd_device(struct mtd_info *mtd)
{
int i;
down(&mtd_table_mutex);
for (i=0; i < MAX_MTD_DEVICES; i++)
if (!mtd_table[i]) {
struct list_head *this;
mtd_table[i] = mtd;
mtd->index = i;
mtd->usecount = 0;
DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
list_for_each(this, &mtd_notifiers) {
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
not->add(mtd);//我的观点是如果你不打算动态增加和删除设备的话这一
//部分是没有必要的。系统初始化mtd设备时,这个链表也是空的。
}
up(&mtd_table_mutex);
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
either. :) */
__module_get(THIS_MODULE);
return 0;
}
up(&mtd_table_mutex);
return 1;
}
整 个函数比较简单,在mtd_table中选择一个空位置放置你的分区的mtd_info。红色部分是比较难懂一点的:这个mtd_notifiers链表 起什么作用?看看谁在这个链表中加了东西。register_mtd_user是唯一向这个链表添加了成员的,谁调了?看一下下面的程序
void register_mtd_user (struct mtd_notifier *new)
{
int i;
down(&mtd_table_mutex);
list_add(&new->list, &mtd_notifiers);
__module_get(THIS_MODULE);
for (i=0; i< MAX_MTD_DEVICES; i++)
if (mtd_table[i])
new->add(mtd_table[i]);
up(&mtd_table_mutex);
}
static inline void mtdchar_devfs_init(void)
{
devfs_mk_dir("mtd");
register_mtd_user(¬ifier);
}
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
int ret, i;
/* Register the notifier if/when the first device type is
registered, to prevent the link/init ordering from fucking
us over. */
if (!blktrans_notifier.list.next)
register_mtd_user(&blktrans_notifier);
。。。。。省略n行
return 0;
}
看来这个链表里面通常就只有两个成员。作用是当删除或添加一个mtd分区时告诉建立在其上的字符设备和块设备驱动。干什么?
当创建mtd字符设备时会调用mtdchar_devfs_init,他的notifier是这样的:
static struct mtd_notifier notifier = {
.add = mtd_notify_add,
.remove = mtd_notify_remove,
};
static void mtd_notify_add(struct mtd_info* mtd)
{
if (!mtd)
return;
devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
S_IFCHR | S_IRUGO | S_IWUGO, "mtd/%d", mtd->index);
devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
S_IFCHR | S_IRUGO, "mtd/%dro", mtd->index);
}
这个devfs_mk_cdev应该就是自动创建devfs的节点吧,是不是就是不用再mknod了?我真的是linux新手,如果有人知道请不吝赐教,我只是从名字上猜测的。
总结一下:
以我的理解是上面的mtd设备驱动架构分析对于移植来说大都不是特别重要,除了那个分区表。但是当你更换了flash芯片型号时,我该如何做?我粗略地看了一下驱动部分,参考了下面这个链接的文章:http://os.yesky.com/lin/233/3386733.shtml 高手进阶 Linux系统下MTD/CFI驱动介绍。
我只是粗粗了解了一下所谓CFI(Common Flash Interface)。是否真的这个驱动就可以解决所有问题了?改天好好trace一下,就从那个do_map_probe开始。