五、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,
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
的函數中完成。
我雖然研讀過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
的佈局了。最後,我們就可以順利地調用nand_scan_tail
函數了。