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或NANDflash空间(每一个可能是多块相同的芯片来构成)每一个这样的空间被看成一个MTD原始设备(我不知道这个名字谁起的,我也这么用吧)根据一些文章和代码中使用的变量名,我后面称呼它为主分区。你可以按照自己的需要把主分区分成几个区,我的开发板用的分区信息如下: 来自alchemy_flash.c: static struct mtd_partitionalchemy_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),//这块给自解压 //的压缩内核,最后留了点给booterloader的环境变量,它没有被设备驱动使用,而是由booterloader以自己的方式访问。 .offset = MTDPART_OFS_APPEND, } }; 如果你增加或者是减少了你的flash空间(通过增加或减少flash芯片)或则你想调整几个分区的大小,你只需要修改这个表就可以了。 如果你还有一块NAND区,那么你可能有如下的分区表(au1550nd.c): const static struct mtd_partitionpartition_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; alchemy_map.bankwidth = BOARD_FLASH_WIDTH; window_addr = 0x20000000 - BOARD_FLASH_SIZE; window_size = BOARD_FLASH_SIZE; #ifdef CONFIG_MIPS_MIRAGE_WHY window_addr = 0x1C000000; window_size = 0x04000000; alchemy_partitions[0].size = 0x03C00000; #endif parts = alchemy_partitions; nb_parts = NB_OF(alchemy_partitions); alchemy_map.size = window_size; printk(KERN_NOTICE BOARD_MAP_NAME ": probing %d-bit flashbus/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){
*parts[i].mtdp= &slave->mtd;
slave->registered= 0;//你也可以不加进mtd_table中
}
else
{
add_mtd_device(&slave->mtd););//最后是这个,加进mtd_table中的是而不是那个包含它的结构mtd_part
slave->registered= 1;
}
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) returnpart->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); 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); __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 voidmtdchar_devfs_init(void) { devfs_mk_dir("mtd"); register_mtd_user(¬ifier); } int register_mtd_blktrans(structmtd_blktrans_ops *tr) { int ret, i; 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 FlashInterface)。是否真的这个驱动就可以解决所有问题了?改天好好trace一下,就从那个do_map_probe开始。