大約用了兩個禮拜不到的時間爲公司的IPcamera系統寫了基於MTD的NAND驅動(linux-2.6.22.10內核),目前已可以在該驅動的支持下跑cramfs和jffs2文件系統,另外,該驅動也可以同時支持small
page(每頁512 Byte)和big page(每頁2048 Byte)兩種NAND芯片。在此整理一下與NAND驅動相關的概念,結構體,驅動框架和流程,同時分析一下基於MTD的NAND驅動的部分函數,尤其是其中的nand_scan()函數。(涉及到具體NAND芯片時,若不做說明,將以small
page的NAND芯片爲例。)
注:個人理解,有誤難免!—— 筆者:曹榮榮
MTD 驅動程序是專門針對嵌入式Linux的一種驅動程序,相對於常規塊設備驅動程序(比如PC中的IDE硬盤)而言,MTD驅動程序能更好的支持和管理閃存設備,因爲它本身就是專爲閃存設備而設計的。
具體地講,基於MTD的FLASH驅動,承上可以很好地支持cramfs,jffs2和yaffs等文件系統,啓下也能對FLASH的擦除,讀寫,FLASH壞塊以及損耗平衡進行很好的管理。所謂損耗平衡,是指對NAND的擦寫不能總是集中在某一個或某幾個block中,這是由NAND芯片有限的擦寫次數的特性決定的。
總之,在現階段,要爲FLASH設備開發Linux下的驅動程序,那麼基於MTD的開發將幾乎是省時又省力的唯一選擇!
一、NAND和NOR的區別
Google “Nand Flash和Nor
Flash的區別”。
簡單點說,主要的區別就是:
1、 NAND比NOR便宜;NAND的容量比NOR大(指相同成本);NAND的擦寫次數是NOR的十倍;NAND的擦除和寫入速度比NOR快,讀取速度比NOR稍慢;
2、 NAND和NOR的讀都可以以字節爲單位,但NAND的寫以page爲單位,而NOR可以隨機寫每一個字節。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芯片,即OneNAND
Flash。我尚未對OneNand FLASH有過研究,只是通過網上資料得知,OneNand FLASH克服了傳統NAND Flash接口複雜的缺點,具有接口簡單、讀寫速度快、容量大、壽命長、成本低等優點,因此應該是一種較常用NAND先進的FLASH吧,只是目前似乎普及率並不高,本文也將不做討論。
因此,若只是開發基於MTD的NAND驅動程序,那麼我們需要關注的代碼就基本上全在linux-2.6.22.10/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驅動,其實所需閱讀的代碼量也不是很大。
另外,在動手寫NAND驅動之前,也許需要讀一下以下文檔:
1、 Linux MTD 源代碼分析:
該文檔可以讓我們對MTD有一個直觀而又相對具體的認識,但它似乎主要是針對NOR
FLASH的,對於實際開發NAND驅動的幫助並不是很大。
2、 MTD NAND Driver Programming Interface:
該文檔中關於ECC的說明很有幫助。
3、 MTD的官方網站:
三、NAND相關原理
在我們開始NAND驅動編寫之前,至少應該知道:數據在NAND中是怎樣存儲的,以及以怎樣的方式從NAND中讀寫數據時。
1、 NAND的存儲結構和操作方式
這方面的資料可以從任意一種NAND的datasheet中得到,因爲基本上每一種NAND的datasheet都會介紹NAND的組成結構和操作命令,而且事實上,大多數的NAND
datasheet都大同小異,所不同的大概只是該NAND芯片的容量大小和讀寫速度等基本特性。
這裏以每頁512字節的NAND FLASH爲例簡單說明一下:每一塊NAND芯片由n個block組成->每一個block由m個page組成->每一個page由256字節大小的column1(也稱1st
half page)、256字節大小的column2(也稱2nd half page)和16字節大小的oob(out-of-band,也稱spare
area)組成。至於m和n的大小可以查看特定NAND的datasheet。相應的,若給定NAND中的一個字節的地址,我們可以根據這個地址算出block地址(即第幾個block)、page地址(即該block中的第幾個page)和column地址(即1st
half page,或2nd half page,或oob中的第幾個字節)。
在擦除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字節);(2)進行ECC檢測時(不管是hardware
ECC還是software ECC),一次讀寫一整個page(包括16字節的oob部分)。所以部分NAND所支持的寫不足一個page大小數據的功能,對MTD來說是用不着的。
那麼,如果只需要讀寫不足一個page大小的數據怎麼辦?這是MTD更上層的部分需要處理的事。也就是說,對於NAND驅動來說,它只會讀寫整整一個page的數據!
最後值得一提的是,NAND驅動有可能只去讀oob部分,這是因爲除了ECC信息之外,壞塊信息也存儲在oob之中,NAND驅動需要讀取oob中描述壞塊的那個字節(通常是每個block的第一個page的oob中的第六個字節)來判斷該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芯片來說,其中壞塊信息佔據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 eccbytes:ECC的字節數,對於512B-per-page的NAND來說,eccbytes
= 3,如果你需要額外用到oob中的數據,那麼也可以大於3.
uint32_t eccpos[64]:ECC數據在oob中的位置,這裏之所以是個64字節的數組,是因爲對於2048-per-page的NAND來說,它的oob有64個字節。而對於512B-per-page的NAND來說,可以而且只可以定義它的前16個字節。
uint32_t oobavail:oob中可用的字節數,這個值不用賦值,MTD會根據其它三個變量自動計算得到。
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]:顯示定義空閒的oob字節。
四、基於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、以上代碼只是向內核註冊了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內核中有很多例子可以參考。
相對來說,第一種方式更加方便和直觀一點,而第二種方式則更加靈活一點。
C、 在加載NAND驅動時,我們還需要向MTD Core提供一個信息,那就是NAND的分區信息,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芯片的讀寫主要分三部分:
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芯片的其它操作,如寫,擦除等,都與讀操作類似。
對於很多嵌入式Linux的外設driver來說,probe函數將是我們遇到的第一個與具體硬件打交道,同時也相對複雜的函數。而且根據我的經驗,對於很多外設的driver來說,只要能成功實現probe函數,那基本上完成這個外設的driver也就成功了一多半,基於MTD的NAND
driver就是一個典型的例子。稍後就可以看到,在NAND driver的probe函數中,就已經涉及到了對NAND芯片的讀寫。
在基於MTD的NAND driver的probe函數中,主要可以分爲兩部分內容,其一是與很多外設driver類似的一些工作,如申請地址,中斷,DMA等資源,kzalloc及初始化一些結構體,分配DMA用的內存等等;其二就是與MTD相關的一
些特定的工作,在這裏我們將只描述第二部分內容。
1、probe函數中與MTD相關的結構體
在probe函數中,我們需要爲三個與MTD相關的結構體分配內存以及初始化,它們是struct mtd_info、structmtd_partition和struct
nand_chip。其中前兩者已經在前面做過說明,在此略過,這裏只對struct nand_chip做一些介紹。
struct nand_chip是一個與NAND芯片密切相關的結構體,主要包含三方面內容:
A. 指向一些操作NAND芯片的函數的指針,稍後將對這些函數指針作一些說明;
B. 表示NAND芯片特性的成員變量,主要有:
unsigned int options:與具體的NAND芯片相關的一些選項,如NAND_NO_AUTOINCR,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 chip_shift:用位表示的NAND芯片的容量;
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中的第幾個字節。在每個block的第一個page的oob中,通常用1或2個字節來表示這是否爲一個壞塊。對於絕大多數的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中幾個重要的函數指針做一些說明。
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的函數賦值給它們。
順便提一下,以上所說的讀寫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 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的函數即可。
爲進一步理解各函數之間的調用關係,這裏提供一張從網上找到的write NAND芯片的流程圖,僅供參考:
3、probe函數的工作流程
由前面的說明可知,我們在要對NAND芯片進行實際操作前已經爲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芯片交互的函數,並已經把它們賦給了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的函數中完成。