mtd Nand驅動

大約用了兩個禮拜不到的時間爲公司的IPcamera系統寫了基於MTDNAND驅動(linux-2.6.22.10內核),目前已可以在該驅動的支持下跑cramfsjffs2文件系統,另外,該驅動也可以同時支持small page(每頁512 Byte)big page(每頁2048 Byte)兩種NAND芯片。在此整理一下與NAND驅動相關的概念,結構體,驅動框架和流程,同時分析一下基於MTDNAND驅動的部分函數,尤其是其中的nand_scan()函數。(涉及到具體NAND芯片時,若不做說明,將以small pageNAND芯片爲例。)
 
注:個人理解,有誤難免!—— 筆者:曹榮榮
 
 
MTD 驅動程序是專門針對嵌入式Linux的一種驅動程序,相對於常規塊設備驅動程序(比如PC中的IDE硬盤)而言,MTD驅動程序能更好的支持和管理閃存設備,因爲它本身就是專爲閃存設備而設計的。
具體地講,基於MTDFLASH驅動,承上可以很好地支持cramfsjffs2yaffs等文件系統,啓下也能對FLASH的擦除,讀寫,FLASH壞塊以及損耗平衡進行很好的管理。所謂損耗平衡,是指對NAND的擦寫不能總是集中在某一個或某幾個block中,這是由NAND芯片有限的擦寫次數的特性決定的。
總之,在現階段,要爲FLASH設備開發Linux下的驅動程序,那麼基於MTD的開發將幾乎是省時又省力的唯一選擇!
 
一、NANDNOR的區別
 
Google Nand FlashNor Flash的區別”。
 
簡單點說,主要的區別就是:
 
1、  NANDNOR便宜;NAND的容量比NOR大(指相同成本);NAND的擦寫次數是NOR的十倍;NAND的擦除和寫入速度比NOR快,讀取速度比NOR稍慢;
 
2、  NANDNOR的讀都可以以字節爲單位,但NAND的寫以page爲單位,而NOR可以隨機寫每一個字節。NANDNOR的擦除都以block爲單位,但一般NANDblockNORblock小。另外,不管是NAND還是NOR,在寫入前,都必須先進行擦除操作,但是NOR在擦除前要先寫0
 
3、  NAND不能在片內運行程序,而NOR可以。但目前很多CPU都可以在上電時,以硬件的方式先將NAND的第一個block中的內容(一般是程序代碼,且也許不足一個block,如2KB大小)自動copyram中,然後再運行,因此只要CPU支持,NAND也可以當成啓動設備;
 
4、  NANDNOR都可能發生比特位反轉(但NAND反轉的機率遠大於NOR),因此這兩者都必須進行ECC操作;NAND可能會有壞塊(出廠時廠家會對壞塊做標記),在使用過程中也還有可能會出現新的壞塊,因此NAND驅動必須對壞塊進行管理。
 
二、內核樹中基於MTDNAND驅動代碼的佈局
 
Linux內核中,MTD源代碼放在linux-2.6.22.10/driver/mtd目錄中,該目錄中包含chipsdevicesmapsnandonenandubi六個子目錄。
 
其中只有nandonenand目錄中的代碼才與NAND驅動相關,不過nand目錄中的代碼比較通用,而onenand目錄中的代碼相對於nand中的代碼而言則簡化了很多,它是針對三星公司開發的另一類Flash芯片,即OneNAND Flash。我尚未對OneNand FLASH有過研究,只是通過網上資料得知,OneNand FLASH克服了傳統NAND Flash接口複雜的缺點,具有接口簡單、讀寫速度快、容量大、壽命長、成本低等優點,因此應該是一種較常用NAND先進的FLASH吧,只是目前似乎普及率並不高,本文也將不做討論。
 
因此,若只是開發基於MTDNAND驅動程序,那麼我們需要關注的代碼就基本上全在linux-2.6.22.10/drivers/mtd/nand目錄中了,而該目錄中也不是所有的代碼文件都與我們將要開發的NAND驅動有關,除了MakefileKconfig之外,其中真正與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[ ]中有三項屬性比較重要,即pagesizechipsizeerasesize驅動就是依據這三項屬性來決定對NAND芯片進行擦除,讀寫等操作時的大小的。其中pagesizeNAND芯片的頁大小,一般爲2565122048chipsizeNAND芯片的容量;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.cs3c2410.c兩個,而其中又尤以cafe_nand.c更爲詳細,另外,nand目錄中也似乎只有cafe_nand.c中的驅動程序在讀寫NAND芯片時用到了DMA操作。
 
綜上所述,若要研究基於MTDNAND驅動,其實所需閱讀的代碼量也不是很大。
 
另外,在動手寫NAND驅動之前,也許需要讀一下以下文檔:
1、  Linux MTD 源代碼分析
該文檔可以讓我們對MTD有一個直觀而又相對具體的認識,但它似乎主要是針對NOR FLASH的,對於實際開發NAND驅動的幫助並不是很大。
2、  MTD NAND Driver Programming Interface
該文檔中關於ECC的說明很有幫助。
3、  MTD的官方網站:
 
三、NAND相關原理
 
在我們開始NAND驅動編寫之前,至少應該知道:數據在NAND中是怎樣存儲的,以及以怎樣的方式從NAND中讀寫數據時。
 
1、  NAND的存儲結構和操作方式
 
這方面的資料可以從任意一種NANDdatasheet中得到,因爲基本上每一種NANDdatasheet都會介紹NAND的組成結構和操作命令,而且事實上,大多數的NAND datasheet都大同小異,所不同的大概只是該NAND芯片的容量大小和讀寫速度等基本特性。
 
這裏以每頁512字節的NAND FLASH爲例簡單說明一下:每一塊NAND芯片由nblock組成->每一個blockmpage組成->每一個page256字節大小的column1(也稱1st half page)256字節大小的column2(也稱2nd half page)16字節大小的oob(out-of-band,也稱spare area)組成。至於mn的大小可以查看特定NANDdatasheet。相應的,若給定NAND中的一個字節的地址,我們可以根據這個地址算出block地址(即第幾個block)page地址(即該block中的第幾個page)column地址(1st half page,或2nd half page,或oob中的第幾個字節)
 
在擦除NAND時,必須每次至少擦除1block;在寫NAND時,必須每次寫1page(有些NAND也支持寫不足一個page大小的數據);在讀NAND時,分爲三種情況(對應三種不同的NAND命令),即讀column1、讀column2和讀oob,那麼爲什麼要分這三種情況呢?假如知道NAND怎樣根據給定的地址確定它的存儲單元,那麼自然也就能明白原因了,其實也並不複雜,主要是因爲給定地址中的A8並不在NAND的視野範圍之內(也許表達並不準確)
 
事實上,在寫基於MTDNAND驅動時,我們並不需要實現精確到讀寫某一個byte地址的函數(除了讀oob之外),這是因爲:
 
基於MTDNAND驅動在讀寫NAND時,可以分兩種情況,即:(1)不進行ECC檢測時,一次讀寫一整個page中的MAIN部分(也就是那真實存儲數據的512字節)(2)進行ECC檢測時(不管是hardware ECC還是software ECC),一次讀寫一整個page(包括16字節的oob部分)。所以部分NAND所支持的寫不足一個page大小數據的功能,對MTD來說是用不着的。
 
那麼,如果只需要讀寫不足一個page大小的數據怎麼辦?這是MTD更上層的部分需要處理的事。也就是說,對於NAND驅動來說,它只會讀寫整整一個page的數據!
 
最後值得一提的是,NAND驅動有可能只去讀oob部分,這是因爲除了ECC信息之外,壞塊信息也存儲在oob之中,NAND驅動需要讀取oob中描述壞塊的那個字節(通常是每個block的第一個pageoob中的第六個字節)來判斷該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];
};
這是用來定義ECCoob中佈局的一個結構體。
 
前面已經提及過,oob中主要存儲兩種信息:壞塊信息和ECC數據。對與small pageNAND芯片來說,其中壞塊信息佔據1個字節(一般固定在第六個字節)ECC數據佔據三個字節。所以sturct nand_ecclayout這個結構體,也就是用來告訴那些與ECC操作無關的函數,Nand芯片的oob部分中,哪些字節是用來存儲ECC(即不可用作它用的),哪些字節是空閒的,即可用的。
 
其實之所以有這個結構體,主要是因爲硬件ECC的緣故。以寫數據爲例,在使用硬件ECC的情況下,那三個字節的ECC數據是由硬件計算得到,並且寫到NAND芯片的oob中去的,同時也是由硬件決定寫到oob的哪三個字節中去。這些都是由硬件做的,而NAND驅動並不知道,所以就需要用這個結構體來告訴驅動了。
 
所以,在寫NAND驅動時,就有可能需要對這個結構體進行賦值。這裏說“有可能”,是因爲MTD對這個結構體有一個默認的賦值,假如這個賦值所定義的ECC位置與你的硬件一致的話,那就不必在你的驅動中手動賦值了。其實對大多數硬件(這裏所說的硬件,不是指NAND芯片,而是NAND控制器)來說,是不必手動賦值的,但也有許多例外。
 
值得一提的是,這個結構體不僅僅用來定義ECC佈局,也可以用來將你的驅動oob中需要額外用到的字節位置保護起來。
 
現在對struct nand_ecclayout 這個結構體進行一下說明。
 
uint32_t eccbytesECC的字節數,對於512B-per-pageNAND來說,eccbytes = 3,如果你需要額外用到oob中的數據,那麼也可以大於3.
uint32_t eccpos[64]ECC數據在oob中的位置,這裏之所以是個64字節的數組,是因爲對於2048-per-pageNAND來說,它的oob64個字節。而對於512B-per-pageNAND來說,可以而且只可以定義它的前16個字節。
uint32_t oobavailoob中可用的字節數,這個值不用賦值,MTD會根據其它三個變量自動計算得到。
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]:顯示定義空閒的oob字節。
四、基於MTDNAND驅動架構
 
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芯片時,會調用structmtd_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芯片的其它操作,如寫,擦除等,都與讀操作類似。
 
五、NAND驅動中的probe函數
 
對於很多嵌入式Linux的外設driver來說,probe函數將是我們遇到的第一個與具體硬件打交道,同時也相對複雜的函數。而且根據我的經驗,對於很多外設的driver來說,只要能成功實現probe函數,那基本上完成這個外設的driver也就成功了一多半,基於MTDNAND driver就是一個典型的例子。稍後就可以看到,在NAND driverprobe函數中,就已經涉及到了對NAND芯片的讀寫。
 
在基於MTDNAND driverprobe函數中,主要可以分爲兩部分內容,其一是與很多外設driver類似的一些工作,如申請地址,中斷,DMA等資源,kzalloc及初始化一些結構體,分配DMA用的內存等等;其二就是與MTD相關的一
 
些特定的工作,在這裏我們將只描述第二部分內容。
 
1probe函數中與MTD相關的結構體
 
probe函數中,我們需要爲三個與MTD相關的結構體分配內存以及初始化,它們是struct mtd_infostructmtd_partitionstruct nand_chip。其中前兩者已經在前面做過說明,在此略過,這裏只對struct nand_chip做一些介紹。
 
struct nand_chip是一個與NAND芯片密切相關的結構體,主要包含三方面內容:
 
A.  指向一些操作NAND芯片的函數的指針,稍後將對這些函數指針作一些說明;
 
B.  表示NAND芯片特性的成員變量,主要有:
 
unsigned int options:與具體的NAND芯片相關的一些選項,如NAND_NO_AUTOINCRNAND_BUSWIDTH_16等,至於這些選項具體表示什麼含義,可以參考<linux/mtd/nand.h>,那裏有較爲詳細的說明;
 
int page_shift:用位表示的NAND芯片的page大小,如某片NAND芯片的一個page512 個字節,那麼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   chip_shift:用位表示的NAND芯片的容量;
 
int   numchips:表示系統中有多少片NAND芯片;
 
unsigned long chipsizeNAND芯片的大小;
 
int   pagemask:計算page number時的掩碼,總是等於chipsize/page大小  1
int   pagebuf:用來保存當前讀取的NAND芯片的page number,這樣一來,下次讀取的數據若還是屬於同一個page,就不必再從NAND芯片讀取了,而是從data_buf中直接得到;
 
int   badblockpos:表示壞塊信息保存在oob中的第幾個字節。在每個block的第一個pageoob中,通常用12個字節來表示這是否爲一個壞塊。對於絕大多數的NAND芯片,若page size  > 512,那麼壞塊信息從Byte 0開始存儲,否則就存儲在Byte 5,即第六個字節。
 
C.  eccoobbbt (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中幾個重要的函數指針做一些說明。

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_RIO_ADDR_W8NAND芯片的讀寫地址,如果你的NAND controller是用PIO模式與NAND芯片交互,那麼只要把這兩個值賦上合適的地址,就完全可以使用MTD提供的default的讀寫函數來操作NAND芯片了。所以這兩個變量視具體的NAND controller而定,不一定用得着;
 
read_byteread_word:從NAND芯片讀一個字節或一個字,通常MTD會在讀取NAND芯片的IDSTATUSOOB中的壞塊信息時調用這兩個函數,具體是這樣的流程,首先MTD調用cmdfunc函數,發起相應的命令,NAND芯片收到命令後就會做好準備,最後MTD就會調用read_byteread_word函數從NAND芯片中讀取芯片的IDSTATUS或者OOB
 
read_bufwrite_bufverify_buf:分別是從NAND芯片讀取數據到buffer,把buffer中的數據寫入到NAND芯片,和從NAND芯片中讀取數據並驗證。調用read_buf時的流程與read_byteread_word類似,MTD也是先調用cmdfunc函數發起讀命令(NAND_CMD_READ0命令),接着NAND芯片收到命令後做好準備,最後MTD再調用read_buf函數把NAND芯片中的數據讀取到buffer中。調用write_buf函數的流程與read_buf相似;
 
select_chip:因爲系統中可能有不止一片NAND芯片,所以在對NAND芯片進行操作前,需要這個函數來指定一片NAND芯片;
 
cmdfunc:向NAND芯片發起命令;
 
waitfuncNAND芯片在接收到命令後,並不一定能立即響應NAND controller的下一步動作,對有些命令,比如eraseprogram等命令,NAND芯片需要一定的時間來完成,所以就需要這個waitfunc來等待NAND芯片完成命令,並再次進入準備好狀態;
write_page:把一個page的數據寫入NAND芯片,這個函數一般不需我們實現,因爲它會調用struct nand_ecc_ctrl中的write_page_raw或者write_page函數,關於這兩個函數將在稍後介紹。
 
以上提到的這些函數指針,都是REPLACEABLE的,也就是說都是可以被替換的,根據你的NAND controller,如果你需要自己實現相應的函數,那麼只需要把你的函數賦值給這些函數指針就可以了,如果你沒有賦值,那麼MTD會把它自己定義的default的函數賦值給它們。
 
順便提一下,以上所說的讀寫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中罷了。
 
最後,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 controllerNAND芯片發出NAND_ECC_READNAND_ECC_WRITENAND_ECC_READSYN等命令,與struct nand_chip結構體中的cmdfunc類似,只不過發起的命令是ECC相關的罷了;
 
calculate:根據data計算ecc值;
 
correct:根據ecc值,判斷讀寫數據時是否有錯誤發生,若有錯,則立即試着糾正,糾正失敗則返回錯誤;
 
read_page_rawwrite_page_raw:從NAND芯片中讀取一個page的原始數據和向NAND芯片寫入一個page的原始數據,所謂的原始數據,即不對讀寫的數據做ecc處理,該讀寫什麼值就讀寫什麼值。另外,這兩個函數會讀寫整個page中的所有內容,即不但會讀寫一個pageMAIN部分,還會讀寫OOB部分。
 
read_pagewrite_page:與read_page_rawwrite_page_raw類似,但不同的是,read_pagewrite_page在讀寫過程中會加入ecc的計算,校驗,和糾正等處理。
read_oobwrite_oob:讀寫oob中的內容,不包括MAIN部分。
 
其實,以上提到的這幾個read_xxxwrite_xxx函數,最終都會調用struct nand_chip中的read_bufwrite_buf這兩個函數,所以如果沒有特殊需求的話,我認爲不必自己實現,使用MTD提供的default的函數即可。
 
爲進一步理解各函數之間的調用關係,這裏提供一張從網上找到的write NAND芯片的流程圖,僅供參考:
 
 
 
3probe函數的工作流程
 
由前面的說明可知,我們在要對NAND芯片進行實際操作前已經爲struct mtd_infostruct mtd_partitionstruct nand_chip這三個結構體分配好了內存,接下來就要爲它們做一些初始化工作。
 
其中,我們需要爲struct mtd_info所做的初始化工作並不多,因爲MTD Core會在稍後爲它做很多初始化工作,但是有一點必須由我們來做,那就是把指向struct nand_chip結構體的指針賦給struct mtd_infopriv成員變量,因爲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_RIO_ADDR_W賦上地址,其它則什麼都不做,利用MTD提供的函數即可。
 
現在假定你定義好了所有需要的與NAND芯片交互的函數,並已經把它們賦給了struct nand_chip結構體中的函數指針。當然,此時你還不能保證這些函數一定能正確工作,但是沒有關係,probe函數在接下來的工作中會調用到幾乎所有的這些函數,你可以依次來驗證和調試。當你的probe函數能順利通過後,那麼這些函數也就基本沒什麼問題了,你的NAND驅動也就已經完成了80%了。
 
接下來,probe函數就會開始與NAND芯片進行交互了,它要做的事情主要包括這幾個方面:讀取NAND芯片的ID,然後查表得到這片NAND芯片的如廠商,page sizeerase size以及chip size等信息,接着,根據struct nand_chipoptions的值的不同,或者在NAND芯片中的特定位置查找bad block table,或者scan整個NAND芯片,並在內存中建立bad block table。說起來複雜,但其實所有的這些動作,都可以在MTD提供的一個叫做nand_scan的函數中完成。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章