Linux 2.6.11 MTD驅動情景分析

最近幾天爲了熟悉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)
   { /* store the object pointer (caller may or may not register it */
    *parts[i].mtdp = &slave->mtd;
    slave->registered = 0;//你也可以不加進mtd_table中
   }
   else
   {
    /* register our partition */
    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)
              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(&notifier);
}
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開始。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章