基於MTD的NAND驅動開發(二)

四、基於MTD的NAND 驅動架構
 
1platform_deviceplatform_driver 的定義和註冊
 
對於我們的NAND driver ,以下是一個典型的例子:

 static struct platform_driver caorr_nand_driver = {
               . driver = {
                                . name = " caorr-nand" ,
                                . owner = THIS_MODULE,
                } ,
                . probe = caorr_nand_probe,
                . remove = caorr_nand_remove,
 } ;

 static int __init caorr_nand_init( void )
 {
                printk( "CAORR NAND Driver, (c) 2008-2009./n" ) ;
                return platform_driver_register( & caorr_nand_driver) ;
 }

 static void __exit caorr_nand_exit( void )
 {
                platform_driver_unregister( & caorr_nand_driver) ;
 }

 module_init( caorr_nand_init) ;
 module_exit( caorr_nand_exit) ;


 
與大多數嵌入式Linux 驅動一樣,NAND 驅動也是從module_init 宏開始。 caorr_nand_init 是驅動初始化函數,在此函數中註冊platform driver 結構體,platform driver 結構體中自然需要定義proberemove 函數。其實在大多數嵌入式Linux 驅動中,這樣的套路基本已經成了一個定式
 
至於module_init 有什麼作用,caorr_nand_probe 又是何時調用的,以及這個driver 是怎麼和NAND 設備聯繫起來的,就不再多說了,這裏只提三點:
 
A、 以上代碼只是向內核註冊了NANDplatform_driver ,即caorr_nand_driver ,我們當然還需要一個NANDplatform_device ,要不然caorr_nand_driverprobe 函數就永遠不會被執行,因爲沒有device 需要這個driver
 
B、Linux 內核註冊NANDplatform_device 有兩種方式:
其一是直接定義一個NANDplatform_device 結構體,然後調用platform_device_register 函數註冊。作爲例子,我們可以這樣定義NANDplatform_device 結構體:

 struct platform_device caorr_nand_device = {
          . name = "caorr-nand" ,
          . id = - 1,
          . num_resources = 0,
          . resource = NULL ,
          . dev = {
              . platform_data = & caorr_platform_default_nand,
          }
 } ;
 platform_device_register( & caorr_nand_device) ;

其中num_resourcesresource 與具體的硬件相關,主要包括一些寄存器地址範圍和中斷的定義。caorr_platform_default_nand 待會兒再說。需要注意的是,這個platform_devicename 的值必須與platform_driver->driver->name 的值完全一致,因爲platform_bus_typematch 函數是根據這兩者的name 值來進行匹配的。

其二是用platform_device_alloc 函數動態分配一個platform_device ,然後再用platform_device_add 函數把這個platform_device 加入到內核中去。具體不再細說,Linux 內核中有很多例子可以參考。
相對來說,第一種方式更加方便和直觀一點,而第二種方式則更加靈活一點。
 
C、 在加載NAND 驅動時,我們還需要向MTD Core 提供一個信息,那就是NAND 的分區信息,caorr_platform_default_nand 主要就是起這個作用,更加詳細的容後再說。
 
2MTD 架構的簡單描述
 
MTD(memory technology device 存儲技術設備) 是用於訪問memory 設備(ROMflash )的Linux 的子系統。MTD 的主要目的是爲了使新的memory 設備的驅動更加簡單,爲此它在硬件和上層之間提供了一個抽象的接口。MTD 的所有源代碼在/drivers/mtd 子目錄下。MTD 設備可分爲四層(從設備節點直到底層硬件驅動),這四層從上到下依次是:設備節點、MTD 設備層、MTD 原始設備層和硬件驅動層。

A、Flash硬件驅動層:硬件驅動層負責驅動Flash硬件。
 
B、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 中)。
 
C、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一一對應。
 
D、設備節點:通過mknod在/dev子目錄下建立MTD字符設備節點(主設備號爲90)和MTD塊設備節點(主設備號爲31),通過訪問此設備節點即可訪問MTD字符設備和塊設備。
 
E、根文件系統:在Bootloader中將JFFS(或JFFS2)的文件 系統映像jffs.image(或jffs2.img)燒到flash的某一個分區中,在/arch/arm/mach-your/arch.c文件的 your_fixup函數中將該分區作爲根文件系統掛載。
 
F、文件系統:內核啓動後,通過mount 命令可以將flash中的其餘分區作爲文件系統掛載到mountpoint上。
 
以上是從網上找到的一些資料,我只是斷斷續續地看過一些code,沒有系統地研究過,所以這裏只能講一下MTD原始設備層與FLASH硬件驅動之間的交互。
 
一個MTD原始設備可以通過mtd_part分割成數個MTD原 始設備註冊進mtd_table,mtd_table中的每個MTD原始設備都可以被註冊成一個MTD設備,有兩個函數可以完成這個工作,即 add_mtd_device函數和add_mtd_partitions函數。
 
其中add_mtd_device函數是把整個NAND FLASH註冊進MTD Core,而add_mtd_partitions函數則是把NAND FLASH的各個分區分別註冊進MTD Core。
 
add_mtd_partitions函數的原型是:

int add_mtd_partitions( struct mtd_info * master,

             const struct mtd_partition * parts, int nbparts) ;

 
其中master就是這個MTD原始設備,parts即NAND的分區信息,nbparts指有幾個分區。那麼parts和nbparts怎麼來?caorr_platform_default_nand 就是起這個作用了。

 static struct mtd_partition caorr_platform_default_nand[ ] = {
    [ 0] = {
               . name = "Boot Strap" ,
               . offset = 0,
               . size = 0x40000,
    } ,
    [ 1] = {
               . name = "Bootloader" ,
               . offset = MTDPART_OFS_APPEND,
               . size = 0x40000,
    } ,
    [ 2] = {
               . name = "Partition Table" ,
               . offset = MTDPART_OFS_APPEND,
               . size = 0x40000,
    } ,
    [ 3] = {
               . name = "Linux Kernel" ,
               . offset = MTDPART_OFS_APPEND,
               . size = 0x500000,
    } ,
    [ 4] = {
               . name = "Rootfs" ,
               . offset = MTDPART_OFS_APPEND,
               . size = MTDPART_SIZ_FULL,
    } ,
 } ;

 
其中offset是分區開始的偏移地址,在後4個分區我們設爲 MTDPART_OFS_APPEND,表示緊接着上一個分區,MTD Core會自動計算和處理分區地址;size是分區的大小,在最後一個分區我們設爲MTDPART_SIZ_FULL,表示這個NADN剩下的所有部分。
 
這樣配置NAND的分區並不是唯一的,需要視具體的系統而定,我們可以在kernel中這樣顯式的指定,也可以使用bootloader傳給內核的參數進行配置。
 
另外,MTD對NAND芯片的讀寫主要分三部分:
 
A、struct mtd_info中的讀寫函數,如read,write_oob等,這是MTD原始設備層與FLASH硬件層之間的接口;
 
B、struct nand_ecc_ctrl中的讀寫函數,如read_page_raw,write_page等,主要用來做一些與ecc有關的操作;
 
C、struct nand_chip中的讀寫函數,如read_buf,cmdfunc等,與具體的NAND controller相關,就是這部分函數與硬件交互,通常需要我們自己來實現。(注:這裏提到的read,write_oob,cmdfunc等,其實 都是些函數指針,所以這裏所說的函數,是指這些函數指針所指向的函數,以後本文將不再另做說明。)
 
值得一提的是,struct nand_chip中的讀寫函數雖然與具體的NAND controller相關,但是MTD也爲我們提供了default的讀寫函數,如果你的NAND controller比較通用(使用PIO模式),對NAND芯片的讀寫與MTD提供的這些函數一致,就不必自己實現這些函數了。
 
這三部分讀寫函數是相互配合着完成對NAND芯片的讀寫的。首 先,MTD上層需要讀寫NAND芯片時,會調用struct mtd_info中的讀寫函數,接着struct mtd_info中的讀寫函數就會調用struct nand_chip或struct nand_ecc_ctrl中的讀寫函數,最後,若調用的是struct nand_ecc_ctrl中的讀寫函數,那麼它又會接着調用struct nand_chip中的讀寫函數。如下圖所示:

以讀NAND芯片爲例,講解一下這三部分讀寫函數的工作過程。
 
首先,MTD上層會調用struct mtd_info中的讀page函數,即nand_read函數。
 
接着nand_read函數會調用struct nand_chip中cmdfunc函數,這個cmdfunc函數與具體的NAND controller相關,它的作用是使NAND controller向NAND 芯片發出讀命令,NAND芯片收到命令後,就會做好準備等待NAND controller下一步的讀取。
 
接着nand_read函數又會調用struct nand_ecc_ctrl中的read_page函數,而read_page函數又會調用struct nand_chip中read_buf函數,從而真正把NAND芯片中的數據讀取到buffer中(所以這個read_buf的意思其實應該是read into buffer,另外,這個buffer是struct mtd_info中的nand_read函數傳下來的)。
 
read_buf函數返回後,read_page函數就會對buffer中的數據做一些處理,比如校驗ecc,以及若數據有錯,就根據ecc對數據修正之類的,最後read_page函數返回到nand_read函數中。
 
對NAND芯片的其它操作,如寫,擦除等,都與讀操作類似。
 
發佈了47 篇原創文章 · 獲贊 6 · 訪問量 48萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章