如何編寫linux下nandflash驅動-4

maojunxu發表於2018-03-09

2.       軟體方面

如果想要在Linux下編寫Nand Flash驅動,那麼就先要搞清楚Linux下,關於此部分的整個框架。弄明白,系統是如何管理你的nand flash的,以及,系統都幫你做了那些準備工作,而剩下的,驅動底層實現部分,你要去實現哪些功能,才能使得硬體正常工作起來。

 

【記憶體技術裝置,MTDMemory Technology Device)】

MTD,是Linux的儲存裝置中的一個子系統。其設計此係統的目的是,對於記憶體類的裝置,提供一個抽象層,一個介面,使得對於硬體驅動設計者來說,可以儘量少的去關心儲存格式,比如FTLFFS2等,而只需要去提供最簡單的底層硬體裝置的讀//擦除函式就可以了。而對於資料對於上層使用者來說是如何表示的,硬體驅動設計者可以不關心,而MTD儲存裝置子系統都幫你做好了。

對於MTD字系統的好處,簡單解釋就是,他幫助你實現了,很多對於以前或者其他系統來說,本來也是你驅動設計者要去實現的很多功能。換句話說,有了MTD,使得你設計Nand Flash的驅動,所要做的事情,要少很多很多,因為大部分工作,都由MTD幫你做好了。

當然,這個好處的一個“副作用”就是,使得我們不瞭解的人去理解整個Linux驅動架構,以及MTD,變得更加複雜。但是,總的說,覺得是利遠遠大於弊,否則,就不僅需要你理解,而且還是做更多的工作,實現更多的功能了。

此外,還有一個重要的原因,那就是,前面提到的nand flash和普通硬碟等裝置的特殊性:

有限的通過出複用來實現輸入輸出命令和地址/資料等的IO介面,最小單位是頁而不是常見的bit,寫前需擦除等,導致了這類裝置,不能像平常對待硬碟等操作一樣去操作,只能採取一些特殊方法,這就誕生了MTD裝置的統一抽象層。

MTD,將nand flashnor 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原始碼中的driversmtd
ands3c2410.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)
“, 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中,根據這些引數,去設定其他一些重要引數,尤其是ecclayout,即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層,一層層呼叫,最後呼叫到你所實現的那些底層訪問硬體資料/快取的函式中。

 

Linuxnand 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,

       },

};

 

對於上面這個結構體,沒多少要解釋的。從名字,就能看出來:

1probe就是系統“探測”,就是前面解釋的整個過程,這個過程中的多數步驟,都是和你自己的nand flash相關的,尤其是那些硬體初始化部分,是你必須要自己實現的。

2remove,就是和probe對應的,“反初始化”相關的動作。主要是釋放系統相關資源和關閉硬體的時鐘等常見操作了。

(3)suspendresume,對於很多沒用到電源管理的情況下,至少對於我們剛開始寫基本的驅動的時候,可以不用關心,放個空函式即可。

 

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;

}

 

而我們要實現的底層函式,也就是上面藍色標出來的一些函式而已:

1s3c2410_nand_write_buf  s3c2410_nand_read_buf:這是兩個最基本的操作函式,其功能,就是往你的nand flash的控制器中的FIFO讀寫資料。一般情況下,是MTD上層的操作,比如要讀取一頁的資料,那麼在傳送完相關的讀命令和等待時間之後,就會呼叫到你底層的read_buf,去nand FlashFIFO中,一點點把我們要的資料,讀取出來,放到我們制定的記憶體的快取中去。寫操作也是類似,將我們記憶體中的資料,寫到Nand FlashFIFO中去。具體的資料流向,參考上面的圖4

2s3c2410_nand_select_chip  實現Nand Flash的片選。

3s3c2410_nand_hwcontrol:給底層傳送命令或地址,或者設定具體操作的模式,都是通過此函式。

4s3c2410_nand_devreadyNand Flash的一些操作,比如讀一頁資料,寫入(程式設計)一頁資料,擦除一個塊,都是需要一定時間的,在命傳送完成後,就是硬體開始忙著工作的時候了,而硬體什麼時候完成這些操作,什麼時候不忙了,變就緒了,就是通過這個函式去檢查狀態的。一般具體實現都是去讀硬體的一個狀態暫存器,其中某一位是否是1,對應著是出於“就緒/不忙”還是“忙”的狀態。這個暫存器,也就是我們前面分析時序圖中的R/B#

5s3c2410_nand_enable_hwecc 在硬體支援的前提下,前面設定了硬體ECC的話,要實現這個函式,用於每次在讀寫操作前,通過設定對應的硬體暫存器的某些位,使得啟用硬體ECC,這樣在讀寫操作完成後,就可以去讀取硬體校驗產生出來的ECC數值了。

6s3c2410_nand_calculate_ecc:如果是上面提到的硬體ECC的話,就不用我們用軟體去實現校驗演算法了,而是直接去讀取硬體產生的ECC數值就可以了。

7s3c2410_nand_correct_data:當實際操作過程中,讀取出來的資料所對應的硬體或軟體計算出來的ECC,和從oob中讀出來的ECC不一樣的時候,就是說明資料有誤了,就需要呼叫此函式去糾正錯誤。對於現在SLC常見的ECC演算法來說,可以發現2位,糾正1位。如果錯誤大於1位,那麼就無法糾正回來了。一般情況下,出錯超過1位的,好像機率不大。至少我看到的不是很大。更復雜的情況和更加註重資料安全的情況下,一般是需要另外實現更高效和檢錯和糾錯能力更強的ECC演算法的。

 

當然,除了這些你必須實現的函式之外,在你更加熟悉整個框架之後,你可以根據你自己的nand flash的特點,去實現其他一些原先用系統預設但是效率不高的函式,而用自己的更高效率的函式替代他們,以提升你的nand flash的整體效能和效率。

 

【引用文章】

1.Brief Intro of Nand Flash

http://hi.baidu.com/serial_story/blog/item/3f1635d1dc041cd7562c84a1.html

2. Samsung的型號為K9G8G08U0MNand Flash的資料手冊

要下載資料手冊,可以去這裡介紹的網站下載:

samsung 4K pagesize SLC Nand Flash K9F8G08U0M datasheet + 推薦一個datasheet搜尋的網站

http://hi.baidu.com/serial_story/blog/item/7f25a03def1de309bba167c8.html

3.Nand Falsh Read Operation

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

4. Memory Technology Device (MTD) Subsystem for Linux.

http://www.linux-mtd.infradead.org/index.html


相關文章