2012年6月3日 星期日

Introduction of MTD NAND flash

MTD 驅動程序是專門針對嵌入式Linux的一種驅動程序,相對於常規塊設備驅動程序(比如PC中的IDE硬盤)而言,MTD驅動程序能更好的支持和管理閃存設備,因為它本身就是專為閃存設備而設計的。
具體地講,基於MTD的FLASH驅動,承上可以很好地支持cramfs,jffs2和yaffs等文件系統,啟下也能對FLASH的擦除,讀寫,FLASH壞塊以及損耗平衡進行很好的管理。
所謂損耗平衡,是指對NAND的擦寫不能總是集中在某一個或某幾個block中,這是由NAND chip有限的擦寫次數的特性決定的。 
總之,在現階段,要為FLASH設備開發Linux下的驅動程序,那麼基於MTD的開發將幾乎是省時又省力的唯一選擇!

一、NAND和NOR的區別 
  
Google “Nand Flash和Nor Flash的區別”。 
  
簡單點說,主要的區別就是: 
  
1、 NAND比NOR便宜;NAND的容量比NOR大(指相同成本);NAND的擦寫次數是NOR的十倍;NAND的擦除和寫入速度比NOR快,讀取速度比NOR稍慢; 
  
2、 NAND和NOR的讀都可以以 byte為單位,但NAND的寫以page為單位,而NOR可以隨機寫每一個 byte。 NAND和NOR的擦除都以block為單位,但一般NAND的block比NOR的block小。另外,不管是NAND還是NOR,在寫入前,都必須先進行擦除操作,但是NOR在擦除前要先寫0; 
  
3、 NAND不能在片內運行程序,而NOR可以。但目前很多CPU都可以在上電時,以硬件的方式先將NAND的第一個block中的內容(一般是程序代碼,且也許不足一個block,如2KB大小)自動copy到ram中,然後再運行,因此只要CPU支持,NAND也可以當成啟動設備; 
  
4、 NAND和NOR都可能發生比特位反轉(但NAND反轉的機率遠大於NOR),因此這兩者都必須進行ECC操作;NAND可能會有壞塊(出廠時廠家會對壞塊做標記),在使用過程中也還有可能會出現新的壞塊,因此NAND驅動必須對壞塊進行管理。

二、內核樹中基於MTD的NAND驅動代碼的佈局 
  
在Linux內核中,MTD源代碼放在linux-2.6.22.10/driver/mtd目錄中,該目錄中包含chips、devices、maps、nand、onenand和ubi六個子目錄。 
  
其中只有nand和onenand目錄中的代碼才與NAND驅動相關,不過nand目錄中的代碼比較通用,而onenand目錄中的代碼相對於nand中的代碼而言則簡化了很多,它是針對三星公司開發的另一類Flash chip,即OneNAND Flash。我尚未對OneNand FLASH有過研究,只是通過網上資料得知,OneNand FLASH克服了傳統NAND Flash接口複雜的缺點,具有接口簡單、讀寫速度快、容量大、壽命長、成本低等優點,因此應該是一種較常用NAND先進的FLASH吧,只是目前似乎普及率並不高,本文也將不做討論。 
  
因此,若只是開發基於MTD的NAND驅動程序,那麼我們需要關注的code就基本上全在linux-2.6.22.10/drivers/mtd/nand目錄中了,而該目錄中也不是所有的code文件都與我們將要開發的NAND驅動有關,除了Makefile和Kconfig之外,其中真正與NAND驅動有關的code文件只有6個,即: 
  
1、 nand_base.c: 
定義了NAND驅動中對NAND chip最基本的操作函數和操作流程,如擦除、讀寫page、讀寫oob等。當然這些函數都只是進行一些default的操作,若你的系統在對NAND操作時有一些特殊的動作,則需要在你自己的驅動代碼中進行定義,然後Replace這些default的函數。 (mtd_info)
  
2、 nand_bbt.c: 
定義了NAND驅動中與壞塊管理有關的函數和結構體。

3、 nand_ids.c: 
定義了兩個全域類型的結構體:struct nand_flash_dev nand_flash_ids[ ]和struct nand_manufacturers nand_manuf_ids[ ]。其中前者定義了一些NAND chip的類型,後者定義了NAND chip的幾個廠商。 NAND chip的ID至少包含兩項內容:廠商ID和廠商為自己的NAND chip定義的chip ID。當NAND驅動被掛載的時候,它會去讀取具體NAND chip的ID,然後根據讀取的內容到上述定義的nand_manuf_ids[ ]和nand_flash_ids[ ]兩個結構體中去查找,以此判斷該NAND chip是那個廠商的產品,以及該NAND chip的類型。若查找不到,則NAND驅動就會掛載失敗,因此在開發NAND驅動前必須事先將你的NAND chip添加到這兩個結構體中去(其實這兩個結構體中已經定義了市場上絕大多數的NAND chip,所以除非你的NAND chip實在比較特殊,否則一般不需要額外添加)。值得一提的是,nand_flash_ids[ ]中有三項屬性比較重要,即pagesize、chipsize和erasesize,驅動就是依據這三項屬性來決定對NAND chip進行erase,read and write等操作時的大小。其中pagesize即NAND chip的頁大小,一般為256、512或2048;chipsize即NAND chip的容量;erasesize即每次擦除操作的大小,通常就是NAND chip的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 chip時用到了DMA操作。 
  
綜上所述,若要研究基於MTD的NAND驅動,其實所需閱讀的code量也不是很大。

另外,在動手寫NAND驅動之前,也許需要讀一下以下文件:
1、 Linux MTD source code 分析:
該文件可以讓我們對MTD有一個直觀而又相對具體的認識,但它似乎主要是針對NOR FLASH的,對於實際開發NAND驅動的幫助並不是很大。
2、 MTD NAND Driver Programming Interface:
http://www.aoc.nrao.edu/~tjuerges/ALMA/Kernel/mtdnand/
該文檔中關於ECC的說明很有幫助。
3、 MTD的官方網站:
http://www.linux-mtd.infradead.org/

三、NAND相關原理 
  
在我們開始NAND驅動編寫之前,至少應該知道:數據在NAND中是怎樣儲存的,以及怎樣的方式從NAND中讀寫數據時。 
  
1、 NAND的存儲結構和操作方式 
  
這方面的資料可以從任意一種NAND的datasheet中得到,因為基本上每一種NAND的datasheet都會介紹NAND的組成結構和操作命令,而且事實上,大多數的NAND datasheet都大同小異,所不同的大概只是該NAND chip的容量大小和讀寫速度等基本特性。 
  
這裡以每頁512 byte的NAND FLASH為例簡單說明一下:每一塊NAND chip由n個block組成->每一個block由m個page組成->每一個page由256 byte大小的column1(也稱1st half page)、256 byte大小的column2(也稱2nd half page)和16 byte大小的oob(out-of-band,也稱spare area)組成。至於m和n的大小可以查看特定NAND的datasheet。相應的,若給定NAND中的一個 byte的地址,我們可以根據這個地址算出block地址(即第幾個block)、page地址(即該block中的第幾個page)和column地址(即1st half page,或2nd half page,或oob中的第幾個 byte)。 
  
在擦除NAND時,必須每次至少擦除1個block;在寫NAND時,必須每次寫1個page(有些NAND也支持寫不足一個page大小的數據);在讀NAND時,分為三種情況(對應三種不同的NAND命令),即讀column1、讀column2和讀oob,那麼為什麼要分這三種情況呢?假如知道NAND怎樣根據給定的地址確定它的存儲單元,那麼自然也就能明白原因了,其實也並不復雜,主要是因為給定地址中的A8並不在NAND的視野範圍之內(也許表達並不準確)。 
  
事實上,在寫基於MTD的NAND驅動時,我們並不需要實現精確到讀寫某一個byte地址的函數(除了讀oob之外),這是因為: 
  
基於MTD的NAND驅動在讀寫NAND時,可以分兩種情況,即:(1)不進行ECC檢測時,一次讀寫一整個page中的MAIN部分(也就是那真實存儲數據的512 byte); (2)進行ECC檢測時(不管是hardware ECC還是software ECC),一次讀寫一整個page(包括16 byte的oob部分)。所以部分NAND所支持的寫不足一個page大小數據的功能,對MTD來說是用不著的。 
  
那麼,如果只需要讀寫不足一個page大小的數據怎麼辦?這是MTD更上層的部分需要處理的事。也就是說,對於NAND驅動來說,它只會讀寫整整一個page的數據! 
  
最後值得一提的是,NAND驅動有可能只去讀oob部分,這是因為除了ECC信息之外,壞塊信息也存儲在oob之中,NAND驅動需要讀取oob中描述壞塊的那個 byte(通常是每個block的第一個page的oob中的第六個 byte)來判斷該block是不是一個壞塊。所以,我們只有在讀oob時,才需要實現精確到讀某一個byte地址的函數。 
  
由此,我們也可以額外知道一件事,那就是NAND驅動中用到的column地址只在讀oob時才有用,而在其他情況下,column地址都為0。

2、 ECC相關的結構體 
struct nand_ecclayout { 
           uint32_t eccbytes; 
           uint32_t eccpos[64]; 
           uint32_t oobavail; 
           struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; 
}; 
這是用來定義ECC在oob中佈局的一個結構體。 
  
前面已經提及過,oob中主要存儲兩種信息:壞塊信息和ECC數據。對與small page的NAND chip來說,其中壞塊信息佔據1個 byte(一般固定在第六個 byte),ECC數據佔據三個 byte。所以sturct nand_ecclayout這個結構體,也就是用來告訴那些與ECC操作無關的函數,Nand chip的oob部分中,哪些 byte是用來存儲ECC的(即不可用作它用的),哪些 byte是空閒的,即可用的。 
  
其實之所以有這個結構體,主要是因為硬件ECC的緣故。以寫數據為例,在使用硬件ECC的情況下,那三個 byte的ECC數據是由硬件計算得到,並且寫到NAND chip的oob中去的,同時也是由硬件決定寫到oob的哪三個 byte中去。這些都是由硬件做的,而NAND驅動並不知道,所以就需要用這個結構體來告訴驅動了。 
  
所以,在寫NAND驅動時,就有可能需要對這個結構體進行賦值。這裡說“有可能”,是因為MTD對這個結構體有一個默認的賦值,假如這個賦值所定義的ECC位置與你的硬件一致的話,那就不必在你的驅動中手動賦值了。其實對大多數硬件(這裡所說的硬件,不是指NAND chip,而是NAND控制器)來說,是不必手動賦值的,但也有許多例外。 
  
值得一提的是,這個結構體不僅僅用來定義ECC佈局,也可以用來將你的驅動在oob中需要額外用到的 byte位置保護起來。 
  
現在對struct nand_ecclayout 這個結構體進行一下說明。 
  
uint32_t eccbytes:ECC的 byte數,對於512B-per-page的NAND來說,eccbytes = 3,如果你需要額外用到oob中的數據,那麼也可以大於3. 
uint32_t eccpos[64]:ECC數據在oob中的位置,這里之所以是個64 byte的數組,是因為對於2048-per-page的NAND來說,它的oob有64個 byte。而對於512B-per-page的NAND來說,可以而且只可以定義它的前16個 byte。 
uint32_t oobavail:oob中可用的 byte數,這個值不用賦值,MTD會根據其它三個變量自動計算得到。 
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]:顯示定義空閒的oob byte。

四、基於MTD的NAND驅動架構
 
1、platform_device和platform_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結構體中自然需要定義probe和remove函數。其實在大多數嵌入式Linux驅動中,這樣的套路基本已經成了一個定式 
  
至於module_init有什麼作用,caorr_nand_probe又是何時調用的,以及這個driver是怎麼和NAND設備聯繫起來的,就不再多說了,這裡只提三點: 
  
A、以上code只是向kernel註冊了NAND的platform_driver,即caorr_nand_driver,我們當然還需要一個NAND的platform_device,要不然caorr_nand_driver的probe函數就永遠不會被執行,因為沒有device需要這個driver。 
  
B、 向Linux內核註冊NAND的platform_device有兩種方式: 
其一是直接定義一個NAND的platform_device結構體,然後調用platform_device_register函數註冊。作為例子,我們可以這樣定義NAND的platform_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_resources和resource與具體的硬件相關,主要包括一些寄存器地址範圍和中斷的定義。 caorr_platform_default_nand待會兒再說。需要注意的是,這個platform_device中name的值必須與platform_driver->driver->name的值完全一致,因為platform_bus_type的match函數是根據這兩者的name值來進行匹配的。 
其二是用platform_device_alloc函數動態分配一個platform_device,然後再用platform_device_add函數把這個platform_device加入到內核中去。具體不再細說,Linux kernel中有很多例子可以參考。 
相對來說,第一種方式更加方便和直觀一點,而第二種方式則更加靈活一點。 
  
C、 在加載NAND驅動時,我們還需要向MTD Core提供一個信息,那就是NAND的分區信息(MTD partition),caorr_platform_default_nand主要就是起這個作用,更加詳細的容後再說。 
  
2、MTD架構的簡單描述 
  
MTD(memory technology device存儲技術設備)是用於訪問memory設備(ROM、flash)的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 chip的讀寫主要分三部分: 
  
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 chip的讀寫與MTD提供的這些函數一致,就不必自己實現這些函數了。 
  
這三部分讀寫函數是相互配合著完成對NAND chip的讀寫的。首先,MTD上層需要讀寫NAND chip時,會調用struct mtd_info中的讀寫函數,接著struct mtd_info中的讀寫函數就會調用struct nand_chip或struct nand_ecc_ctrl中的讀寫函數,最後,若調用的是struct nand_ecc_ctrl中的讀寫函數,那麼它又會接著調用struct nand_chip中的讀寫函數。如下圖所示:
以讀NAND chip為例,講解一下這三部分讀寫函數的工作過程。 
  
首先,MTD上層會調用struct mtd_info中的讀page函數,即nand_read函數。 
  
接著nand_read函數會調用struct nand_chip中cmdfunc函數,這個cmdfunc函數與具體的NAND controller相關,它的作用是使NAND controller向NAND chip發出讀命令,NAND chip收到命令後,就會做好準備等待NAND controller下一步的讀取。 
  
接著nand_read函數又會調用struct nand_ecc_ctrl中的read_page函數,而read_page函數又會調用struct nand_chip中read_buf函數,從而真正把NAND chip中的數據讀取到buffer中(所以這個read_buf的意思其實應該是read into buffer ,另外,這個buffer是struct mtd_info中的nand_read函數傳下來的)。 
  
read_buf函數返回後,read_page函數就會對buffer中的數據做一些處理,比如校驗ecc,以及若數據有錯,就根據ecc對數據修正之類的,最後read_page函數返回到nand_read函數中。 
  
對NAND chip的其它操作,如寫,擦除等,都與讀操作類似。 

五、NAND驅動中的probe函數 
  
對於很多嵌入式Linux的外設driver來說,probe函數將是我們遇到的第一個與具體硬件打交道,同時也相對複雜的函數。而且根據我的經驗,對於很多外設的driver來說,只要能成功實現probe函數,那基本上完成這個外設的driver也就成功了一多半,基於MTD的NAND driver就是一個典型的例子。稍後就可以看到,在NAND driver的probe函數中,就已經涉及到了對NAND chip的讀寫。 
  
在基於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 chip密切相關的結構體,主要包含三方面內容: 
  
A. 指向一些操作NAND chip的函數的指針,稍後將對這些函數指針作一些說明; 
  
B. 表示NAND chip特性的成員變量,主要有: 
  
unsigned int options:與具體的NAND chip相關的一些選項,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等,至於這些選項具體表示什麼含義,可以參考<linux/mtd/nand.h>,那裡有較為詳細的說明; 
  
int page_shift:用位表示的NAND chip的page大小,如某片NAND chip的一個page有512 個 byte,那麼page_shift就是9; 
  
int phys_erase_shift:用位表示的NAND chip的每次可擦除的大小,如某片NAND chip每次可擦除16K byte(通常就是一個block的大小),那麼phys_erase_shift就是14; 
  
int bbt_erase_shift:用位表示的bad block table的大小,通常一個bbt佔用一個block,所以bbt_erase_shift通常與phys_erase_shift相等; 
  
int chip_shift:用位表示的NAND chip的容量; 
  
int numchips:表示系統中有多少片NAND chip; 
  
unsigned long chipsize:NAND chip的大小; 
  
int pagemask:計算page number時的掩碼,總是等於chipsize/page大小- 1; 
int pagebuf:用來保存當前讀取的NAND chip的page number,這樣一來,下次讀取的數據若還是屬於同一個page,就不必再從NAND chip讀取了,而是從data_buf中直接得到; 
  
int badblockpos:表示壞塊信息保存在oob中的第幾個 byte。在每個block的第一個page的oob中,通常用1或2個 byte來表示這是否為一個壞塊。對於絕大多數的NAND chip,若page size > 512,那麼壞塊信息從Byte 0開始存儲,否則就存儲在Byte 5,即第六個 byte。 
  
C. 與ecc,oob和bbt (bad block table)相關的一些結構體,對於壞塊及壞塊管理,將在稍後做專門介紹。 
  
2、對NAND chip進行實際操作的函數 
  
前面已經說過,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中幾個重要的函數指針做一些說明。
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 chip的讀寫地址,如果你的NAND controller是用PIO模式與NAND chip交互,那麼只要把這兩個值賦上合適的地址,就完全可以使用MTD提供的default的讀寫函數來操作NAND chip了。所以這兩個變量視具體的NAND controller而定,不一定用得著; 
  
read_byte和read_word:從NAND chip讀一個 byte或一個字,通常MTD會在讀取NAND chip的ID,STATUS和OOB中的壞塊信息時調用這兩個函數,具體是這樣的流程,首先MTD調用cmdfunc函數,發起相應的命令,NAND chip收到命令後就會做好準備,最後MTD就會調用read_byte或read_word函數從NAND chip中讀取 chip的ID,STATUS或者OOB; 
  
read_buf、write_buf和verify_buf:分別是從NAND chip讀取數據到buffer,把buffer中的數據寫入到NAND chip,和從NAND chip中讀取數據並驗證。調用read_buf時的流程與read_byte和read_word類似,MTD也是先調用cmdfunc函數發起讀命令(如NAND_CMD_READ0命令),接著NAND chip收到命令後做好準備,最後MTD再調用read_buf函數把NAND chip中的數據讀取到buffer中。調用write_buf函數的流程與read_buf相似; 
  
select_chip:因為系統中可能有不止一片NAND chip,所以在對NAND chip進行操作前,需要這個函數來指定一片NAND chip; 
  
cmdfunc:向NAND chip發起命令; 
  
waitfunc:NAND chip在接收到命令後,並不一定能立即響應NAND controller的下一步動作,對有些命令,比如erase,program等命令,NAND chip需要一定的時間來完成,所以就需要這個waitfunc來等待NAND chip完成命令,並再次進入準備好狀態; 
write_page:把一個page的數據寫入NAND chip,這個函數一般不需我們實現,因為它會調用struct nand_ecc_ctrl中的write_page_raw或者write_page函數,關於這兩個函數將在稍後介紹。 
  
以上提到的這些函數指針,都是REPLACEABLE的,也就是說都是可以被替換的,根據你的NAND controller,如果你需要自己實現相應的函數,那麼只需要把你的函數賦值給這些函數指針就可以了,如果你沒有賦值,那麼MTD會把它自己定義的default的函數賦值給它們。 
  
順便提一下,以上所說的讀寫NAND chip的流程並不是唯一的,如果你的NAND controller在讀寫NAND chip時有自己獨特的方式,那麼完全可以按照自己的方式來做。就比如我們公司 chip的NAND controller,因為它使用DMA的方式從NAND chip中讀寫數據,所以在我的NAND driver中,讀數據的流程是這樣的:首先在cmdfunc函數中初始化DMA專用的buffer,配置NAND地址,發起命令等,在cmdfunc中我幾乎做了所有需要與NAND chip交互的事情,總之等cmdfunc函數返回後,NAND chip中的數據就已經在DMA專用的buffer中了,之後MTD會再調用read_buf函數,所以我的read_buf函數其實只是把數據從DMA專用的buffer中,拷貝到MTD提供的buffer中罷了。 
  
最後,struct nand_chip結構體中還包含了一個很重要的結構體,即struct 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 chip發出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 chip中讀取一個page的原始數據和向NAND chip寫入一個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的函數即可。 
  
為進一步理解各函數之間的調用關係,這裡提供一張從網上找到的write NAND chip的流程圖,僅供參考:


3、probe函數的工作流程 
  
由前面的說明可知,我們在要對NAND chip進行實際操作前已經為struct mtd_info、struct mtd_partition和struct nand_chip這三個結構體分配好了內存,接下來就要為它們做一些初始化工作。 
  
其中,我們需要為struct mtd_info所做的初始化工作並不多,因為MTD Core會在稍後為它做很多初始化工作,但是有一點必須由我們來做,那就是把指向struct nand_chip結構體的指針賦給struct mtd_info的priv成員變量,因為MTD Core中很多函數之間的調用都只傳遞struct mtd_info,它需要通過priv成員變量得到struct nand_chip。 
  
對於struct mtd_partition的賦值,前面已經做過介紹,這裡不再贅述。 
  
所以,為struct nand_chip的初始化,才是我們在probe函數中的主要工作。其實這裡所謂的初始化,主要就是為struct nand_chip結構體中的眾多函數指針賦值。 
  
前面已經為struct nand_chip結構體中的函數指針做過說明,想必你已經知道這些函數指針所指向的函數具體實現什麼樣的功能,負責做什麼事情。那麼如何讓這些函數實現既定的功能呢?這就與具體的NAND controller有關了,實在沒辦法多說。根據你的NAND controller,也許你需要做很多工作,為struct nand_chip中的每一個函數指針實現特定的函數,也或許你只需要為IO_ADDR_R和IO_ADDR_W賦上地址,其它則什麼都不做,利用MTD提供的函數即可。 
  
現在假定你定義好了所有需要的與NAND chip交互的函數,並已經把它們賦給了struct nand_chip結構體中的函數指針。當然,此時你還不能保證這些函數一定能正確工作,但是沒有關係,probe函數在接下來的工作中會調用到幾乎所有的這些函數,你可以依次來驗證和調試。當你的probe函數能順利通過後,那麼這些函數也就基本沒什麼問題了,你的NAND驅動也就已經完成了80%了。 
  
接下來,probe函數就會開始與NAND chip進行交互了,它要做的事情主要包括這幾個方面:讀取NAND chip的ID,然後查表得到這片NAND chip的如廠商,page size,erase size以及chip size等信息,接著,根據struct nand_chip中options的值的不同,或者在NAND chip中的特定位置查找bad block table,或者scan整個NAND chip,並在內存中建立bad block table。說起來複雜,但其實所有的這些動作,都可以在MTD提供的一個叫做nand_scan的函數中完成。 
  
我雖然研讀過nand_scan函數中的代碼,但不會在這裡做情景分析式的詳細說明,若你對這部分代碼的實現感興趣,可以參考以下兩篇文章:
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156927.aspx
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156929.aspx

關於nand_scan函數,在使用時我想有一個地方值得一提。 
  
nand_scan函數主要有兩個兩個函數組成,即nand_scan_ident函數和nand_scan_tail函數。其中nand_scan_ident函數會讀取NAND chip的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 chip,它們的ecc在oob中的佈局不盡相同。 
  
如果你的driver中對這兩種 chip的ecc佈局與MTD中定義的default的佈局一致,那麼就很方便,直接調用nand_scan函數即可。但如果不是,那你就需要為這兩種不同的NAND chip分別定義你的ecc佈局。於是問題來了,因為我們在調用nand_scan_ident函數之前,是不知道系統中的NAND chip是small page類型的,還是big page類型,然而在調用nand_scan_tail函數之前,卻必須確定NAND chip的oob佈局(包括ecc佈局和壞塊信息pattern),因為nand_scan_tail函數在讀取oob以及處理ecc時需要這個信息。 
所以在這種情況下,我們就需要首先調用nand_scan_ident函數,它會調用一個叫做nand_get_flash_type的函數,MTD就是在這個函數中讀取NAND chip的ID,然後就能查表(即全局變量nand_flash_ids)知道這片NAND chip的類型(即writesize的大小)了。 
  
接下來,你就可以在你的NAND驅動中,根據writesize的大小來區分ecc的佈局了。最後,我們就可以順利地調用nand_scan_tail函數了。

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

在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 chip中,另一種是把bbt存儲在內存中。對於前者,好處是驅動加載更快,因為它只會在第一次加載NAND驅動時掃描整個NAND chip,然後在NAND chip的某個block中建立bbt,壞處是需要至少消耗NAND chip一個block的存儲容量;而對於後者,好處是不會耗用NAND chip的容量,壞處是驅動加載稍慢,因為存儲在內存中的bbt每次斷電後都不會保存,所以在每次加載NAND驅動時,都會掃描整個NAND chip,以便建立bbt。 

如果你係統中的NAND chip容量不是太大的話,我建議還是把bbt存儲在內存中比較好,因為根據本人的使用經驗,對一塊容量為2G bits的NAND chip,分別採用這兩種存儲方式的驅動的加載速度相差不大,甚至幾乎感覺不出來。 

建立bbt後,以後在做擦除等操作時,就不用每次都去驗證當前block是否是個壞塊了,因為從bbt中就可以得到這個信息。另外,若在讀寫等操作時,發現產生了新的壞塊,那麼除了標誌這個block是個壞塊外,也還需更新bbt。 

接下來,介紹一下MTD是如何查找或者建立bbt的。 

1、MTD中與bbt相關的結構體 

struct nand_chip中的scan_bbt函數指針所指向的函數,即nand_default_bbt函數會首先檢查struct nand_chip中options成員變量,如果當前NAND chip是AG-AND類型的,會強制把bbt存儲在NAND chip中,因為這種類型的NAND chip中含有廠家標註的“好塊”信息,擦除這些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 chip中,或者把bbt存儲在內存中。 

在struct nand_chip中與bbt相關的結構體如下:
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 chip中查找bbt是否存在,若存在,就把bbt的內容讀出來並保存到bbt指向的內存中,若不存在,則在bbt指向的內存中建立bbt,最後把它寫入到NAND chip中去。 

bbt_td、bbt_md和badblock_pattern就是在nand_default_bbt函數中賦值的3個結構體。它們雖然是相同的結構體類型,但卻有不同的作用和含義。 
其中bbt_td和bbt_md是主bbt和鏡像bbt的描述符(鏡像bbt主要用來對bbt的update和備份),它們只在把bbt存儲在NAND chip的情況下使用,用來從NAND chip中查找bbt。若bbt存儲在內存中,bbt_td和bbt_md將會被賦值為NULL。 

badblock_pattern就是壞塊信息的pattern,其中定義了壞塊信息在oob中的存儲位置,以及內容(即用什麼值表示這個block是個壞塊)。 

通常用1或2個 byte來標誌一個block是否為壞塊,這1或2個 byte就是壞塊信息,如果這1或2個 byte的內容是0xff,那就說明這個block是好的,否則就是壞塊。對於壞塊信息在NAND chip中的存儲位置,small page(每頁512 Byte)和big page(每頁2048 Byte)的兩種NAND chip不盡相同。一般來說,small page的NAND chip,壞塊信息存儲在每個block的第一個page的oob的第六個 byte中,而big page的NAND chip,壞塊信息存儲在每個block的第一個page的oob的第1和第2個 byte中。 

我不能確定是否所有的NAND chip都是如此佈局,但應該絕大多數NAND chip是這樣的,不過,即使某種NAND chip的壞塊信息不是這樣的存儲方式也沒關係,因為我們可以在badblock_pattern中自己指定壞塊信息的存儲位置,以及用什麼值來標誌壞塊(其實這個值表示的應該是“好塊”,因為MTD會把從oob中壞塊信息存儲位置讀出的內容與這個值做比較,若相等,則表示是個“好塊”,否則就是壞塊)。 

bbt_td、bbt_md和badblock_pattern的結構體類型定義如下:
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 chip,我們可以為每一片NAND chip建立一個bbt,也可以只在其中一片NAND chip中建立唯一的一個bbt,所以這裡的pages是個維數為NAND_MAX_CHIPS的數值,用來保存每一片NAND chip的bbt位置。當然,若只建立了一個bbt,那麼就只使用pages[0]。 

offs、len和pattern:MTD會從oob的offs中讀出len長度的內容,然後與pattern指針指向的內容做比較,若相等,則表示找到了bbt,或者表示這個block是好的。 

veroffs和version:bbt專用。 MTD會從oob的veroffs中讀出一個 byte的內容,作為bbt的版本值保存在version中。 

maxblocks:bbt專用。 MTD在查找bbt的時候,不會查找NAND chip中所有的block,而是最多查找maxblocks個block。 

2、bbt存儲在內存中時的工作流程 

前文說過,不管bbt是存儲在NAND chip中,還是存儲在內存中,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 chip所有的block,讀取每個block中第一個page的oob內容,然後根據oob中的壞塊信息建立起bbt,可以參見上節關於struct nand_bbt_descr中的offs、len和pattern成員變量的解釋。 

3、bbt存儲在NAND chip時的工作流程 

相對於把bbt存儲在內存中,這種方式的工作流程稍顯複雜一點。 

nand_scan_bbt函數首先從NAND chip中讀取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 chip的maxblocks(請見上文關於struct nand_bbt_descr中maxblocks的說明)個block中查找bbt是否存在,若找到,就可以讀取bbt了。 

MTD查找bbt的過程為:如果你在bbt_td和bbt_md的options 成員變量中定義了NAND_BBT_LASTBLOCK,那麼MTD就會從NAND chip的最後一個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 chip,並且為每一片NAND chip都建立一個bbt,那麼就會在每片NAND chip上重複以上過程。 

接著,nand_scan_bbt函數會調用check_create函數,該函數會判斷是否找到了bbt,其實就是判斷bbt_td或者bbt_md中pages成員變量的值是否有效。若找到了bbt,就會把bbt從NAND chip中讀取出來,並保存到struct nand_chip中bbt指針指向的內存中;若沒找到,就會調用create_bbt函數建立bbt(與bbt存儲在內存中時情況一樣),同時把bbt寫入到NAND chip中去。 
  
  
七、總結 

本文沒有糾纏於MTD中每一句code怎麼實現這種細節,對於開發一個基於NAND的驅動來說,並不需要對MTD中的每一條代碼都徹底細緻的研究,只要能在總體或者大局上有所把握,能了解MTD中主要函數的工作流程,也就可以了。

沒有留言: