1. 程式人生 > >linux NAND驅動之一:核心中的NAND程式碼佈局

linux NAND驅動之一:核心中的NAND程式碼佈局

在Linux 核心中,MTD 原始碼放在/driver/mtd 目錄中,該目錄中包含chips 、devices 、maps 、nand 、onenand 和ubi 六個子目錄。其中只有nand 和onenand 目錄中的程式碼才與NAND 驅動相關,不過nand 目錄中的程式碼比較通用,而onenand 目錄中的程式碼相對於nand 中的程式碼而言則簡化了很多,它是針對三星公司開發的另一類Flash晶片,即OneNAND Flash,是一種較常用NAND先進的FLASH吧,只是目前似乎普及率並不高,本文也將不做討論。
      因此,若只是開發基於MTD 的NAND 驅動程式,那麼我們需要關注的程式碼就基本上全在drivers/mtd/nand 目錄中了,而該目錄中也不是所有的程式碼檔案都與我們將要開發的NAND 驅動有關,除了Makefile 和Kconfig 之外,其中真正與NAND 驅動有關的程式碼檔案只有6 個,即:
1)nand_base.c :
      定義了NAND 驅動中對NAND 晶片最基本的操作函式和操作流程,如擦除、讀寫page 、讀寫oob 等。當然這些函式都只是進行一些default 的操作,若你的系統在對NAND 操作時有一些特殊的動作,則需要在你自己的驅動程式碼中進行定義,然後Replace 這些default 的函式。
2)nand_bbt.c :
定義了NAND 驅動中與壞塊管理有關的函式和結構體。
3)nand_ids.c :
      定義了兩個全域性型別的結構體:struct nand_flash_dev nand_flash_ids[ ] 和struct nand_manufacturers nand_manuf_ids[ ] 。其中前者定義了一些NAND 晶片的型別,後者定義了NAND 晶片的幾個廠商。NAND 晶片的ID 至少包含兩項內容:廠商ID 和廠商為自己的NAND 晶片定義的晶片ID 。當NAND 驅動被載入的時候,它會去讀取具體NAND 晶片的ID ,然後根據讀取的內容到上述定義的nand_manuf_ids[ ] 和nand_flash_ids[ ] 兩個結構體中去查詢,以此判斷該NAND 晶片是那個廠商的產品,以及該NAND 晶片的型別。若查詢不到,則NAND 驅動就會載入失敗,因此在開發NAND 驅動前必須事先將你的NAND 晶片新增到這兩個結構體中去(其實這兩個結構體中已經定義了市場上絕大多數的NAND 晶片,所以除非你的NAND 晶片實在比較特殊,否則一般不需要額外新增)。

     值得一提的是,nand_flash_ids[ ] 中有三項屬性比較重要,即pagesize 、chipsize 和erasesize ,驅動就是依據這三項屬性來決定對NAND 晶片進行擦除,讀寫等操作時的大小的。其中pagesize 即NAND 晶片的頁大小,一般為256 、512 或2048 ;chipsize 即NAND 晶片的容量;erasesize 即每次擦除操作的大小,通常就是NAND 晶片的block 大小。
4)nand_ecc.c :
      定義了NAND 驅動中與softeware ECC 有關的函式和結構體,若你的系統支援hardware ECC ,且不需要software ECC ,則該檔案也不需理會。
5)nandsim.c :
      定義了Nokia 開發的模擬NAND 裝置,預設是Toshiba NAND 8MiB 1,8V 8-bit (根據ManufactureID ),開發普通NAND 驅動時不用理會。
6)diskonchip.c :
      定義了片上磁碟(DOC) 相關的一些函式,開發普通NAND 驅動時不用理會。
除了上述六個檔案之外,nand 目錄中其他檔案基本都是特定系統的NAND 驅動程式例子,但看來真正有參考價值的還有cafe_nand.c 和s3c2410.c 兩個,而其中又尤以cafe_nand.c 更為詳細,另外,nand 目錄中也似乎只有cafe_nand.c 中的驅動程式在讀寫NAND 晶片時用到了DMA 操作

四、基於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晶片的其它操作,如寫,擦除等,都與讀操作類似。  在基於MTD 的NAND driver 的probe 函式中,主要可以分為兩部分內容,其一是與很多外設driver 類似的一些工作,如申請地址,中斷,DMA 等資源,kzalloc 及初始化一些結構體,分配DMA 用的記憶體等等;其二就是與MTD 相關的一些特定的工作,在這裡我們將只描述第二部分內容。

(1)probe 函式中與MTD 相關的結構體
      在probe 函式中,我們需要為三個與MTD 相關的結構體分配記憶體以及初始化,它們是struct mtd_info 、struct mtd_partition 和struct nand_chip 。其中前兩者已經在四節做過說明,這裡只對struct nand_chip 做一些介紹。struct nand_chip 是一個與NAND 晶片密切相關的結構體,主要包含三方面內容:

      A)指向一些操作NAND 晶片的函式的指標,稍後將對這些函式指標作一些說明;
      B)表示NAND 晶片特性的成員變數,主要有:
unsigned int options :與具體的NAND 晶片相關的一些選項,如NAND_BUSWIDTH_16 等,可以參考<linux/mtd/nand.h>
int page_shift :用位表示的NAND 晶片的page 大小,如某片NAND 晶片的一個page 有512 個位元組,那麼page_shift 就是9 ;
int phys_erase_shift :用位表示的NAND 晶片的每次可擦除的大小,如某片NAND 晶片每次可擦除16K 位元組( 通常就是一個block 的大小) ,那麼phys_erase_shift 就是14 ;
int bbt_erase_shift :用位表示的bad block table 的大小,通常一個bbt 佔用一個block ,所以bbt_erase_shift 通常與phys_erase_shift 相等;
int   numchips :表示系統中有多少片NAND 晶片;
unsigned long chipsize :NAND 晶片的大小;
int   pagemask :計算page number 時的掩碼,總是等於chipsize/page 大小 - 1 ;
int   pagebuf :用來儲存當前讀取的NAND 晶片的page number ,這樣一來,下次讀取的資料若還是屬於同一個page ,就不必再從NAND 晶片讀取了,而是從data_buf 中直接得到;
int   badblockpos :表示壞塊資訊儲存在oob 中的第幾個位元組。對於絕大多數的NAND 晶片,若page size  > 512 ,那麼壞塊資訊從Byte 0 開始儲存,否則就儲存在Byte 5 ,即第六個位元組。
C.  與ecc ,oob 和bbt (bad block table) 相關的一些結構體,對於壞塊及壞塊管理,將在稍後做專門介紹。

(2)對NAND 晶片進行實際操作的函式
     前面已經說過,MTD 為我們提供了許多default 的操作NAND 的函式,這些函式與具體的硬體( 即NAND controller) 相關,而現有的NAND controller 都有各自的特性和配置方式,MTD 當然不可能為所有的NAND controller 都提供一套這樣的函式,所以在MTD 中定義的這些函式只適用於通用的NAND controller( 使用PIO 模式) 。 
      如果你的NAND controller 在操作或者說讀寫NAND 時有自己獨特的方式,那就必須自己定義適用於你的NAND controller 的函式。一般來說,這些與硬體相關的函式都在struct nand_chip 結構體中定義,或者應該說是給此結構體中的函式指標賦值。
 struct nand_chip {
    void __iomem * IO_ADDR_R;
    void __iomem * IO_ADDR_W;

    uint8_t ( * read_byte) ( struct mtd_info * mtd) ;
    u16 ( * read_word) ( struct mtd_info * mtd) ;
    void ( * write_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
    void ( * read_buf) ( struct mtd_info * mtd, uint8_t * buf, int len) ;
    int ( * verify_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
    void ( * select_chip) ( struct mtd_info * mtd, int chip) ;
    int ( * block_bad) ( struct mtd_info * mtd, loff_t ofs, int getchip) ;
    int ( * block_markbad) ( struct mtd_info * mtd, loff_t ofs) ;
    void ( * cmd_ctrl) ( struct mtd_info * mtd, int dat, unsigned int ctrl) ;
    int ( * dev_ready) ( struct mtd_info * mtd) ;
    void ( * cmdfunc) ( struct mtd_info * mtd, unsigned command, int column, int page_addr) ;
    int ( * waitfunc) ( struct mtd_info * mtd, struct nand_chip * this ) ;
    void ( * erase_cmd) ( struct mtd_info * mtd, int page) ;
    int ( * scan_bbt) ( struct mtd_info * mtd) ;
    int ( * errstat) ( struct mtd_info * mtd, struct nand_chip * this , int state, int status, int page) ;
    int ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf, int page, int cached, int raw) ;

    ……

    struct nand_ecc_ctrl ecc;

    ……
}

IO_ADDR_R 和IO_ADDR_W :8 位NAND 晶片的讀寫地址,如果你的NAND controller 是用PIO 模式與NAND 晶片互動,那麼只要把這兩個值賦上合適的地址,就完全可以使用MTD 提供的default 的讀寫函式來操作NAND 晶片了。所以這兩個變數視具體的NAND controller 而定,不一定用得著;
read_byte 和read_word :從NAND 晶片讀一個位元組或一個字,通常MTD 會在讀取NAND 晶片的ID ,STATUS 和OOB 中的壞塊資訊時呼叫這兩個函式,具體是這樣的流程,首先MTD 呼叫cmdfunc 函式,發起相應的命令,NAND 晶片收到命令後就會做好準備,最後MTD 就會呼叫read_byte 或read_word 函式從NAND 晶片中讀取晶片的ID ,STATUS 或者OOB ;
read_buf 、write_buf 和verify_buf:分別是從NAND 晶片讀取資料到buffer、把buffer 中的資料寫入到NAND 晶片、和從NAND 晶片中讀取資料並驗證。呼叫read_buf 時的流程與read_byte 和read_word 類似,MTD 也是先呼叫cmdfunc 函式發起讀命令( 如NAND_CMD_READ0 命令) ,接著NAND 晶片收到命令後做好準備,最後MTD 再呼叫read_buf 函式把NAND 晶片中的資料讀取到buffer 中。呼叫write_buf 函式的流程與read_buf 相似;
select_chip :因為系統中可能有不止一片NAND 晶片,所以在對NAND 晶片進行操作前,需要這個函式來指定一片NAND 晶片;
cmdfunc :向NAND 晶片發起命令;
waitfunc :NAND 晶片在接收到命令後,並不一定能立即響應NAND controller 的下一步動作,對有些命令,比如erase ,program 等命令,NAND 晶片需要一定的時間來完成,所以就需要這個waitfunc 來等待NAND 晶片完成命令,並再次進入準備好狀態;
write_page :把一個page 的資料寫入NAND 晶片,這個函式一般不需我們實現,因為它會呼叫struct nand_ecc_ctrl 中的write_page_raw 或者write_page 函式,關於這兩個函式將在稍後介紹。
      以上提到的這些函式指標,都是REPLACEABLE 的,也就是說都是可以被替換的,根據你的NAND controller ,如果你需要自己實現相應的函式,那麼只需要把你的函式賦值給這些函式指標就可以了,如果你沒有賦值,那麼MTD 會把它自己定義的default 的函式賦值給它們。在原生代碼上,以上函式指標都採用的預設的方式,通過s3c_nand_probe-》nand_scan-》nand_scan_ident-》nand_set_defaults,在該函式中以上的函式指標都被nand_base.c定義的預設函式賦值。
      順便提一下,以上所說的讀寫NAND 晶片的流程並不是唯一的,如果你的NAND controller 在讀寫NAND 晶片時有自己獨特的方式,那麼完全可以按照自己的方式來做。就比如我們公司晶片的NAND controller ,因為它使用DMA 的方式從NAND 晶片中讀寫資料,所以在我的NAND driver 中,讀資料的流程是這樣的:首先在cmdfunc 函式中初始化DMA 專用的buffer ,配置NAND 地址,發起命令等,在cmdfunc 中我幾乎做了所有需要與NAND 晶片互動的事情,總之等cmdfunc 函式返回後,NAND 晶片中的資料就已經在DMA 專用的buffer 中了,之後MTD 會再呼叫read_buf 函式,所以我的read_buf 函式其實只是把資料從DMA 專用的buffer 中,拷貝到MTD 提供的buffer 中罷了。

(3)最後,struct nand_chip 結構體中還包含了一個很重要的結構體,即struct  nand_ecc_ctrl ,它的定義如下:
struct nand_ecc_ctrl {
    ……
    void ( * hwctl) ( struct mtd_info * mtd, int mode) ;
    int ( * calculate) ( struct mtd_info * mtd, const uint8_t * dat, uint8_t * ecc_code) ;
    int ( * correct) ( struct mtd_info * mtd, uint8_t * dat, uint8_t * read_ecc, uint8_t * calc_ecc) ;
    int ( * read_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
    void ( * write_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
    int ( * read_page) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
    void ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
    int ( * read_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page, int sndcmd) ;
    int ( * write_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page) ;
} ;
hwctl :這個函式用來控制硬體產生ecc ,其實它主要的工作就是控制NAND controller 向NAND 晶片發出NAND_ECC_READ 、NAND_ECC_WRITE 和NAND_ECC_READSYN 等命令,與struct nand_chip 結構體中的cmdfunc 類似,只不過發起的命令是ECC 相關的罷了;
calculate :根據data 計算ecc 值;
correct :根據ecc 值,判斷讀寫資料時是否有錯誤發生,若有錯,則立即試著糾正,糾正失敗則返回錯誤;
read_page_raw 和write_page_raw :從NAND 晶片中讀取一個page 的原始資料和向NAND 晶片寫入一個page 的原始資料,所謂的原始資料,即不對讀寫的資料做ecc 處理,該讀寫什麼值就讀寫什麼值。另外,這兩個函式會讀寫整個page 中的所有內容,即不但會讀寫一個page 中MAIN 部分,還會讀寫OOB 部分。
read_page 和write_page :與read_page_raw 和write_page_raw 類似,但不同的是,read_page 和write_page 在讀寫過程中會加入ecc 的計算,校驗,和糾正等處理。
read_oob 和write_oob :讀寫oob 中的內容,不包括MAIN 部分。
      其實,以上提到的這幾個read_xxx 和write_xxx 函式,最終都會呼叫struct nand_chip 中的read_buf 和write_buf 這兩個函式,所以如果沒有特殊需求的話,我認為不必自己實現,使用MTD 提供的default 的函式即可

由前面的說明可知,我們在要對NAND 晶片進行實際操作前已經為struct mtd_info 、struct mtd_partition 和struct nand_chip 這三個結構體分配好了記憶體,接下來就要為它們做一些初始化工作。 其中,我們需要為struct mtd_info 所做的初始化工作並不多,因為MTD Core 會在稍後為它做很多初始化工作(這些工作在nand_scan_tail這個函式的後半部分來填充),但是有一點必須由我們來做,那就是把指向struct nand_chip 結構體的指標賦給struct mtd_info 的priv 成員變數,因為MTD Core 中很多函式之間的呼叫都只傳遞struct mtd_info ,它需要通過priv 成員變數得到struct nand_chip 。如下,在s3c_nand.c中:

       struct mtd_info *s3c_mtd = NULL;   //mtd_info結構體指標

       struct nand_chip *nand;                  //nand_chip結構體指標

       /* allocate memory for MTD device structure and private data */
       s3c_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);  //一起分記憶體

       nand = (struct nand_chip *) (&s3c_mtd[1]);  //得到劃分的記憶體

       memset((char *) s3c_mtd, 0, sizeof(struct mtd_info));     //初始化0
       memset((char *) nand, 0, sizeof(struct nand_chip));

       s3c_mtd->priv = nand;

      所以,為struct nand_chip 的初始化,才是我們在probe 函式中的主要工作。其實這裡所謂的初始化,主要就是為struct nand_chip 結構體中的眾多函式指標賦值。現在假定你定義好了所有需要的與NAND 晶片互動的函式,並已經把它們賦給了struct nand_chip 結構體中的函式指標。當然,此時你還不能保證這些函式一定能正確工作,但是沒有關係,probe 函式在接下來的工作中會呼叫到幾乎所有的這些函式,你可以依次來驗證和除錯。當你的probe 函式能順利通過後,那麼這些函式也就基本沒什麼問題了,你的NAND 驅動也就已經完成了80 %了。
      接下來,probe 函式就會開始與NAND 晶片進行互動了,它要做的事情主要包括這幾個方面:讀取NAND 晶片的ID ,然後查表得到這片NAND 晶片的如廠商,page size ,erase size 以及chip size 等資訊,接著,根據struct nand_chip 中options 的值的不同,或者在NAND 晶片中的特定位置查詢bad block table ,或者scan 整個NAND 晶片,並在記憶體中建立bad block table 。說起來複雜,但其實所有的這些動作,都可以在MTD 提供的一個叫做nand_scan 的函式中完成。
      關於nand_scan 函式,在使用時我想有一個地方值得一提。nand_scan 函式主要有兩個兩個函式組成,即nand_scan_ident 函式和nand_scan_tail 函式。其中nand_scan_ident 函式會讀取NAND 晶片的ID,而nand_scan_tail 函式則會查詢或者建立bbt (bad block table) 。
      在一般情況下,我們可以直接呼叫nand_scan 函式來完成所要做的工作,然而卻並不總是如此,在有些情況下,我們必須分別呼叫nand_scan_ident 函式和nand_scan_tail 函式,因為在這兩者之間,我們還需要做一些額外的工作。在《基於MTD 的NAND 驅動開發( 一) 》中介紹過一個叫做struct nand_ecclayout 的結構體,它用來定義ecc 在oob 中的佈局。對於small page( 每頁512 Byte) 和big page( 每頁2048 Byte) 的兩種NAND 晶片,它們的ecc 在oob 中的佈局不盡相同。如果你的driver 中對這兩種晶片的ecc 佈局與MTD 中定義的default 的佈局一致,那麼就很方便,直接呼叫nand_scan 函式即可。

      但如果不是,那你就需要為這兩種不同的NAND 晶片分別定義你的ecc 佈局。於是問題來了,因為我們在呼叫nand_scan_ident 函式之前,是不知道系統中的NAND 晶片是small page 型別的,還是big page 型別,然而在呼叫nand_scan_tail 函式之前,卻必須確定NAND 晶片的oob 佈局( 包括ecc 佈局和壞塊資訊pattern) ,因為nand_scan_tail 函式在讀取oob 以及處理ecc 時需要這個資訊。所以在這種情況下,我們就需要首先呼叫nand_scan_ident 函式,它會呼叫一個叫做nand_get_flash_type 的函式,MTD 就是在這個函式中讀取NAND 晶片的ID ,然後就能查表( 即全域性變數nand_flash_ids) 知道這片NAND 晶片的型別( 即writesize 的大小) 了。接下來,你就可以在你的NAND 驅動中,根據writesize 的大小來區分ecc 的佈局了。最後,我們就可以順利地呼叫nand_scan_tail 函數了。

六、NAND驅動中的壞塊管理

由於NAND Flash的現有工藝不能保證NAND的Memory Array在其生命週期中保持效能的可靠,因此在NAND晶片出廠的時候,廠家只能保證block 0不是壞塊,對於其它block,則均有可能存在壞塊,而且NAND晶片在使用的過程中也很容易產生壞塊。因此,我們在讀寫NAND FLASH 的時候,需要檢測壞塊,同時還需在NAND驅動中加入壞塊管理的功能。 NAND驅動在載入的時候,會呼叫nand_scan函式,對bad block table的搜尋,建立等操作就是在這個函式的第二部分,即nand_scan_tail函式中完成的。

struct nand_chip {
    ……
    uint8_t      * bbt
    struct nand_bbt_descr    * bbt_td;
    struct nand_bbt_descr    * bbt_md;
    struct nand_bbt_descr    * badblock_pattern;
    ……
} ;

bbt指向一塊在 nand_default_bbt函式中分配的記憶體,若options中沒有定義NAND_USE_FLASH_BBT,MTD就直接在bbt指向的記憶體中建立bbt,否則就會先從NAND晶片中查詢bbt是否存在,若存在,就把bbt的內容讀出來並儲存到bbt指向的記憶體中,若不存在,則在bbt指向的記憶體中建立bbt,最後把它寫入到NAND晶片中去。

struct nand_bbt_descr {
    int     options;
    int     pages[ NAND_MAX_CHIPS] ;
    int     offs;
    int     veroffs;
    uint8_t     version[ NAND_MAX_CHIPS] ;
    int     len;
    int     maxblocks;
    int     reserved_block_code;
    uint8_t     * pattern;
} ;

options:bad block table或者bad block的選項,可用的選擇以及各選項具體表示什麼含義,可以參考<linux/mtd/nand.h>。
pages:bbt專用。在查詢bbt的時候,若找到了bbt,就把bbt所在的page號儲存在這個成員變數中。若沒找到bbt,就會把新建立的bbt的儲存位置賦值給它。因為系統中可能會有多個NAND晶片,我們可以為每一片NAND晶片建立一個bbt,也可以只在其中一片NAND 晶片中建立唯一的一個bbt,所以這裡的pages是個維數為NAND_MAX_CHIPS的數值,用來儲存每一片NAND晶片的bbt位置。當然,若只建立了一個bbt,那麼就只使用pages[0]。
offs、len和pattern:MTD會從oob的offs中讀出len長度的內容,然後與pattern指標指向的內容做比較,若相等,則表示找到了bbt,或者表示這個block是好的。
veroffs和version:bbt專用。MTD會從oob的veroffs中讀出一個位元組的內容,作為bbt的版本值儲存在version中。
maxblocks:bbt專用。MTD在查詢bbt的時候,不會查詢NAND晶片中所有的block,而是最多查詢maxblocks個block。
2、bbt儲存在記憶體中時的工作流程
前文說過,不管bbt是儲存在NAND晶片中,還是儲存在記憶體中,nand_default_bbt函式都會呼叫nand_scan_bbt函式。
nand_scan_bbt函式會判斷bbt_td的值,若是NULL,則表示bbt儲存在記憶體中,它就在呼叫nand_memory_bbt函式後返回。nand_memory_bbt函式的主要工作就是在記憶體中建立bbt,其實就是呼叫了create_bbt函式。
create_bbt函式的工作方式很簡單,就是掃描NAND晶片所有的block,讀取每個block中第一個page的oob內容,然後根據oob中的壞塊資訊建立起bbt,可以參見上節關於struct nand_bbt_descr中的offs、len和pattern成員變數的解釋。
3、bbt儲存在NAND晶片時的工作流程
相對於把bbt儲存在記憶體中,這種方式的工作流程稍顯複雜一點。
nand_scan_bbt函式首先從NAND晶片中讀取bbt的內容,它讀取的方式分為兩種:
其一是呼叫read_abs_bbts函式直接從給定的page地址讀取,那麼這個page地址在什麼時候指定呢?就是在你的NAND driver中指定。前文說過,在struct nand_chip結構體中有兩個成員變數,分別是bbt_td和bbt_md,MTD為它們附上了default的值,但是你也可以根據你的需要為它們附上你自己定義的值。假如你為bbt_td和bbt_md的options成員變數定義了NAND_BBT_ABSPAGE,同時又把你的bbt所在的 page地址儲存在bbt_td和bbt_md的pages成員變數中,MTD就可以直接在這個page地址中讀取bbt了。值得一提的是,在實際使用時一般不這麼幹,因為你不能保證你儲存bbt的那個block就永遠不會壞,而且這樣也不靈活;
其二是呼叫那個search_read_bbts函式試著在NAND晶片的maxblocks(請見上文關於struct nand_bbt_descr中maxblocks的說明)個block中查詢bbt是否存在,若找到,就可以讀取bbt了。
MTD查詢bbt的過程為:如果你在bbt_td和bbt_md的options 成員變數中定義了 NAND_BBT_LASTBLOCK,那麼MTD就會從NAND晶片的最後一個block開始查詢(在default情況下,MTD就是這麼幹的),否則就從第一個block開始查詢。
與查詢oob中的壞塊資訊時類似,MTD會從所查詢block的第一個page的oob中讀取內容,然後與bbt_td或bbt_md 中patter指向的內容做比較,若相等,則表示找到了bbt,否則就繼續查詢下一個block。順利的情況下,只需查詢一個block中就可以找到 bbt,否則MTD最多會查詢maxblocks個block。
若找到了bbt,就把該bbt所在的page地址儲存到bbt_td或bbt_md的pages成員變數中,否則pages的值為-1。
如果系統中有多片NAND晶片,並且為每一片NAND晶片都建立一個bbt,那麼就會在每片NAND晶片上重複以上過程。
接著,nand_scan_bbt函式會呼叫check_create函式,該函式會判斷是否找到了bbt,其實就是判斷bbt_td 或者bbt_md中pages成員變數的值是否有效。若找到了bbt,就會把bbt從NAND晶片中讀取出來,並儲存到struct nand_chip中bbt指標指向的記憶體中;若沒找到,就會呼叫create_bbt函式建立bbt(與bbt儲存在記憶體中時情況一樣),同時把bbt 寫入到NAND晶片中去。 七、總結
自從寫了《基於MTD的NAND驅動開發(一)》後,好久沒有動筆,時隔一年才把這篇文章寫完,真是慚愧!不過,不管怎麼樣,總算是寫完了,除了還有一些ECC相關的內容外,也基本把我想表達的內容都表達出來了。
本文沒有糾纏於MTD中每一句code怎麼實現這種細節,因為一來本文主要是寫給我自己的,二來我覺得對於開發一個基於NAND的驅動來說,並不需要對MTD中的每一條程式碼都徹底細緻的研究,只要能在總體或者大局上有所把握,能瞭解MTD中主要函式的工作流程,也就可以了。而且,我覺得對於太細節的東西,只依靠講解是不起什麼作用的,還得自己去研讀程式碼才能明白和掌握。
bbt_td、bbt_md和badblock_pattern就是在nand_default_bbt函式中賦值的3個結構體。它們雖然是相同的結構體型別,但卻有不同的作用和含義。
其中bbt_td和bbt_md是主bbt和映象bbt的描述符(映象bbt主要用來對bbt的update和備份),它們只在把bbt儲存在NAND晶片的情況下使用,用來從NAND晶片中查詢bbt。若bbt儲存在記憶體中,bbt_td和bbt_md將會被賦值為NULL。
badblock_pattern就是壞塊資訊的pattern,其中定義了壞塊資訊在oob中的儲存位置,以及內容(即用什麼值表示這個block是個壞塊)。
通常用1或2個位元組來標誌一個block是否為壞塊,這1或2個位元組就是壞塊資訊,如果這1或2個位元組的內容是0xff,那就說明這個 block是好的,否則就是壞塊。對於壞塊資訊在NAND晶片中的儲存位置,small page(每頁512 Byte)和big page(每頁2048 Byte)的兩種NAND晶片不盡相同。一般來說,small page的NAND晶片,壞塊資訊儲存在每個block的第一個page的oob的第六個位元組中,而big page的NAND晶片,壞塊資訊儲存在每個block的第一個page的oob的第1和第2個位元組中。
我不能確定是否所有的NAND晶片都是如此佈局,但應該絕大多數NAND晶片是這樣的,不過,即使某種NAND晶片的壞塊資訊不是這樣的儲存方式也沒關係,因為我們可以在badblock_pattern中自己指定壞塊資訊的儲存位置,以及用什麼值來標誌壞塊(其實這個值表示的應該是 “好塊”,因為MTD會把從oob中壞塊資訊儲存位置讀出的內容與這個值做比較,若相等,則表示是個“好塊”,否則就是壞塊)。
bbt_td、bbt_md和badblock_pattern的結構體型別定義如下:

在nand_scan_tail函式中,會首先檢查struct nand_chip結構體中的options成員變數是否被賦上了NAND_SKIP_BBTSCAN,這個巨集表示跳過掃描bbt。所以,只有當你的 driver中沒有為options定義NAND_SKIP_BBTSCAN時,MTD才會繼續與bbt相關工作,即呼叫struct nand_chip中的scan_bbt函式指標所指向的函式,在MTD中,這個函式指標指向nand_default_bbt函式。
bbt有兩種儲存方式,一種是把bbt儲存在NAND晶片中,另一種是把bbt儲存在記憶體中。對於前者,好處是驅動載入更快,因為它只會在第一次載入NAND驅動時掃描整個NAND晶片,然後在NAND晶片的某個block中建立bbt,壞處是需要至少消耗NAND晶片一個block的儲存容量;而對於後者,好處是不會耗用NAND晶片的容量,壞處是驅動載入稍慢,因為儲存在記憶體中的bbt每次斷電後都不會儲存,所以在每次載入NAND 驅動時,都會掃描整個NAND晶片,以便建立bbt。
如果你係統中的NAND晶片容量不是太大的話,我建議還是把bbt儲存在記憶體中比較好,因為根據本人的使用經驗,對一塊容量為2G bits的NAND晶片,分別採用這兩種儲存方式的驅動的載入速度相差不大,甚至幾乎感覺不出來。
建立bbt後,以後在做擦除等操作時,就不用每次都去驗證當前block是否是個壞塊了,因為從bbt中就可以得到這個資訊。另外,若在讀寫等操作時,發現產生了新的壞塊,那麼除了標誌這個block是個壞塊外,也還需更新bbt。
接下來,介紹一下MTD是如何查詢或者建立bbt的。
1、MTD中與bbt相關的結構體
struct nand_chip中的scan_bbt函式指標所指向的函式,即nand_default_bbt函式會首先檢查struct nand_chip中options成員變數,如果當前NAND晶片是AG-AND型別的,會強制把bbt儲存在NAND晶片中,因為這種型別的NAND 晶片中含有廠家標註的“好塊”資訊,擦除這些block時會導致丟失壞塊資訊。
接著nand_default_bbt函式會再次檢查struct nand_chip中options成員變數,根據它是否定義了NAND_USE_FLASH_BBT,而為struct nand_chip中3個與bbt相關的結構體附上不同的值,然後再統一呼叫nand_scan_bbt函式,nand_scan_bbt函式會那3個結構體的不同的值做不同的動作,或者把bbt儲存在NAND晶片中,或者把bbt儲存在記憶體中。
在struct nand_chip中與bbt相關的結構體如下: