3、基於MTD的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_infostruct mtd_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, 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_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 結構體中還包含了一個很重要的結構體,即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 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 的函數中完成。
 
我雖然研讀過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 函數,因爲在這兩者之間,我們還需要做一些額外的工作。那麼這裏所謂的額外的工作,具體是做什麼呢?
 
在《基於MTDNAND 驅動開發() 》中介紹過一個叫做struct nand_ecclayout 的結構體,它用來定義eccoob 中的佈局。對於small page( 每頁512 Byte)big page( 每頁2048 Byte) 的兩種NAND 芯片,它們的eccoob 中的佈局不盡相同。
 
如果你的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 的佈局了。最後,我們就可

發佈了81 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章