Linux下NAND FLASH驅動開發

但是由於物理製程 / 製造方面的原因,導致 nor nand 在一些具體操作方面的特性不同:

 

NOR

NAND

(備註)

接口

總線

I/O 接口

這個兩者最大的區別

單個 cell 大小

 

單個 Cell 成本

 

讀耗時

 

單字節的編程時間

 

多字節的編程時間

 

擦除時間

 

功耗

低,但是需要額外的 RAM

 

是否可以執行代碼

不行 , 但是一些新的芯片,可以在第一頁之外執行一些小的 loader 1

即是否允許,芯片內執行 (XIP, eXecute In Place) (2)

位反轉 (Bit twiddling/bit flip)

幾乎無限制

1-4 次,也稱作 “部分頁編程限制”

也就是數據錯誤, 0->1 1->0

在芯片出廠時候是否允許壞塊

不允許

允許

 

3 Nand Flash Nor Flash 的區別

1.       理論上是可以的,而且也是有人驗證過可以的,只不過由於 nand flash 的物理特性,不能完全保證所讀取的數據 / 代碼是正確的,實際上,很少這麼用而已。因爲,如果真是要用到 nand flash XIP ,那麼除了讀出速度慢之外,還要保證有數據的校驗,以保證讀出來的,將要執行的代碼 / 數據,是正確的。否則,系統很容易就跑飛了。。。

2.       芯片內執行 (XIP, eXecute In Place) :

http://hi.baidu.com/serial_story/blog/item/adb20a2a3f8ffe3c5243c1df.html

 

Nand Flash 的種類】

具體再分,又可以分爲

1)Bare NAND chips :裸片,單獨的 nand 芯片

2)SmartMediaCards = 裸片 + 一層薄塑料,常用於數碼相機和 MP3 播放器中。之所以稱 smart ,是由於其軟件 smart ,而不是硬件本身有啥 smart 之處。 ^_^

3)DiskOnChip :裸片 +glue logic glue logic= 硬件 ECC 產生器 + 用於靜態的 nand 芯片控制的寄存器 + 直接訪問一小片地址窗口,那塊地址中包含了引導代碼的 stub 樁,其可以從 nand flash 中拷貝真正的引導代碼。

 

spare area/oob

Nand 由於最初硬件設計時候考慮到,額外的錯誤校驗等需要空間,專門對應每個頁,額外設計了叫做 spare area 空區域,在其他地方,比如 jffs2 文件系統中,也叫做 oob out of band )數據。

其具體用途,總結起來有:

1.       標記是否是壞快

2.       存儲 ECC 數據

3.       存儲一些和文件系統相關的數據,如 jffs2 就會用到這些空間存儲一些特定信息, yaffs2 文件系統,會在 oob 中,存放很多和自己文件系統相關的信息。

 

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 設備驅動,感興趣的可以去參考

MTD 原始設備與 FLASH 硬件驅動的對話

MTD 原始設備與 FLASH 硬件驅動的對話 -

那裏,算是比較詳細地介紹了整個流程,方便大家理解整個 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 的整體性能和效率。

 

 原文地址 http://green-waste.blog.163.com/blog/static/326776782009622112913547/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章