五、NAND 驅動中的probe 函數
對於很多嵌入式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 、struct
mtd_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, constuint8_t
* buf,int len)
;
void (
* read_buf)
( struct mtd_info
* mtd, uint8_t* buf,
int len)
;
int (
* verify_buf)
( struct mtd_info
* mtd, constuint8_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 結構體中還包含了一個很重要的結構體,即structstruct nand_ecc_ctrl ,該結構體中也定義了幾個很重要的函數指針。它的定義如下:
struct nand_ecc_ctrl{
……
void (
* hwctl) (struct mtd_info
* mtd,int mode)
;
int (
* calculate)
( struct mtd_info
* mtd, constuint8_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 的函數中完成。
我雖然研讀過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 的佈局了。最後,我們就可