最近幾天為了熟悉linux的驅動開發,我選擇了其MTD驅動做了一些研究。我能找到的文章中我覺得有些部分不夠細緻,所以我還是自己寫了一部分分析,希望對別人也能有所協助,也做為自己的一個備忘,。藍色文字的部分是從網路上摘錄的。 一個嵌入式系統經常會使用NOR flash 或NAND flash來存放bootload,核心和檔案系統等等。下面是網路上找到的linux下的mtd驅動的分析:
一、Flash
硬體驅動層:硬體驅動層負責在init時驅動Flash硬體,Linux MTD裝置的NOR Flash晶片驅動遵循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開始。