歡迎您光臨本站 註冊首頁

Linux 2.6.11 MTD驅動情景分析

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

原創:Allen
最近幾天為了熟悉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.size; //改了size
slave->mtd.oobblock = master->oobblock; //複製了主分區的信息
slave->mtd.name = parts.name; //改了名字
slave->mtd.bank_size = master->bank_size; //複製了主分區的信息
slave->mtd.read = part_read; //這個是自己的,下面分析看看
if(parts.mtdp)
{ /* store the object pointer (caller may or may not register it */
*parts.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) {
struct list_head *this;

mtd_table = 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)
new->add(mtd_table);

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開始。

[火星人 ] Linux 2.6.11 MTD驅動情景分析已經有668次圍觀

http://coctec.com/docs/linux/show-post-187414.html