2. 軟體方面
如果想要在Linux下編寫Nand Flash驅動,那麼就先要搞清楚Linux下,關于此部分的整個架構。弄明白,系統是如何管理你的nand flash的,以及,系統都幫你做了那些準備工作,而剩下的,驅動底層實作部分,你要去實作哪些功能,才能使得硬體正常工作起來。
【記憶體技術裝置,MTD(Memory Technology Device)】
MTD,是Linux的儲存設備中的一個子系統。其設計此系統的目的是,對于記憶體類的裝置,提供一個抽象層,一個接口,使得對于硬體驅動設計者來說,可以盡量少的去關心存儲格式,比如FTL,FFS2等,而隻需要去提供最簡單的底層硬體裝置的讀/寫/擦除函數就可以了。而對于資料對于上層使用者來說是如何表示的,硬體驅動設計者可以不關心,而MTD儲存設備子系統都幫你做好了。
對于MTD字系統的好處,簡單解釋就是,他幫助你實作了,很多對于以前或者其他系統來說,本來也是你驅動設計者要去實作的很多功能。換句話說,有了MTD,使得你設計Nand Flash的驅動,所要做的事情,要少很多很多,因為大部分工作,都由MTD幫你做好了。
當然,這個好處的一個“副作用”就是,使得我們不了解的人去了解整個Linux驅動架構,以及MTD,變得更加複雜。但是,總的說,覺得是利遠遠大于弊,否則,就不僅需要你了解,而且還是做更多的工作,實作更多的功能了。
此外,還有一個重要的原因,那就是,前面提到的nand flash和普通硬碟等裝置的特殊性:
有限的通過出複用來實作輸入輸出指令和位址/資料等的IO接口,最小機關是頁而不是常見的bit,寫前需擦除等,導緻了這類裝置,不能像平常對待硬碟等操作一樣去操作,隻能采取一些特殊方法,這就誕生了MTD裝置的統一抽象層。
MTD,将nand flash,nor flash和其他類型的flash等裝置,統一抽象成MTD裝置來管理,根據這些裝置的特點,上層實作了常見的操作函數封裝,底層具體的内部實作,就需要驅動設計者自己來實作了。具體的内部硬體裝置的讀/寫/擦除函數,那就是你必須實作的了。
HARD drives
MTD device
連續的扇區
連續的可擦除塊
扇區都很小(512B,1024B)
可擦除塊比較大 (32KB,128KB)
主要通過兩個操作對其維護操作:讀扇區,寫扇區
主要通過三個操作對其維護操作:從擦除塊中讀,寫入擦除塊,擦寫可擦除塊
壞快被重新映射,并且被硬體隐藏起來了(至少是在如今常見的LBA硬碟裝置中是如此)
壞的可擦除塊沒有被隐藏,軟體中要處理對應的壞塊問題。
HDD扇區沒有擦寫壽命超出的問題。
可擦除塊是有擦除次數限制的,大概是104-105次.
表4.MTD裝置和硬碟裝置之間的差別
多說一句,關于MTD更多的内容,感興趣的,去附錄中的MTD的首頁去看。
關于mtd裝置驅動,感興趣的可以去參考
<a href="http://www.cnitblog.com/luofuchong/archive/2007/08/31/32682.html" target="_blank">MTD原始裝置與FLASH硬體驅動的對話</a>
<a href="http://www.cnitblog.com/luofuchong/archive/2007/09/04/32939.html" target="_blank">MTD原始裝置與FLASH硬體驅動的對話-續</a>
那裡,算是比較詳細地介紹了整個流程,友善大家了解整個mtd架構和nand flash驅動。
【Nand flash驅動工作原理】
在介紹具體如何寫Nand Flash驅動之前,我們先要了解,大概的,整個系統,和Nand Flash相關的部分的驅動工作流程,這樣,對于後面的驅動實作,才能更加清楚機制,才更容易實作,否則就是,即使寫完了代碼,也還是沒搞懂系統是如何工作的了。
讓我們以最常見的,Linux核心中已經有的三星的Nand Flash驅動,來解釋Nand Flash驅動具體流程和原理。
此處是參考2.6.29版本的Linux源碼中的\drivers\mtd\nand\s3c2410.c,以2410為例。
1. 在nand flash驅動加載後,第一步,就是去調用對應的init函數,s3c2410_nand_init,去将在nand flash驅動注冊到Linux驅動架構中。
2. 驅動本身,真正開始,是從probe函數,s3c2410_nand_probe->s3c24xx_nand_probe,
在probe過程中,去用clk_enable打開nand flash控制器的clock時鐘,用request_mem_region去申請驅動所需要的一些記憶體等相關資源。然後,在s3c2410_nand_inithw中,去初始化硬體相關的部分,主要是關于時鐘頻率的計算,以及啟用nand flash控制器,使得硬體初始化好了,後面才能正常工作。
3. 需要多解釋一下的,是這部分代碼:
for (setno = 0; setno < nr_sets; setno++, nmtd++) {
pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
/* 調用init chip去挂載你的nand 驅動的底層函數到nand flash的結構體中,以及設定對應的ecc mode,挂載ecc相關的函數 */
s3c2410_nand_init_chip(info, nmtd, sets);
/* scan_ident,掃描nand 裝置,設定nand flash的預設函數,獲得實體裝置的具體型号以及對應各個特性參數,這部分算出來的一些值,對于nand flash來說,是最主要的參數,比如nand falsh的晶片的大小,塊大小,頁大小等。 */
nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
(sets) ? sets->nr_chips : 1);
if (nmtd->scan_res == 0) {
s3c2410_nand_update_chip(info, nmtd);
/* scan tail,從名字就可以看出來,是掃描的後一階段,此時,經過前面的scan_ident,我們已經獲得對應nand flash的硬體的各個參數,然後就可以在scan tail中,根據這些參數,去設定其他一些重要參數,尤其是ecc的layout,即ecc是如何在oob中擺放的,最後,再去進行一些初始化操作,主要是根據你的驅動,如果沒有實作一些函數的話,那麼就用系統預設的。 */
nand_scan_tail(&nmtd->mtd);
/* add partion,根據你的nand flash的分區設定,去分區 */
s3c2410_nand_add_partition(info, nmtd, sets);
}
if (sets != NULL)
sets++;
}
4. 等所有的參數都計算好了,函數都挂載完畢,系統就可以正常工作了。
上層通路你的nand falsh中的資料的時候,通過MTD層,一層層調用,最後調用到你所實作的那些底層通路硬體資料/緩存的函數中。
【Linux下nand flash驅動編寫步驟簡介】
關于上面提到的,在nand_scan_tail的時候,系統會根據你的驅動,如果沒有實作一些函數的話,那麼就用系統預設的。如果實作了自己的函數,就用你的。
估計很多人就會問了,那麼到底我要實作哪些函數呢,而又有哪些是可以不實作,用系統預設的就可以了呢。
此問題的,就是我們下面要介紹的,也就是,你要實作的,你的驅動最少要做哪些工作,才能使整個nand flash工作起來。
1. 對于驅動架構部分
其實,要了解,關于驅動架構部分,你所要做的事情的話,隻要看看三星的整個nand flash驅動中的這個結構體,就差不多了:
static struct platform_driver s3c2410_nand_driver = {
.probe = s3c2410_nand_probe,
.remove = s3c2410_nand_remove,
.suspend = s3c24xx_nand_suspend,
.resume = s3c24xx_nand_resume,
.driver = {
.name = "s3c2410-nand",
.owner = THIS_MODULE,
},
};
對于上面這個結構體,沒多少要解釋的。從名字,就能看出來:
(1)probe就是系統“探測”,就是前面解釋的整個過程,這個過程中的多數步驟,都是和你自己的nand flash相關的,尤其是那些硬體初始化部分,是你必須要自己實作的。
(2)remove,就是和probe對應的,“反初始化”相關的動作。主要是釋放系統相關資源和關閉硬體的時鐘等常見操作了。
(3)suspend和resume,對于很多沒用到電源管理的情況下,至少對于我們剛開始寫基本的驅動的時候,可以不用關心,放個空函數即可。
2. 對于nand flash底層操作實作部分
而對于底層硬體操作的有些函數,總體上說,都可以在上面提到的s3c2410_nand_init_chip中找到:
static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
struct s3c2410_nand_mtd *nmtd,
struct s3c2410_nand_set *set)
{
struct nand_chip *chip = &nmtd->chip;
void __iomem *regs = info->regs;
chip->write_buf = s3c2410_nand_write_buf;
chip->read_buf = s3c2410_nand_read_buf;
chip->select_chip = s3c2410_nand_select_chip;
chip->chip_delay = 50;
chip->priv = nmtd;
chip->options = 0;
chip->controller = &info->controller;
switch (info->cpu_type) {
case TYPE_S3C2410:
/* nand flash控制器中,一般都有對應的資料寄存器,用于給你往裡面寫資料,表示将要讀取或寫入多少個位元組(byte,u8)/字(word,u32) ,是以,此處,你要給出位址,以便後面的操作所使用 */
chip->IO_ADDR_W = regs + S3C2410_NFDATA;
info->sel_reg = regs + S3C2410_NFCONF;
info->sel_bit = S3C2410_NFCONF_nFCE;
chip->cmd_ctrl = s3c2410_nand_hwcontrol;
chip->dev_ready = s3c2410_nand_devready;
break;
。。。。。。
}
chip->IO_ADDR_R = chip->IO_ADDR_W;
nmtd->info = info;
nmtd->mtd.priv = chip;
nmtd->mtd.owner = THIS_MODULE;
nmtd->set = set;
if (hardware_ecc) {
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
chip->ecc.correct = s3c2410_nand_correct_data;
/* 此處,多數情況下,你所用的Nand Flash的控制器,都是支援硬體ECC的,是以,此處設定硬體ECC(HW_ECC) ,也是充分利用硬體的特性,而如果此處不用硬體去做的ECC的話,那麼下面也會去設定成NAND_ECC_SOFT,系統會用預設的軟體去做ECC校驗,相比之下,比硬體ECC的效率就低很多,而你的nand flash的讀寫,也會相應地要慢不少*/
chip->ecc.mode = NAND_ECC_HW;
switch (info->cpu_type) {
case TYPE_S3C2410:
chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
chip->ecc.calculate = s3c2410_nand_calculate_ecc;
break;
。。。。。
} else {
chip->ecc.mode = NAND_ECC_SOFT;
if (set->ecc_layout != NULL)
chip->ecc.layout = set->ecc_layout;
if (set->disable_ecc)
chip->ecc.mode = NAND_ECC_NONE;
}
而我們要實作的底層函數,也就是上面藍色标出來的一些函數而已:
(1)s3c2410_nand_write_buf 和 s3c2410_nand_read_buf:這是兩個最基本的操作函數,其功能,就是往你的nand flash的控制器中的FIFO讀寫資料。一般情況下,是MTD上層的操作,比如要讀取一頁的資料,那麼在發送完相關的讀指令和等待時間之後,就會調用到你底層的read_buf,去nand Flash的FIFO中,一點點把我們要的資料,讀取出來,放到我們制定的記憶體的緩存中去。寫操作也是類似,将我們記憶體中的資料,寫到Nand Flash的FIFO中去。具體的資料流向,參考上面的圖4。
(2)s3c2410_nand_select_chip : 實作Nand Flash的片選。
(3)s3c2410_nand_hwcontrol:給底層發送指令或位址,或者設定具體操作的模式,都是通過此函數。
(4)s3c2410_nand_devready:Nand Flash的一些操作,比如讀一頁資料,寫入(程式設計)一頁資料,擦除一個塊,都是需要一定時間的,在命發送完成後,就是硬體開始忙着工作的時候了,而硬體什麼時候完成這些操作,什麼時候不忙了,變就緒了,就是通過這個函數去檢查狀态的。一般具體實作都是去讀硬體的一個狀态寄存器,其中某一位是否是1,對應着是出于“就緒/不忙”還是“忙”的狀态。這個寄存器,也就是我們前面分析時序圖中的R/B#。
(5)s3c2410_nand_enable_hwecc: 在硬體支援的前提下,前面設定了硬體ECC的話,要實作這個函數,用于每次在讀寫操作前,通過設定對應的硬體寄存器的某些位,使得啟用硬體ECC,這樣在讀寫操作完成後,就可以去讀取硬體校驗産生出來的ECC數值了。
(6)s3c2410_nand_calculate_ecc:如果是上面提到的硬體ECC的話,就不用我們用軟體去實作校驗算法了,而是直接去讀取硬體産生的ECC數值就可以了。
(7)s3c2410_nand_correct_data:當實際操作過程中,讀取出來的資料所對應的硬體或軟體計算出來的ECC,和從oob中讀出來的ECC不一樣的時候,就是說明資料有誤了,就需要調用此函數去糾正錯誤。對于現在SLC常見的ECC算法來說,可以發現2位,糾正1位。如果錯誤大于1位,那麼就無法糾正回來了。一般情況下,出錯超過1位的,好像幾率不大。至少我看到的不是很大。更複雜的情況和更加注重資料安全的情況下,一般是需要另外實作更高效和檢錯和糾錯能力更強的ECC算法的。
當然,除了這些你必須實作的函數之外,在你更加熟悉整個架構之後,你可以根據你自己的nand flash的特點,去實作其他一些原先用系統預設但是效率不高的函數,而用自己的更高效率的函數替代他們,以提升你的nand flash的整體性能和效率。
【引用文章】
<a href="http://hi.baidu.com/serial_story/blog/item/3f1635d1dc041cd7562c84a1.html" target="_blank">http://hi.baidu.com/serial_story/blog/item/3f1635d1dc041cd7562c84a1.html</a>
2. Samsung的型号為K9G8G08U0M的Nand Flash的資料手冊
要下載下傳資料手冊,可以去這裡介紹的網站下載下傳:
samsung 4K pagesize SLC Nand Flash K9F8G08U0M datasheet + 推薦一個datasheet搜尋的網站
<a href="http://hi.baidu.com/serial_story/blog/item/7f25a03def1de309bba167c8.html" target="_blank">http://hi.baidu.com/serial_story/blog/item/7f25a03def1de309bba167c8.html</a>
<a href="http://hi.baidu.com/serial_story/blog/item/f06db3546eced11a3b29356c.html" target="_blank">http://hi.baidu.com/serial_story/blog/item/f06db3546eced11a3b29356c.html</a>
4. Memory Technology Device (MTD) Subsystem for Linux.
<a href="http://www.linux-mtd.infradead.org/index.html" target="_blank">http://www.linux-mtd.infradead.org/index.html</a>