轉自 http://www.crifan.com/detailed_how_to_write_nand_flash_driver_v18_for_linux/
(這上面的文章很多,寫的也很詳細,強烈建議大家去看看,肯定會有收獲的)
【詳解】如何編寫Linux下Nand Flash驅動
版本:v2.1
摘要
本文先解釋了Nand Flash相關的一些名詞,再從Flash硬件機制開始,介紹到Nand Flash的常見的物理特性,且深入介紹了Nand Flash的一些高級功能,然后開始介紹Linux下面和Nand Flash相關的軟件架構MTD的相關知識,最后介紹了在Linux的MTD驅動框架下,如何實現Nand Flash的驅動。
2012-08-09
修訂歷史 | ||
---|---|---|
修訂 1.0 | 2009-07-21 | crl |
|
||
修訂 1.2 | 2011-03-15 | crl |
|
||
修訂 1.3 | 2011-06-12 | crl |
|
||
修訂 1.7 | 2011-07-02 | crl |
|
||
修訂 1.8 | 2011-10-04 | crl |
|
||
修訂 1.9 | 2012-06-14 | crl |
|
||
修訂 2.1 | 2012-08-09 | crl |
|
版權 © 2012 Crifan, http://crifan.com
目錄
- 縮略詞
- 正文之前
- 1. 編寫驅動之前要了解的知識
-
- 1.1. 一些相關的名詞的解釋
- 1.2. 硬件特性
-
- 1.2.1. 什么是Flash
- 1.2.2. 什么是Nand Flash
- 1.2.3. SLC和MLC的實現機制
- 1.2.4. Nand Flash數據存儲單元的整體架構
- 1.2.5. Nand Flash的物理存儲單元的陣列組織結構
- 1.2.6. Flash名稱的由來
- 1.2.7. Flash相對於普通設備的特殊性
- 1.2.8. Nand Flash的位反轉特性
- 1.2.9. Nand Flash引腳(Pin)的說明
- 1.2.10. Nand Flash的一些典型(typical)的特性
- 1.2.11. Nand Flash控制器與Nand Flash芯片
- 1.2.12. Nand Flash中的特殊硬件結構
- 1.2.13. Nand Flash中的壞塊(Bad Block)
- 1.2.14. Nand Flash中頁的訪問順序
- 1.2.15. 常見的Nand Flash的操作
- 1.2.16. Nand Flash的一些高級特性
-
- 1.2.16.1. Nand Flash的Unique ID
- 1.2.16.2. 片選無關(CE don’t-care)技術
- 1.2.16.3. 帶EDC的拷回操作以及Sector的定義(Copy-Back Operation with EDC & Sector Definition for EDC)
- 1.2.16.4. 多片同時編程(Simultaneously Program Multi Plane)
- 1.2.16.5. 交錯頁編程(Interleave Page Program)
- 1.2.16.6. 隨機輸出頁內數據(Random Data Output In a Page)
- 1.3. 軟件方面
- 2. Linux下Nand Flash驅動編寫步驟簡介
- 3. 和Nand Flash相關的一些資料
- 參考書目
插圖清單
- 1.1. 典型的Flash內存單元的物理結構
- 1.2. Nand Flash的結構圖
- 1.3. Nand Flash物理存儲單元的陣列組織結構
- 1.4. Nand Flash引腳功能說明
- 1.5. Nand Flash讀寫時的數據流向
- 1.6. Nand Flash K9K8G08U0A的命令集合
- 1.7. Nand Flash數據讀取操作的時序圖
- 1.8. Nand Flash的地址周期組成
- 1.9. Toshiba的Unique ID
- 1.10. ONFI的參數頁數據結構定義
- 1.11. ONFI中Unique ID的結構
- 1.12. ONFI中Read Unique ID命令的時序圖
- 1.13. ONFI中的Nand Flash的命令集合
- 1.14. Nand Flash數據讀取操作的時序圖
表格清單
- 1.1. Nand Flash和Nor Flash的區別
- 1.2. Nand Flash第3個ID的含義
- 1.3. Flash和普通設備相比所具有的特殊性
- 1.4. Nand Flash引腳功能的中文說明
- 1.5. MTD設備和硬盤設備之間的區別
公式清單
常用縮略詞如下:
B
- BBM (BBM)
-
Bad Block Management
壞塊管理
參見BBT.
- BBT (BBT)
-
Bad Block Table
壞塊表
E
- ECC (ECC)
-
Error Correction Code
錯誤校驗碼
- E 2PROM
-
見EEPROM.
- EEPROM (EEPROM)
-
Electrically Erasable Programmable Read-Only Memory
電可擦只讀存儲器
M
- MLC (MLC)
-
Multi Level Cell
多層單元
參見SLC.
- MOSFET (MOSFET)
-
Metal-Oxide–Semiconductor Field Effect Transistor
金屬氧化物半導體場效應晶體管
- MTD (MTD)
-
Memory Technology Device
內存技術設備
S
- SLC (SLC)
-
Single Level Cell
單層單元,單層式存儲
參見MLC.
本文的主要目的是,看了之后,你應該對Nand Flash的硬件特性以及對應的Linux下軟件平台有了基本的認識,進一步地,對如何實現Linux下的Nand Flash的驅動,知道要做哪些事情了,以及大概是如何實現的。這樣,如果有了對應的開發環境,你就可以自己去實現Nand Flash的驅動了。
不過額外提示一句的是,寫出代碼,並不代表你就完全搞懂了整個系統的流程。而且已經寫好的代碼,很可能有bug,要你不斷地調試,通過調試,你才會對整個系統以及Nand Flash的方方面面有個更深入的了解的。
而且,你會發現,為了寫驅動那點代碼之前,卻要弄懂太多的東西,包括硬件的工作原理,軟件的協議規范,軟件的邏輯架構等等,最后才能去實現你的驅 動,所以有人會說,你寫驅動不是很簡單嘛,不就是寫那幾行代碼嗎,對此,一個經典的回答就是,對於整個寫驅動的工作的價值算作100元的話,寫代碼值1塊 錢,但是知道怎么寫,值99塊錢。^_^
正因為此文目的是讓你搞懂如何在Linux下面實現Nand Flash的驅動,所以,目標讀者就是,希望對Nand Flash硬件知識有一定了解,和想要在Linux下面實現Nand Flash驅動的讀者。 而閱讀此文的前提,是要有一些基本的軟硬件基礎知識,和了解如何在v2.6內核之后Linux的下面開發驅動的流程。有了這些知識,再看本文,然后你才能清楚真正要去實現Nand Flash的驅動,是如何下手。
本文的邏輯是,先介紹Nand Flash的一些基本的硬件知識,然后詳細分析Nand Flash的Read操作的具體的流程,清楚硬件實現的邏輯,接着介紹軟件平台,即Linux下面和Nand Flash相關的內容,這樣,硬件和軟件都清楚是怎么回事了,然后再介紹如何去在Linux的架構下,實現Nand Flash驅動。
之前寫的版本,雖然前面關於Nand Flash的內容介紹的比較詳細,但是后面關於相關的MTD知識,尤其是Linux的MTD的架構和如何實現具體的Nand的Flash的操作等部分的內 容,寫的很簡略,導致有些讀者看了后,覺得是,關於如何寫驅動,和沒說差不多,呵呵。
因此,現在繼續更新,將更詳細的解釋,如何從硬件Nand Flash的規范,一步步映射到具體的軟件實現的過程,這樣,使得讀者更明白其中的內在邏輯,然后接着再介紹如何在理解了軟硬件各自的所具有的功能,以及 Linux的MTD系統,已經幫你實現了哪些功能,然后才會更加明白,余下的要實現的軟件部分,就是你所要實現的Linux下的Nand Flash的驅動部分了。
目錄
- 1.1. 一些相關的名詞的解釋
- 1.2. 硬件特性
-
- 1.2.1. 什么是Flash
- 1.2.2. 什么是Nand Flash
- 1.2.3. SLC和MLC的實現機制
- 1.2.4. Nand Flash數據存儲單元的整體架構
- 1.2.5. Nand Flash的物理存儲單元的陣列組織結構
- 1.2.6. Flash名稱的由來
- 1.2.7. Flash相對於普通設備的特殊性
- 1.2.8. Nand Flash的位反轉特性
- 1.2.9. Nand Flash引腳(Pin)的說明
- 1.2.10. Nand Flash的一些典型(typical)的特性
- 1.2.11. Nand Flash控制器與Nand Flash芯片
- 1.2.12. Nand Flash中的特殊硬件結構
- 1.2.13. Nand Flash中的壞塊(Bad Block)
- 1.2.14. Nand Flash中頁的訪問順序
- 1.2.15. 常見的Nand Flash的操作
- 1.2.16. Nand Flash的一些高級特性
-
- 1.2.16.1. Nand Flash的Unique ID
- 1.2.16.2. 片選無關(CE don’t-care)技術
- 1.2.16.3. 帶EDC的拷回操作以及Sector的定義(Copy-Back Operation with EDC & Sector Definition for EDC)
- 1.2.16.4. 多片同時編程(Simultaneously Program Multi Plane)
- 1.2.16.5. 交錯頁編程(Interleave Page Program)
- 1.2.16.6. 隨機輸出頁內數據(Random Data Output In a Page)
- 1.3. 軟件方面
NVM,即NV (RAM)Memory,斷電數據也不會丟失的存儲器,比如Nand Flash,Nor Flash,硬盤等等。於此相對的是,斷電了數據會丟失的存儲器,比如DRAM等。
OTP,一種非易失性存儲器,但是只允許一次性寫入數據,寫入(或稱燒寫)數據之后,就不能修改了。
OTP的好處或者說用途是,常用於寫入一些和芯片相關的一些特定數據,用於加密的一些數據等。
與一次性寫入數據的OTP相對應的是,像Nand Flash,硬盤等存儲器,可以被多次寫入數據。只要硬盤這類的存儲器沒壞,你高興寫入幾次就寫入幾次,而OTP就只能寫入一次,就沒法再修改里面的數據了。
NDA,中文可以翻譯為,非公開協議,保密協議。
說白了,還是一種協議,常用於這種情況:
某家廠商的某種技術或資料,是保密的,不希望公開的。
但是呢,如果你要用他家的芯片啊之類的東西,在開發過程中,又必須得到對應的技術和資料,才能開發產品,所以,他就會要求和你簽訂這樣的NDA協 議,意思就是,你可以用我的技術和資料,但是你不能公開給(我未授權的)其他人。如果非法泄露我的機密技術,那我肯定要走法律程序控告你,之類的。
英文datasheet,中文一般翻譯為數據手冊。
指的是對應某個硬件,多為芯片,的功能說明,定義了如何操作該硬件,達到你要的功能,這其中主要包括芯片中的相關寄存器的定義,如何發送命令,發送什么命令,以此來操作此硬件等等。
而英文Specification,引文常縮寫為Spec.,中文一般翻譯為規范。
多指某個組織(盈利的或非盈利的),定義了一些規矩,如果你要用某種東西,在計算機領域,常常指的是某硬件和相關的軟件協議,就要按照此規矩來操 作,人家這個組織呢,保證你只要實現了此規范,設備就能按照你所期望的運行,能夠實現對應的功能,而你的芯片實現了此規范,就叫做,是和此規范兼容 (compatible)的。
Nand Flash由於其物理特性,只有有限的擦寫次數,超過那個次數,基本上就是壞了。在使用過程中,有些Nand Flash的block會出現被用壞了,當發現了,要及時將此block標注為壞塊,不再使用。
於此相關的管理工作,屬於Nand Flash的壞塊管理的一部分工作。
Nand Flash的block的管理,還包括負載平衡。
正是由於Nand Flash的block,都是有一定壽命限制的,所以如果你每次都往同一個block擦除然后寫入數據,那么那個block就很容易被用壞了,所以我們要 去管理一下,將這么多次的對同一個block的操作,平均分布到其他一些block上面,使得在block的使用上,相對較平均,這樣相對來說,可以更能 充分利用Nand Flash。
關於wear-leveling這個詞,再簡單解釋一下,wear就是穿(衣服)等,用(東西)導致磨損,而leveling就是使得均衡,所以放 在一起就是,使得對於Nand Flash的那么多的block的使用磨損,相對均衡一些,以此延長Nand Flash的使用壽命或者說更加充分利用Nand Flash。
Nand Flash物理特性上使得其數據讀寫過程中會發生一定幾率的錯誤,所以要有個對應的錯誤檢測和糾正的機制,於是才有此ECC,用於數據錯誤的檢測與糾正。 Nand Flash的ECC,常見的算法有海明碼和BCH,這類算法的實現,可以是軟件也可以是硬件。不同系統,根據自己的需求,采用對應的軟件或者是硬件。
相對來說,硬件實現這類ECC算法,肯定要比軟件速度要快,但是多加了對應的硬件部分,所以成本相對要高些。如果系統對於性能要求不是很高,那么可 以采用軟件實現這類ECC算法,但是由於增加了數據讀取和寫入前后要做的數據錯誤檢測和糾錯,所以性能相對要降低一些,即Nand Flash的讀取和寫入速度相對會有所影響。
其中,Linux中的軟件實現ECC算法,即NAND_ECC_SOFT模式,就是用的對應的海明碼。
而對於目前常見的MLC的Nand Flash來說,由於容量比較大,動輒2GB,4GB,8GB等,常用BCH算法。BCH算法,相對來說,算法比較復雜。
筆者由於水平有限,目前仍未完全搞懂BCH算法的原理。
BCH算法,通常是由對應的Nand Flash的Controller中,包含對應的硬件BCH ECC模塊,實現了BCH算法,而作為軟件方面,需要在讀取數據后,寫入數據之前,分別操作對應BCH相關的寄存器,設置成BCH模式,然后讀取對應的 BCH狀態寄存器,得知是否有錯誤,和生成的BCH校驗碼,用於寫入。
其具體代碼是如何操作這些寄存器的,由於是和具體的硬件,具體的nand flash的controller不同而不同,無法用同一的代碼。如果你是nand flash驅動開發者,自然會得到對應的起nand flash的controller部分的datasheet,按照手冊說明,去操作即可。
不過,額外說明一下的是,關於BCH算法,往往是要從專門的做軟件算法的廠家購買的,但是Micron之前在網上放出一個免費版本的BCH算法。
想要此免費的BCH算法,可以在[17]找到下載地址
Flash全名叫做Flash Memory,從名字就能看出,是種數據存儲設備,存儲設備有很多類,Flash屬於非易失性存儲設備(Non-volatile Memory Device),與此相對應的是易失性存儲設備(Volatile Memory Device)。關於什么是非易失性/易失性,從名字中就可以看出,非易失性就是不容易丟失,數據存儲在這類設備中,即使斷電了,也不會丟失,這類設備, 除了Flash,還有其他比較常見的入硬盤,ROM等,與此相對的,易失性就是斷電了,數據就丟失了,比如大家常用的內存,不論是以前的 SDRAM,DDR SDRAM,還是現在的DDR2,DDR3等,都是斷電后,數據就沒了。
Flash的內部存儲是MOSFET,里面有個懸浮門(Floating Gate),是真正存儲數據的單元。
在Flash之前,紫外線可擦除(uv-erasable)的EPROM,就已經采用了Floating Gate存儲數據這一技術了。
數據在Flash內存單元中是以電荷(electrical charge) 形式存儲的。存儲電荷的多少,取決於圖中的外部門(external gate)所被施加的電壓,其控制了是向存儲單元中沖入電荷還是使其釋放電荷。而數據的表示,以所存儲的電荷的電壓是否超過一個特定的閾值Vth來表示, 因此,Flash的存儲單元的默認值,不是0(其他常見的存儲設備,比如硬盤燈,默認值為0),而是1,而如果將電荷釋放掉,電壓降低到一定程度,表述數 字0。
Flash主要分兩種,Nand Flash和nor flash。
關於Nand Flash和Nor Flash的區別,參見[5]
不過,關於兩者區別,除了那個解釋之外,這里再多解釋解釋:
-
Nor的成本相對高,容量相對小,比如常見的只有128KB,256KB,1MB,2MB等等,優點是讀寫數據時候,不容易出錯。所以在應用領域方面,Nor Flash比較適合應用於存儲少量的代碼。
-
Nand flash成本相對低,說白了就是便宜,缺點是使用中數據讀寫容易出錯,所以一般都需要有對應的軟件或者硬件的數據校驗算法,統稱為ECC。但優點是,相 對來說容量比較大,現在常見的Nand Flash都是1GB,2GB,更大的8GB的都有了,相對來說,價格便宜,因此適合用來存儲大量的數據。其在嵌入式系統中的作用,相當於PC上的硬盤, 用於存儲大量數據。
所以,一個常見的應用組合就是,用小容量的Nor Flash存儲啟動代碼,比如uboot,用大容量的Nand Flash做整個系統和用戶數據的存儲。
而一般的嵌入式平台的啟動流程也就是,系統從裝有啟動代碼的Nor Flash啟動后,初始化對應的硬件,包括SDRAM等,然后將Nand Flash上的Linux 內核讀取到內存中,做好該做的事情后,就跳轉到SDRAM中去執行內核了,然后內核解壓(如果是壓縮內核的話,否則就直接運行了)后,開始運行,在 Linux內核啟動最后,去Nand Flash上,掛載根文件,比如jffs2,yaffs2等,掛載完成,運行初始化腳本,啟動consle交互,才允許你通過console和內核交互。 至此完成整個系統啟動過程。
而Nor Flash就分別存放的是Uboot,Nand Flash存放的是Linux的內核鏡像和根文件系統,以及余下的空間分成一個數據區。
Nor flash,有類似於dram之類的地址總線,因此可以直接和CPU相連,CPU可以直接通過地址總線對nor flash進行訪問,而Nand Flash沒有這類的總線,只有IO接口,只能通過IO接口發送命令和地址,對Nand Flash內部數據進行訪問。相比之下,nor flash就像是並行訪問,Nand Flash就是串行訪問,所以相對來說,前者的速度更快些。
但是由於物理制程/制造方面的原因,導致nor 和nand在一些具體操作方面的特性不同:
表 1.1. Nand Flash和Nor Flash的區別
NOR | NAND | (備注) | |
---|---|---|---|
接口 | 總線 | I/O接口 | 這個是兩者物理結構上的最大區別 |
單個cell大小 | 大 | 小 | |
單個Cell成本 | 高 | 低 | |
讀耗時 | 快 | 慢 | |
單字節的編程時間 | 快 | 慢 | |
多字節的編程時間 | 慢 | 快 | |
擦除時間 | 慢 | 快 | |
功耗 | 高 | 低,但是需要額外的RAM | |
是否可以執行代碼 | 是 | 不行, 但是一些新的芯片,可以在第一頁之外執行一些小的loader | 即是否允許芯片內執行(XIP, eXecute In Place) [8] |
位反轉(Bit twiddling/bit flip)[18] | 幾乎無限制 | 1-4次,也稱作 “部分頁編程限制” | 也就是數據錯誤,0→1或1→0 |
在芯片出廠時候是否允許壞塊 | 不允許 | 允許 |
Nand Flash按照內部存儲數據單元的電壓的不同層次,也就是單個內存單元中,是存儲1位數據,還是多位數據,可以分為SLC和MLC。
單個存儲單元,只存儲一位數據,表示1或0。
就是上面介紹的,對於數據的表示,單個存儲單元中內部所存儲電荷的電壓,和某個特定的閾值電壓Vth,相比,如果大於此Vth值,就是表示1,反之,小於Vth,就表示0。
對於Nand Flash的數據的寫入1,就是控制External Gate去充電,使得存儲的電荷夠多,超過閾值Vth,就表示1了。而對於寫入0,就是將其放電,電荷減少到小於Vth,就表示0了。
關於為何Nand Flash不能從0變成1,我的理解是,物理上來說,是可以實現每一位的,從0變成1的,但是實際上,對於實際的物理實現,出於效率的考慮,如果對於,每 一個存儲單元都能單獨控制,即,0變成1就是,對每一個存儲單元單獨去充電,所需要的硬件實現就很復雜和昂貴,同時,所進行對塊擦除的操作,也就無法實現 之前所說的的,Flash的速度,即一閃而過的速度了,也就失去了Flash的眾多特性了。
與SLC相對應的,就是單個存儲單元,可以存儲多個位,比如2位,4位等。其實現機制,說起來比較簡單,就是通過控制內部電荷的多少,分成多個閾 值,通過控制里面的電荷多少,而達到我們所需要的存儲成不同的數據。比如,假設輸入電壓是Vin=4V(實際沒有這樣的電壓,此處只是為了舉例方便),那 么,可以設計出2的2次方=4個閾值, 1/4 的Vin=1V,2/4的Vin=2V,3/4的Vin=3V,Vin=4V,分別表示2位數據00,01,10,11,對於寫入數據,就是充電,通過控 制內部的電荷的多少,對應表示不同的數據。
對於讀取,則是通過對應的內部的電流(與Vth成反比),然后通過一系列解碼電路完成讀取,解析出所存儲的數據。這些具體的物理實現,都是有足夠精確的設備和技術,才能實現精確的數據寫入和讀出的。
單個存儲單元可以存儲2位數據的,稱作2的2次方=4 Level Cell,而不是2 Level Cell,關於這點,之前看Nand flash的數據手冊(datasheet)的時候,差點搞暈了。
同理,對於新出的單個存儲單元可以存儲4位數據的,稱作 2的4次方=16 Level Cell。
Nand Flash設計中,有個命令叫做Read ID,讀取ID,意思是讀取芯片的ID,就像大家的身份證一樣,這里讀取的ID中,是讀取好幾個字節,一般最少是4個,新的芯片,支持5個甚至更多,從這 些字節中,可以解析出很多相關的信息,比如此Nand Flash內部是幾個芯片(chip)所組成的,每個chip包含了幾片(Plane),每一片中的頁大小,塊大小,等等。在這些信息中,其中有一個,就 是識別此flash是SLC還是MLC。下面這個就是最常見的Nand Flash的datasheet中所規定的,第3個字節,3rd byte,所表示的信息,其中就有SLC/MLC的識別信息:
表 1.2. Nand Flash第3個ID的含義
Description | I/O7 | I/O6 | I/O5 I/O4 | I/O3 I/O2 | I/O1 I/O0 | |
---|---|---|---|---|---|---|
Internal Chip Number |
1 |
0 0 |
||||
Cell Type |
2 Level Cell |
0 0 |
||||
Number of Simultaneously Programmed Pages |
1 |
0 0 |
||||
Interleave Program Between multiple chips |
Not Support |
0 |
||||
Cache Program |
Not Support |
0 |
簡單說就是,常見的Nand Flash,內部只有一個chip,每個chip只有一個plane。
而有些復雜的,容量更大的Nand Flash,內部有多個chip,每個chip有多個plane。這類的Nand Flash,往往也有更加高級的功能,比如下面要介紹的Multi Plane Program和Interleave Page Program等。
概念上,由大到小來說,就是:
Nand Flash ⇒ Chip ⇒ Plane ⇒ Block ⇒ Page ⇒ oob
用圖表來表示,更加易懂:
比如,型號為K9K8G08U0A這塊Nand Flash(有時候也被稱為此塊chip芯片),其內部有兩個K9F4G08U0A的chip,chip#1和chip#2,每個K9F4G08U0A的 chip包含了2個Plane,每個Plane是2Gbbit,所以K9F4G08U0A的大小是2Gb×2 = 4Gb = 512MB,因此,K9K8G08U0A內部有2個K9F4G08U0A,或者說4個Plane,總大小是×256MB=1GB。
用公式表示如下:
公式 1.1. K9K8G08U0A的物理結構所組成的總容量
K9K8G08U0A(這塊Nand Flash)
= 2 × K9F4G08U0A(K9F4G08U0A是chip,1 K9F4G08U0A = 2 Plane)
= 2 × 2個Plane
= 4 Plane(1 Plane = 2048 Block)
= 4 × 2048個Block(1 Block = 64 Page)
= 4 × 2048 × 64Page(1 Page = 2KB)
= 4 × 2048 × 64Page × 2KB
= 4 × 2048 × 128KB(1 Block = 128KB)
= 4 × 256MB(1 Plane = 2Gb = 256MB)
= 2 × 512MB(1 K9F4G08U0A = 4Gb = 512MB)
= 1GB(1 K9K8G08U0A = 1GB)
而型號是K9WAG08U1A的Nand Flash,內部包含了2個K9K8G08U0A,所以,總容量是K9K8G08U0A的兩倍=1GB×2=2GB,類似地K9NBG08U5A,內部包含了4個K9K8G08U0A,總大小就是4×1GB=4GB。
![]() |
通常只關心Nand的總大小 |
---|---|
上面所說的block,page等Nand Flash的物理上的組織結構,是在chip的基礎上來說的,但是軟件編程的時候,除非你要用到Multi Plane Program和Interleave Page Program等,一般很少區分內部有幾個chip以及每個chip有幾個plane,而最關心的只是Nand Flash的總體容量size有多大,比如是1GB還是2GB等等。 |
下面詳細介紹一下,Nand Flash的一個chip內部的硬件邏輯組織結構。
Nand Flash的內部組織結構,此處還是用圖來解釋,比較容易理解:
上圖是K9K8G08U0A的datasheet中的描述。
簡單解釋就是:
一個Nand Flash(的chip,芯片)由很多個塊(Block)組成,塊的大小一般是128KB,256KB,512KB,此處是128KB。其他的小於128KB的,比如64KB,一般都是下面將要介紹到的small block的Nand Flash。
塊Block,是Nand Flash的擦除操作的基本/最小單位。
每個塊里面又包含了很多頁(page)。每個頁的大小,對於現在常見的Nand Flash多數是2KB,最新的Nand Flash的是4KB、8KB等,這類的頁大小大於2KB的Nand Flash,被稱作big block的Nand Flash,對應的發讀寫命令地址,一共5個周期(cycle),而老的Nand Flash,頁大小是256B,512B,這類的Nand Flash被稱作small block,地址周期只有4個。
頁Page,是讀寫操作的基本單位。
不過,也有例外的是,有些Nand Flash支持subpage(1/2頁或1/4頁)子頁的讀寫操作,不過一般很少見。
每一個頁,對應還有一塊區域,叫做空閑區域(spare area)/冗余區域(redundant area),而Linux系統中,一般叫做OOB(Out Of Band),這個區域,是最初基於Nand Flash的硬件特性:數據在讀寫時候相對容易錯誤,所以為了保證數據的正確性,必須要有對應的檢測和糾錯機制,此機制被叫做EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),所以設計了多余的區域,用於放置數據的校驗值。
Oob的讀寫操作,一般是隨着頁的操作一起完成的,即讀寫頁的時候,對應地就讀寫了oob。
關於oob具體用途,總結起來有:
- 標記是否是壞快
- 存儲ECC數據
- 存儲一些和文件系統相關的數據。如jffs2就會用到這些空間存儲一些特定信息,而yaffs2文件系統,會在oob中,存放很多和自己文件系統相關的信息。
Flash的擦除操作是以block塊為單位的,與此相對應的是其他很多存儲設備,是以bit位為最小讀取/寫入的單位,Flash是一次性地擦除 整個塊:在發送一個擦除命令后,一次性地將一個block,常見的塊的大小是128KB/256KB。。,全部擦除為1,也就是里面的內容全部都是 0xFF了,由於是一下子就擦除了,相對來說,擦除用的時間很短,可以用一閃而過來形容,所以,叫做Flash Memory。所以一般將Flash翻譯為 (快速)閃存。
根據上面提到過的,Flash最小操作單位,相對於普通存儲設備,就顯得有些特殊。
因為一般存儲設備,比如硬盤或內存,讀取和寫入都是以位(bit)為單位,讀取一個bit的值,將某個值寫入對應的地址的位,都是可以按位操作的。
但是Flash由於物理特性,使得內部存儲的數據,只能從1變成0,這點,這點可以從前面的內部實現機制了解到,對於最初始值,都是1,所以是 0xFFFFFFFF,而數據的寫入,即是將對應的變成0,而將數據的擦出掉,就是統一地,以block為單位,全部一起充電,所有位,都變成初始的1, 而不是像普通存儲設備那樣,每一個位去擦除為0。而數據的寫入,就是電荷放電的過程,代表的數據也從1變為了0。
所以,總結一下Flash的特殊性如下:
Nand Flash的位反轉,也叫做位翻轉,對應的英文表達有:Bit Flip=Bit Flipping=Bit-Flip=Bit twiddling。
Nand Flash由於本身硬件的內在特性,會導致(極其)偶爾的出現位反轉的現象。
所謂的位反轉,bit flip,指的是原先Nand Flash中的某個位,變化了,即要么從1變成0了,要么從0變成1了。
Nand Flash的位反轉現象,主要是由以下一些原因/效應所導致:
- 漂移效應(Drifting Effects)
漂移效應指的是,Nand Flash中cell的電壓值,慢慢地變了,變的和原始值不一樣了。
- 編程干擾所產生的錯誤(Program-Disturb Errors)
此現象有時候也叫做,過度編程效應(over-program effect)。
對於某個頁面的編程操作,即寫操作,引起非相關的其他的頁面的某個位跳變了。
- 讀操作干擾產生的錯誤(Read-Disturb Errors)
此效應是,對一個頁進行數據讀取操作,卻使得對應的某個位的數據,產生了永久性的變化,即Nand Flash上的該位的值變了。
位反轉,說白了,就是讀取數據的時候,數據出錯了。
因此,如果你讀取的數據正好是屬於某個重要的文件中的數據,比如系統的配置文件等,那么此時錯了一位,都會導致系統出現異常,問題相對會很嚴重。
而如果此數據屬於音視頻流中的數據,那么此時即使錯了一位,對整個音視頻的播放產生的影響也很小,所以問題也不大。
對應的位反轉的類型,有兩種:
- 一種是nand flash物理上的數據存儲的單元上的數據,是正確的,只是在讀取此數據出來的數據中的某位,發生變化,出現了位反轉,即讀取出來的數據中,某位錯了,本 來是0變成1,或者本來是1變成0了。此處可以成為軟件上位反轉。此數據位的錯誤,當然可以通過一定的校驗算法檢測並糾正。
- 另外一種,就是nand flash中的物理存儲單元中,對應的某個位,物理上發生了變化,原來是1的,變成了0,或原來是0的,變成了1,發生了物理上的位的數據變化。此處可以 成為硬件上的位反轉。此錯誤,由於是物理上發生的,雖然讀取出來的數據的錯誤,可以通過軟件或硬件去檢測並糾正過來,但是物理上真正發生的位的變化,則沒 辦法改變了。不過個人理解,好像也是可以通過擦除Erase整個數據塊Block的方式去擦除此錯誤,不過在之后的Nand Flash的使用過程中,估計此位還是很可能繼續發生同樣的硬件的位反轉的錯誤。
以上兩種類型的位反轉,其實對於從Nand Flash讀取出來的數據來說,解決其中的錯誤的位的方法,都是一樣的,即通過一定的校驗算法,常稱為ECC,去檢測出來,或檢測並糾正錯誤。
如果只是單獨檢測錯誤,那么如果發現數據有誤,那么再重新讀取一次即可。
實際中更多的做法是,ECC校驗發現有錯誤,會有對應的算法去找出哪位錯誤並且糾正過來。
其中對錯誤的檢測和糾正,具體的實現方式,有軟件算法,也有硬件實現,即硬件Nand Flash的控制器controller本身包含對應的硬件模塊以實現數據的校驗和糾錯的。
上圖是常見的Nand Flash所擁有的引腳(Pin)所對應的功能,簡單翻譯如下:
表 1.4. Nand Flash引腳功能的中文說明
引腳名稱 | 引腳功能 |
---|---|
I/O0 ~ I/O7 | 用於輸入地址/數據/命令,輸出數據 |
CLE | Command Latch Enable,命令鎖存使能,在輸入命令之前,要先在模式寄存器中,設置CLE使能 |
ALE | Address Latch Enable,地址鎖存使能,在輸入地址之前,要先在模式寄存器中,設置ALE使能 |
CE# | Chip Enable,芯片使能,在操作Nand Flash之前,要先選中此芯片,才能操作 |
RE# | Read Enable,讀使能,在讀取數據之前,要先使CE#有效。 |
WE# | Write Enable,寫使能, 在寫取數據之前,要先使WE#有效 |
WP# | Write Protect,寫保護 |
R/B# | Ready/Busy Output,就緒/忙,主要用於在發送完編程/擦除命令后,檢測這些操作是否完成,忙,表示編程/擦除操作仍在進行中,就緒表示操作完成 |
Vcc | Power,電源 |
Vss | Ground,接地 |
N.C | Non-Connection,未定義,未連接 |
![]() |
數據手冊中的#表示低電平 |
---|---|
在數據手冊中,你常會看到,對於一個引腳定義,有些字母上面帶一橫杠的,那 是說明此引腳/信號是低電平有效,比如你上面看到的RE頭上有個橫線,就是說明,此RE是低電平有效,此外,為了書寫方便,在字母后面加“#”,也是表示 低電平有效,比如我上面寫的CE#;如果字母頭上啥都沒有,就是默認的高電平有效,比如上面的CLE,就是高電平有效。 |
硬件上,有了電源的Vcc和接地的Vss等引腳,很好理解,但是為何還要有ALE和CLE這樣的引腳,為何設計這么多的命令,把整個系統搞這么復雜,關於這點,最后終於想明白了:
設計命令鎖存使能(Command Latch Enable, CLE) 和 地址鎖存使能(Address Latch Enable,ALE),那是因為,Nand Flash就8個I/O,而且是復用的,也就是,可以傳數據,也可以傳地址,也可以傳命令,為了區分你當前傳入的到底是啥,所以,先要用發一個CLE(或 ALE)命令,告訴Nand Flash的控制器一聲,我下面要傳的是命令(或地址),這樣,里面才能根據傳入的內容,進行對應的動作。否則,Nand Flash內部,怎么知道你傳入的是數據,還是地址,還是命令,也就無法實現正確的操作了。
在Nand Flash的硬件設計中,你會發現很多個引腳。關於硬件上為何設計這樣的引腳,而不是直接像其他存儲設備,比如普通的RAM,直接是一對數據線引出來,多么方便和好理解啊。
關於這樣設計的好處:
相對於並口(Parellel)的Nor Flash的48或52個引腳來說,的確是大大減小了引腳數目,這樣封裝后的芯片體積,就小很多。現在芯片在向體積更小,功能更強,功耗更低發展,減小芯 片體積,就是很大的優勢。同時,減少芯片接口,也意味着使用此芯片的相關的外圍電路會更簡化,避免了繁瑣的硬件連線。
因為沒有像其他設備一樣用物理大小對應的完全數目的addr引腳,在芯片內部換了芯片的大小等的改動,對於用全部的地址addr的引腳,那么就會引 起這些引腳數目的增加,比如容量擴大一倍,地址空間/尋址空間擴大一倍,所以,地址線數目/addr引腳數目,就要多加一個,而對於統一用8個I/O的引 腳的Nand Flash,由於對外提供的都是統一的8個引腳,內部的芯片大小的變化或者其他的變化,對於外部使用者(比如編寫Nand Flash驅動的人)來說,不需要關心,只是保證新的芯片,還是遵循同樣的接口,同樣的時序,同樣的命令,就可以了。這樣就提高了系統的擴展性。
說白了,對於舊的Nand Flash所實現的驅動,這些軟件工作,在換新的硬件的Nand Flash的情況下,仍然可以工作,或者是通過極少的修改,就同樣可以工作,使得軟硬件兼容性大大提高。
- 頁擦除時間是200us,有些慢的有800us
- 塊擦除時間是1.5ms
- 頁數據讀取到數據寄存器的時間一般是20us
- 串行訪問(Serial access)讀取一個數據的時間是25ns,而一些舊的Nand Flash是30ns,甚至是50ns
- 輸入輸出端口是地址和數據以及命令一起multiplex復用的
- Nand Flash的編程/擦除的壽命:即,最多允許的擦除的次數
以前老的Nand Flash,編程/擦除時間比較短,比如K9G8G08U0M,才5K次,而后來的多數也只有10K=1萬次,而現在很多新的Nand Flash,技術提高了,比如,Micron的MT29F1GxxABB,Numonyx的 NAND04G-B2D/NAND08G-BxC,都可以達到100K,也就是10萬次的編程/擦除,達到和接近於之前常見的Nor Flash,幾乎是同樣的使用壽命了。
- 封裝形式
48引腳的TSOP1封裝 或 52引腳的ULGA封裝
關於Nand Flash的控制器Controller和Nand Flash芯片chip之間的關系,覺得有必要解釋一下:
首先,我們要知道的是,我們寫驅動,是寫Nand Flash 控制器的驅動,而不是Nand Flash 芯片的驅動,因為獨立的Nand Flash芯片,一般來說,是很少直接拿來用的,多數都是硬件上有對應的硬件的Nand Flash的控制器,去操作和控制Nand Flash,包括提供時鍾信號,提供硬件ECC校驗等等功能,我們所寫的驅動軟件,是去操作Nand Flash的控制器
然后由控制器去操作Nand Flash芯片,實現我們所要的功能。
由於Nand Flash相對其他常見設備來說,比較特殊,所以,特殊的設備,就有特殊的設計,就對應某些特殊的硬件特性,就有必要解釋解釋:
頁寄存器(Page Register):
由於Nand Flash讀取和編程操作來說,一般最小單位是頁,所以Nand Flash在硬件設計時候,就考慮到這一特性,對於每一片(Plane),都有一個對應的區域專門用於存放,將要寫入到物理存儲單元中去的或者剛從存儲單 元中讀取出來的,一頁的數據,這個數據緩存區,本質上就是一個緩存buffer,但是只是此處datasheet里面把其叫做頁寄存器page register而已,實際將其理解為頁緩存,更貼切原意。
而正是因為有些人不了解此內部結構,才容易產生之前遇到的某人的誤解,以為內存里面的數據,通過Nand Flash的FIFO,寫入到Nand Flash里面去,就以為立刻實現了實際數據寫入到物理存儲單元中了,而實際上只是寫到了這個頁緩存中,只有當你再發送了對應的編程第二階段的確認命令, 即0x10,之后,實際的編程動作才開始,才開始把頁緩存中的數據,一點點寫到物理存儲單元中去。
所以,簡單總結一下就是,對於數據的流向,實際是經過了如下步驟:
Nand Flash中,一個塊中含有1個或多個位是壞的,就稱為其為壞塊Bad Block。
壞塊的穩定性是無法保證的,也就是說,不能保證你寫入的數據是對的,或者寫入對了,讀出來也不一定對的。與此對應的正常的塊,肯定是寫入讀出都是正常的。
壞塊有兩種:
- 出廠時就有存在的壞塊
一種是出廠的時候,也就是,你買到的新的,還沒用過的Nand Flash,就可以包含了壞塊。此類出廠時就有的壞塊,被稱作factory (masked) bad block或initial bad/invalid block,在出廠之前,就會做對應的標記,標為壞塊。
- 使用過程中產生的壞塊
第二類叫做在使用過程中產生的,由於使用過程時間長了,在擦塊除的時候,出錯了,說明此塊壞了,也要在程序運行過程中,發現,並且標記成壞塊的。具體標記的位置,和上面一樣。這類塊叫做worn-out bad block。即用壞了的塊。
具體標記的地方是,對於現在常見的頁大小為2K的Nand Flash,是塊中第一個頁的oob起始位置(關於什么是頁和oob,下面會有詳細解釋)的第1個字節(舊的小頁面,pagesize是512B甚至 256B的Nand Flash,壞塊標記是第6個字節),如果不是0xFF,就說明是壞塊。相對應的是,所有正常的塊,好的塊,里面所有數據都是0xFF的。
不過,對於現在新出的有些Nand Flash,很多標記方式,有些變化,有的變成該壞塊的第一個頁或者第二個頁,也有的是,倒數最后一個或倒數第二個頁,用於標記壞塊的。
具體的信息,請參考對應的Nand Flash的數據手冊,其中會有說明。
對於壞塊的標記,本質上,也只是對應的flash上的某些字節的數據是非0xFF而已,所以,只要是數據,就是可以讀取和寫入的。也就意味着,可以寫入其他值,也就把這個壞塊標記信息破壞了。對於出廠時的壞塊,一般是不建議將標記好的信息擦除掉的。
uboot中有個命令是
nand scrub
就可以將塊中所有的內容都擦除了,包括壞塊標記,不論是出廠時的,還是后來使用過程中出現而新標記的。一般來說,不建議用這個。
不過,在實際的驅動編程開發過程中,為了方便起見,我倒是經常用,其實也沒啥大礙,呵呵。不過呢,其實最好的做法是,用
nand erase
只擦除好的塊,對於已經標記壞塊的塊,不要輕易擦除掉,否則就很難區分哪些是出廠時就壞的,哪些是后來使用過程中用壞的了。
對於壞塊的管理,在Linux系統中,叫做壞塊管理(BBM,Bad Block Management),對應的會有一個表去記錄好塊,壞塊的信息,以及壞塊是出廠就有的,還是后來使用產生的,這個表叫做壞塊表(BBT,Bad Block Table)。在Linux 內核MTD架構下的Nand Flash驅動,和Uboot中Nand Flash驅動中,在加載完驅動之后,如果你沒有加入參數主動要求跳過壞塊掃描的話,那么都會去主動掃描壞塊,建立必要的BBT的,以備后面壞塊管理所使 用。
而關於好塊和壞塊,Nand Flash在出廠的時候,會做出保證:
- 關於好的,可以使用的塊的數目達到一定的數目,比如三星的K9G8G08U0M,整個flash一共有 4096個塊,出廠的時候,保證好的塊至少大於3996個,也就是意思是,你新買到這個型號的Nand Flash,最壞的可能, 有3096-3996=100個壞塊。不過,事實上,現在出廠時的壞塊,比較少,絕大多數,都是使用時間長了,在使用過程中出現的。
- 保證第一個塊是好的,並且一般相對來說比較耐用。做此保證的主要原因是,很多Nand Flash壞塊管理方法中,就是將第一個塊,用來存儲上面提到的BBT,否則,都是出錯幾率一樣的塊,那么也就不太好管理了,連放BBT的地方,都不好找了,^_^。
一般來說,不同型號的Nand Flash的數據手冊中,也會提到,自己的這個Nand Flash,最多允許多少個壞塊。就比如上面提到的,三星的K9G8G08U0M,最多有100個壞塊。
在一個塊內,對每一個頁進行編程的話,必須是順序的,而不能是隨機的。比如,一個塊中有128個頁,那么你只能先對page0編程,再對page1編程,。。。。,而不能隨機的,比如先對page3,再page1,page2,page0,page4,。。。
關於此處對於只能順序給頁編程的說法,只是翻譯自datasheet,但是實際情況卻發現是,程序中沒有按照此邏輯處理,可以任意對某Block內 的Page去做Program的動作,而不必是順序的。但是datasheet為何如此解釋,原因未知,有待知情者給解釋一下。
要實現對Nand Flash的操作,比如讀取一頁的數據,寫入一頁的數據等,都要發送對應的命令,而且要符合硬件的規定,如圖:
從上圖可以看到,如果要實現讀一個頁的數據,就要發送Read的命令,而且是分兩個周期(Cycle),即分兩次發送對應的命令,第一次是0x00h,第二次是0x30h,而兩次命令中間,需要發送對應的你所要讀取的頁的地址,關於此部分詳細內容,留待后表。
對應地,其他常見的一些操作,比如寫一個頁的數據(Page Program),就是先發送0x80h,然后發生要寫入的地址,再發送0x10h。
![]() |
注意 |
---|---|
對於不同廠家的不同型號的Nand Flash 的基本操作,即讀頁數據Read Page,寫頁數據(對頁進行編程)Page Program,擦除整個塊的數據Erase Block等操作,所用的命令都是一樣的,但是針對一些Nand Flash的高級的一些特性,比如交錯頁編程(Interleave Page Program),多片同時編程(Simultaneously Program Multi Plane)等,所用的命令,未必一樣,不過對於同一廠家的Nand Flash的芯片,那一般來說,都是統一的。 |
關於一些常見的操作,比如讀一個頁的Read操作和寫一個頁的Page Program,下面開始更深入的介紹。
Nand flash的寫操作叫做編程Program,編程,一般情況下,是以頁為單位的。
有的Nand Flash,比如K9K8G08U0A,支持部分頁編程(Partial Page Program),但是有一些限制:在同一個頁內的,連續的部分頁的編程,不能超過4次。
一般情況下,都是以頁為單位進行編程操作的,很少使用到部分頁編程。
關於這個部分頁編程,本來是一個頁的寫操作,卻用兩個命令或更多的命令去實現,看起來是操作多余,效率不高,但是實際上,有其特殊考慮:
至少對於塊擦除來說,開始的命令0x60是擦除設置命令(erase setup comman),然后傳入要擦除的塊地址,然后再傳入擦除確認命令(erase confirm command)0xD0,以開始擦除的操作。
這種完成單個操作要分兩步發送命令的設計,即先開始設置,再最后確認的命令方式,是為了避免由於外部由於無意的/未預料而產生的噪音,比如,由於某 種噪音,而產生了0x60命令,此時,即使被Nand Flash誤認為是擦除操作,但是沒有之后的確認操作0xD0,Nand Flash就不會去擦除數據,這樣使得數據更安全,不會由於噪音而誤操作。
下面以最簡單的read操作為例,解釋如何理解時序圖,以及將時序圖中的要求,轉化為代碼。
解釋時序圖之前,讓我們先要搞清楚,我們要做的事情:
從Nand Flash的某個頁Page里面,讀取我們要的數據。
要實現此功能,會涉及到幾部分的知識,即使我們不太懂Nand Flash的細節,但是通過前面的基本知識的介紹,那么以我們的常識,至少很容易想到的就是,需要用到哪些命令,怎么發這些命令,怎么計算所需要的地址,怎么讀取我們要的數據等等。
下面就一步步的解釋,需要做什么,以及如何去做:
首先,是要了解,對於讀取數據,要用什么命令:
根據前面關於Nand Flash的命令集合介紹,我們知道,要讀取數據,要用到Read命令,該命令需要2個周期,第一個周期發0x00,第二個周期發0x30。
知道了用何命令后,再去了解如何發送這些命令。
![]() |
提示 |
---|---|
在開始解釋前,關於”使能”這個詞要羅嗦一下,以防有些讀者和我以前一樣,在聽這類詞語的時候,屬於初次接觸,或者接觸不多的,就很容易被搞得一頭霧水的(雖然該詞匯對於某些專業人士說是屬於最基本的詞匯了,囧)。 使能(Enable),是指使其(某個信號)有效,使其生效的意思,“使其”“能夠”怎么怎么樣。。。。比如,上面圖中的CLE線號,是高電平有效,如果此時將其設為高電平,我們就叫做,將CLE使能,也就是使其生效的意思。 |
![]() |
注意 |
---|---|
此圖來自三星的型號K9K8G08U0A的Nand Flash的數據手冊(datasheet)。 |
我們來一起看看,我在圖6中的特意標注的①邊上的黃色豎線。
黃色豎線所處的時刻,是在發送讀操作的第一個周期的命令0x00之前的那一刻。
讓我們看看,在那一刻,其所穿過好幾行都對應什么值,以及進一步理解,為何要那個值。
- 黃色豎線穿過的第一行,是CLE。還記得前面介紹命令所存使能(CLE)那個引腳吧?CLE,將CLE置1, 就說明你將要通過I/O復用端口發送進入Nand Flash的,是命令,而不是地址或者其他類型的數據。只有這樣將CLE置1,使其有效,才能去通知了內部硬件邏輯,你接下來將收到的是命令,內部硬件邏 輯,才會將受到的命令,放到命令寄存器中,才能實現后面正確的操作,否則,不去將CLE置1使其有效,硬件會無所適從,不知道你傳入的到底是數據還是命令 了。
- 而第二行,是CE#,那一刻的值是0。這個道理很簡單,你既然要向Nand Flash發命令,那么先要選中它,所以,要保證CE#為低電平,使其有效,也就是片選有效。
- 第三行是WE#,意思是寫使能。因為接下來是往Nand Flash里面寫命令,所以,要使得WE#有效,所以設為低電平。
- 第四行,是ALE是低電平,而ALE是高電平有效,此時意思就是使其無效。而對應地,前面介紹的,使CLE有 效,因為將要數據的是命令(此時是發送圖示所示的讀命令第二周期的0x30),而不是地址。如果在其他某些場合,比如接下來的要輸入地址的時候,就要使其 有效,而使CLE無效了。
- 第五行,RE#,此時是高電平,無效。可以看到,知道后面低6階段,才變成低電平,才有效,因為那時候,要發生讀取命令,去讀取數據。
- 第六行,就是我們重點要介紹的,復用的輸入輸出I/O端口了,此刻,還沒有輸入數據,接下來,在不同的階段,會輸入或輸出不同的數據/地址。
- 第七行,R/B#,高電平,表示R(Ready)/就緒,因為到了后面的第5階段,硬件內部,在第四階段,接受了外界的讀取命令后,把該頁的數據一點點送到頁寄存器中,這段時間,屬於系統在忙着干活,屬於忙的階段,所以,R/B#才變成低,表示Busy忙的狀態的。
介紹了時刻①的各個信號的值,以及為何是這個值之后,相信,后面的各個時刻,對應的不同信號的各個值,大家就會自己慢慢分析了,也就容易理解具體的操作順序和原理了。
在介紹具體讀取數據的詳細流程之前,還要做一件事,那就是,先要搞懂我們要訪問的地址,以及這些地址,如何分解后,一點點傳入進去,使得硬件能識別才行。
此處還是以K9K8G08U0A為例,此Nand Flash,一共有8192個塊,每個塊內有64頁,每個頁是2K+64 Bytes。
假設,我們要訪問其中的第7000個塊中的第25頁中的1208字節處的地址,此時,我們就要先把具體的地址算出來:
物理地址
=塊大小×塊號 + 頁大小×頁號 + 頁內地址
=7000×128K + 64×2K + 1208
=0x36B204B8
接下來,我們就看看,怎么才能把這個實際的物理地址,轉化為Nand Flash所要求的格式。
在解釋地址組成之前,先要來看看其datasheet中關於地址周期的介紹:
結合圖 1.7 “Nand Flash數據讀取操作的時序圖”中的2,3階段,我們可以看出,此Nand Flash地址周期共有5個,2個列(Column)周期,3個行(Row)周期。
- 對應地,列地址A0~A10,就是頁內地址,地址范圍是從0到2047。
細心的讀者可能注意到了,為何此處多出來個A11呢?
這樣從A0到A11,一共就是12位,可以表示的范圍就是0~212,即0~4096了。
實際上,由於我們訪問頁內地址,可能會訪問到oob的位置,即2048-2111這64個字節的范圍內,所以,此處實際上只用到了2048~2111,用於表示頁內的oob區域,其大小是64字節。
- 對應地,A12~A30,稱作頁號,頁的號碼,可以定位到具體是哪一個頁。
A18~A30,表示對應的塊號,即屬於哪個塊。
簡單解釋完了地址組成,那么就很容易分析上面例子中的地址了。
注意,下面這樣的方法,是錯誤的:
0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000,分別分配到5個地址周期就是:
1st周期 | A7 ~ A0 | 1011 1000 = 0xB8 |
2nd周期 | A11~ A8 | 0100 = 0x04 |
3rd周期 | A19~A12 | 0010 0000 = 0x20 |
4th周期 | A27~A20 | 0110 1011 = 0x6B |
5th周期 | A30~A28 | 11 = 0x03 |
![]() |
注意 |
---|---|
與圖 1.7 “Nand Flash數據讀取操作的時序圖”中對應的,*L,意思是地電平,由於未用到那些位,datasheet中強制要求設為0,所以,才有上面的2nd周期中的高4位是0000.其他的A30之后的位也是類似原理,都是0。 |
而至於上述計算方法為何是錯誤的,那是因為上面計算過程中,把第11位的值,本來是屬於頁號的位A11,給算成頁內地址里面的值了。
應該是這樣計算,才是對的:
0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000
1st周期 | A7 ~ A0 | 1011 1000 = 0xB8 |
2nd周期 | A10~ A8 | 100 = 0x04 |
3rd周期 | A19~A12 | 010 0000 0 = 0x40 |
4th周期 | A27~A20 | 110 1011 0 = 0xD6 |
5th周期 | A30~A28 | 11 0 = 0x06 |
那有人會問了,上面表11中,不是明明寫的A0到A30,其中包括A11,不是正好對應着此處地址中的bit0到bit30嗎?
其實,我開始也是犯了同樣的錯誤,誤以為我們要傳入的地址的每一位,就是對應着表11中的A0到A30呢,實際上,表11中的A11,是比較特殊 的,只有當我們訪問頁內地址處於oob的位置,即屬於2048~2111的時候,A11才會其效果,才會用A0-A11用來表示對應的某個屬於 2048~2111的某個值,屬於oob的某個位置。
而我們此處的頁內地址為2108,還沒有超過2047呢,所以A11肯定是0。
這么解釋,顯得很繞,很難看懂。
換種方式來解釋,就容易聽懂了:
說白了,我們就是要訪問第7000個塊中的第25頁中的1208字節處,對應着
頁內地址
=1208
=0x4B8
頁號
=塊數×頁數/塊 + 塊內的頁號
= 7000×(128K/2K) + 25
= 7000×64 + 25
= 448025
=0x6D619
也就是,我們要訪問0x6D619頁內的0x4B8地址,這樣很好理解吧,^_^。
然后對應的:
頁內地址=0x4B8
分成兩個對應的列地址,就變成
0x4B8 :列地址1=0xB8,列地址2=0x04
頁號=0x6D619,分成三個行號就是:
0x6D619:行號1=0x19,行號2=0xD6,行號3=0x06
再回頭看看上面的計算方法,
最開始計算出來的:
列地址1=0xB8
列地址2=0x04
行號1=0x20
行號2=0x6B
行號3=0x03
是錯誤的。
而第二次計算正確的:
列地址1=0xB8
列地址2=0x04
行號1=0x19
行號2=0xD6
行號3=0x06
才是對的,也和我們此處自己手動計算,是一致的。
第一次之所以計算錯,就是錯誤的把行地址的最低一位A11,放到列地址中的最高位了。
至此,才算把如何手動計算行地址和列地址,解釋明白和正確了。
對應的,Linux的源碼\drivers\mtd\nand\nand_base.c中,也是這樣處理的:
static void nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) { ...... /* Serially input address */ if (column != -1) { ...... chip->cmd_ctrl(mtd, column, ctrl); /* 發送Col Addr 1 */ ctrl &= ~NAND_CTRL_CHANGE; chip->cmd_ctrl(mtd, column >> 8, ctrl); /* 發送Col Addr 2 */ } if (page_addr != -1) { chip->cmd_ctrl(mtd, page_addr
, ctrl); /* 發送Row Addr 1 */ chip->cmd_ctrl(mtd, page_addr >> 8, /* 發送Row Addr 2 */ NAND_NCE | NAND_ALE); /* One more address cycle for devices > 128MiB */ if (chip->chipsize > (128 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16, /* 發送Row Addr 3 */ NAND_NCE | NAND_ALE); } }
因此,我們要訪問第7000個塊中的第25頁中的1208字節處的話,所要傳入的地址就是分5個周期:
分別傳入兩個列地址的:
列地址1=0xB8
列地址2=0x04
然后再傳3個行地址的:
行號1=0x19
行號2=0xD6
行號3=0x06
這樣硬件才能識別。
而接下來的內容,也就是介紹硬件是如何處理這些輸入的。
准備工作終於完了,下面就可以開始解釋說明,對於讀操作的,上面圖中標出來的,1-6個階段,具體是什么含義。
操作准備階段:此處是讀(Read)操作,所以,先發一個圖5中讀命令的第一個階段的0x00,表示,讓硬件先准備一下,接下來的操作是讀。
發送兩個周期的列地址。也就是頁內地址,表示,我要從一個頁的什么位置開始讀取數據。
接下來再傳入三個行地址。對應的也就是頁號。
然后再發一個讀操作的第二個周期的命令0x30。接下來,就是硬件內部自己的事情了。
Nand Flash內部硬件邏輯,負責去按照你的要求,根據傳入的地址,找到哪個塊中的哪個頁,然后把整個這一頁的數據,都一點點搬運到頁緩存中去。而在此期間, 你所能做的事,也就只需要去讀取狀態寄存器,看看對應的位的值,也就是R/B#那一位,是1還是0,0的話,就表示,系統是busy,仍在”忙“(着讀取 數據),如果是1,就說系統活干完了,忙清了,已經把整個頁的數據都搬運到頁緩存里去了,你可以接下來讀取你要的數據了。
對於這里。估計有人會問了,這一個頁一共2048+64字節,如果我傳入的頁內地址,就像上面給的1208一類的值,只是想讀取1028到2011 這部分數據,而不是頁開始的0地址整個頁的數據,那么內部硬件卻讀取整個頁的數據出來,豈不是很浪費嗎?答案是,的確很浪費,效率看起來不高,但是實際就 是這么做的,而且本身讀取整個頁的數據,相對時間並不長,而且讀出來之后,內部數據指針會定位到你剛才所制定的1208的那個位置。
接下來,就是你“竊取“系統忙了半天之后的勞動成果的時候了,呵呵。通過先去Nand Flash的控制器中的數據寄存器中寫入你要讀取多少個字節(byte)/字(word),然后就可以去Nand Flash的控制器的FIFO中,一點點讀取你要的數據了。
至此,整個Nand Flash的讀操作就完成了。
對於其他操作,可以根據我上面的分析,一點點自己去看datasheet,根據里面的時序圖去分析具體的操作過程,然后對照代碼,會更加清楚具體是如何實現的。
Unique ID,翻譯為中文就是,獨一無二的ID,唯一性標識。
很明顯,這個Unique ID是為了用來識別某些東西的,每一個東西都擁有一個獨一無二的標識信息。
在Nand Flash里面的Unique ID,主要是某個ID信息,保證每個Nand Flash都是獨一無二的。主要用於其它的使用Nand Flash的用戶,根據此unique id去做加密等應用,實現某些安全方面的應用。
簡而言之,就是用Nand Flash的Unique ID來實現安全相關的應用,比如加密,版權保護等等。
此處,繼續解釋之前,還要再次贅述一下:
目前Nand Flash的廠家有samsung,Toshiba,Intel, Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等。
由於前面所說的Nand Flash的規范之爭,即Toshiba & Samsung和Intel + 其它廠商(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)之爭,導致對於Unique ID這么個小的功能點(feature),不同的方面,弄出了不同的實現(做法)。
下面就來解釋一下各個廠家關於Unique ID的實現方法,以及如何讀取對應的Unique ID:
網上找到一個datasheet:
Toshiba TH58NS512DC
http://datasheet.elcodis.com/pdf/11/23/112371/th58ns512dc-to51y.pdf
中有提到:
P1:“The TH58NS512DC is a SmartMediaTM with ID and each device has 128 bit unique ID number embedded in the device. This unique ID number is applicable to image files, music files, electronic books, and so on where copyright protection is required.”即每個Toshiba的TH58NS512DC中,都有一個128 bit=16 byte的Unique ID,可用於圖片,音樂,電子書等應用中的版權保護。
P24:
網上找到的某款三星的Nand的datasheet:
Samsung K9F5608U0B
http://hitmen.c02.at/files/docs/psp/ds_k9f5608u0b_rev13.pdf
“
6. Unique ID for Copyright Protection is available
- The device includes one block sized OTP (One Time Programmable), which can be used to increase system security or to provide identification capabilities. Detailed information can be obtained by contact with Samsung
”即,Samsung的Nand的Unique ID,也和Toshiba的用途類似,也主要是用於版權保護,但是其實現卻不同。
Samsung的Unique ID的實現,是專門在Nand 里面配備了一個OTP的Block,而此Nand芯片的Block大小是16KB。
而關於如何操作此OTP,即如何寫入數據和讀取數據,此處未說明。
欲知詳情,請聯系三星。
不過個人理解,應該和普通的block的操作類似,即普通的block,包含很多page,每個page的操作,有對應的page read,用對應的page read命令來讀取此特殊的OTP的block里面的數據。
而此OTP的block里面的數據是什么,完全取決於自己最開始往里面寫入了什么數據。說白了就是,你根據自己需求,在你的產品出廠的時候,寫入對 應的數據,比如該款產品的SN序列號等數據,然后自己在用page read讀取出相應數據后,自己解析,得到自己要的信息,用於自己的用途,比如版權保護等。
主要指的是Intel英特爾,Hynix海力士,Micron美光,Numonyx恆億,Spansion飛索等公司。
對應的Nand 的Unique ID的相關定義,ONFI的規范中都有,現簡要摘錄如下:
ONFI 2.2
http://onfi.org/wp-content/uploads/2009/02/ONFI%202_2%20Gold.pdf
ONFI規范中,在“5.7.1. Parameter Page Data Structure Definition”中,如圖:
定義了一個page的數據,用於存儲對應的Nand的各種參數,其中,有一個第8個字節的 bit5==1的時候,表示支持“Read Unique ID”的命令,即說明此Nand芯片支持此命令,如果byte8的bit5==0,那么說明不支持,也就沒法去讀Unique ID了。
如果經過上述判斷,此符合ONFI的Nand Flash支持Read Unique ID命令,次此時就可以通過該命令來讀取對應的Nand Flash的Unique ID了。
此Read Unique ID的詳細解釋為:
“ 5.8. Read Unique ID DefinitionThe Read Unique ID function is used to retrieve the 16 byte unique ID (UID) for the device. The unique ID when combined with the device manufacturer shall be unique.
The UID data may be stored within the Flash array. To allow the host to determine if the UID is without bit errors, the UID is returned with its complement, as shown in Table 47. If the XOR of the UID and its bit-wise complement is all ones, then the UID is valid.
”即用Read Unique ID命令來讀取128bit=16字節的Unique ID,但是呢,為了用於防止寫入的Unique ID有誤,因此在16字節后面又添了個對應的補碼,即每位取反的結果,這樣前16字節的Unique ID和后16字節的Unique ID的補碼,構成了32字節,算作一組,如下圖所示:
“
To accommodate robust retrieval of the UID in the case of bit errors, sixteen copies of the UID and the corresponding complement shall be stored by the target. For example, reading bytes 32-63 returns to the host another copy of the UID and its complement. Read Status Enhanced shall not be used during execution of the Read Unique ID command
Figure 57 defines the Read Unique ID behavior. The host may use any timing mode supported by the target in order to retrieve the UID data.
”而為了進一步防止出錯,將上面32字節算一組,重復了16次,將這16個32字節 的數據,存在Nand Flash里面,然后用Read Unique ID命令去讀取出來,取得其中某個32字節即可,然后判斷前16字節和后16字節取反,如果結果所有位都是1,那么結果即為16個0xFF,那么說明 Unique ID是正確的。否則說明有誤。
Read Unique ID的命令的詳細格式如下圖所示:
即先發送0xED命令,再發送0x00地址,然后等待Nand Flash的busy狀態結束,就可以讀取出來的那16組的32個字節了,然后用上面辦法去判斷,找到前16字節和后16字節異或得到結果全部16字節都 為0xFF,即說明得到了正確的Unique ID。至此,符合ONFI規范的Unique ID,就讀取出來了。
很多Nand flash支持一個叫做CE don’t-care的技術,字面意思就是,不關心是否片選。
對此也許有人會問了,如果不片選,那還能對其操作嗎?答案就是,這個技術,主要用在當時是不需要選中芯片,但是芯片內部卻仍可以繼續操作的這些情 況:在某些應用,比如錄音,音頻播放等應用中,外部使用的微秒(us)級的時鍾周期,此處假設是比較少的2us,在進行讀取一頁或者對頁編程時,是對 Nand Flash操作,這樣的串行(Serial Access)訪問的周期都是20/30/50ns,都是納秒(ns)級的,此處假設是50ns,當你已經發了對應的讀或寫的命令之后,接下來只是需要 Nand Flash內部去自己操作,將數據讀取除了或寫入進去到內部的數據寄存器中而已,此處,如果可以把片選取消,CE#是低電平有效,取消片選就是拉高電平, 這樣會在下一個外部命令發送過來之前,即微秒量級的時間里面,即2us-50ns≈2us,這段時間的取消片選,可以降低很少的系統功耗,但是多次的操 作,就可以在很大程度上降低整體的功耗了。
總的來說就是:由於某些外部應用所需要的訪問Nand Flash的頻率比較低,而Nand Flash內部操作速度比較快,所以在針對Nand Flash的讀或寫操作的大部分時間里面,都是在等待外部命令的輸入,同時卻選中芯片,產生了多余的功耗,此“不關心片選”技術,就是在Nand Flash的內部的相對快速的操作(讀或寫)完成之后,就取消片選,以節省系統功耗。待下次外部命令/數據/地址輸入來的時候,再選中芯片,即可正常繼續 操作了。這樣,整體上,就可以大大降低系統功耗了。
![]() |
提示 |
---|---|
|
Copy-Back功能,簡單的說就是,將一個頁的數據,拷貝到另一個頁。
如果沒有Copy-Back功能,那么正常的做法就是,先要將那個頁的數據拷貝出來放到內存的數據buffer中,讀出來之后,再用寫命令將這頁的數據,寫到新的頁里面。
而Copy-Back功能的好處在於,不需要用到外部的存儲空間,不需要讀出來放到外部的buffer里面,而是可以直接讀取數據到內部的頁寄存器(page register)然后寫到新的頁里面去。
而且,為了保證數據的正確,要硬件支持EDC(Error Detection Code)的,否則,在數據的拷貝過程中,可能會出現錯誤,並且拷貝次數多了,可能會累積更多錯誤。
而對於錯誤檢測來說,硬件一般支持的是512字節數據,對應有16字節用來存放校驗產生的ECC數值,而這512字節一般叫做一個扇區。對於 2K+64字節大小的頁來說,按照512字節分,分別叫做A,B,C,D區,而后面的64字節的oob區域,按照16字節一個區,分別叫做E,F,G,H 區,對應存放A,B,C,D數據區的ECC的值。
Copy-Back編程的主要作用在於,去掉了數據串行讀取出來,再串行寫入進去的時間,所以,而這部分操作,是比較耗時的,所以此技術可以提高編程效率,提高系統整體性能。
對於有些新出的Nand Flash,支持同時對多個片進行編程,比如上面提到的三星的K9K8G08U0A,內部包含4片(Plane),分別叫做 Plane0,Plane1,Plane2,Plane3。.由於硬件上,對於每一個Plane,都有對應的大小是2048+64=2112字節的頁寄存 器(Page Register),使得同時支持多個Plane編程成為可能。 K9K8G08U0A支持同時對2個Plane進行編程。
不過要注意的是,只能對Plane0和Plane1或者Plane2和Plane3,同時編程,而不支持Plane0和Plane2同時編程。
多片同時編程,是針對一個chip里面的多個Plane來說的,
而此處的交錯頁編程,是指對多個chip而言的。
可以先對一個chip,假設叫chip1,里面的一頁進行編程,然后此時,chip1內部就開始將數據一點點寫到頁里面,就出於忙的狀態了,而此時 可以利用這個時間,對出於就緒狀態的chip2,也進行頁編程,發送對應的命令后,chip2內部也就開始慢慢的寫數據到存儲單元里面去了,也出於忙的狀 態了。此時,再去檢查chip1,如果編程完成了,就可以開始下一頁的編程了,然后發完命令后,就讓其內部慢慢的編程吧,再去檢查chip2,如果也是編 程完了,也就可以進行接下來的其他頁的編程了。如此,交互操作chip1和chip2,就可以有效地利用時間,使得整體編程效率提高近2倍,大大提高 Nand Flash的編程/擦寫速度了。
在介紹此特性之前,先要說說,與Random Data Output In a Page相對應的是,普通的,正常的,sequential data output in a page。
正常情況下,我們讀取數據,都是先發讀命令,然后等待數據從存儲單元到內部的頁數據寄存器中后,我們通過不斷地將RE#(Read Enale,低電平有效)置低,然后從我們開始傳入的列的起始地址,一點點讀出我們要的數據,直到頁的末尾,當然有可能還沒到頁地址的末尾,就不再讀了。 所謂的順序(sequential)讀取也就是,根據你之前發送的列地址的起始地址開始,每讀一個字節的數據出來,內部的數據指針就加1,移到下個字節的 地址,然后你再讀下一個字節數據,就可以讀出來你要的數據了,直到讀取全部的數據出來為止。
而此處的隨機(random)讀取,就是在你正常的順序讀取的過程中,先發一個隨機讀取的開始命令0x05命令,再傳入你要將內部那個數據指針定位 到具體什么地址,也就是2個cycle的列地址,然后再發隨機讀取結束命令0xE0,然后,內部那個數據地址指針,就會移動到你所制定的位置了,你接下來 再讀取的數據,就是從那個制定地址開始的數據了。
而Nand Flash數據手冊里面也說了,這樣的隨機讀取,你可以多次操作,沒限制的。
請注意,上面你所傳入的地址,都是列地址,也就是頁內地址,也就是說,對於頁大小為2K的Nand Flash來說,所傳入的地址,應該是小於2048+64=2112的。
不過,實際在Nand Flash的使用中,好像這種用法很少的。絕大多數,都是順序讀取數據。
如果想要在Linux下編寫Nand Flash驅動,那么就先要搞清楚Linux下,關於此部分的整個框架。弄明白,系統是如何管理你的Nand Flash的,以及,系統都幫你做了那些准備工作,而剩下的,驅動底層實現部分,你要去實現哪些功能,才能使得硬件正常工作起來。
在介紹Nand Flash的軟件細節方面之前,先來介紹一下Nand Flash的兩個相關的規范:ONFI和LBA。
ONFI規范,即Open Nand Flash Interface specification。
ONFI是Intel主導的,其他一些廠家(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)參與制定的,統一了Nand Flash的操作接口。
所謂操作接口,就是那些對Nand Flash操作的命令等內容。
而所謂統一,意思是之前那些Nand Flash的操作命令等,都是各自為政,雖然大多數常見的Nand Flash的操作,比如page read的命令是0x00,0x30,page write的命令是0x80,0x10等,但是有些命令相關的內容,很特別且很重要的一個例子就是,每個廠家的Nand Flash的read id的命令,雖然都是0x90,但是讀取出來的幾個字節的含義,每個廠家定義的都不太一樣。
因此,才有統一Nand Flash的操作接口這一說。
ONFI規范,官網可以下載的到:
http://onfi.org/specifications/
比如:
ONFI 2.2 Spec
http://onfi.org/wp-content/uploads/2009/02/ONFI%202_2%20Gold.pdf
ONFI規范中定義的Nand Flash的命令集合為:
可以看到,其中常見的一些命令,比如
- page read(0x00,0x30)
- page write(0x80,0x10)
- block erase(0x60,0xD0)
- Reset(0xFF)
等等命令,都是和普通的Nand Flash的命令是一樣的,而額外多出一些命令,比如Read Unique ID(0xED)等命令,是之前某些Nand Flash命令所不具有的。
如此,定義了Nand Flash的操作的命令的集合以及發送對應命令所遵循的時序等內容。
ONFI還定義了另外一個規范:
ONFI Block Abstracted Nand Specification
http://onfi.org/wp-content/uploads/2009/02/BA_NAND_rev_1_1_Gold.pdf
即ONFI LBA Nand,簡單說就是,邏輯塊尋址的Nand。其含義和Toshiba的LBA,基本沒有太多區別。
ONFI規范定義了之后,每家廠商的Nand Flash,只要符合這個ONFI規范,然后上層Nand Flash的軟件,就可以統一只用一種了,換句話說,我寫了一份Nand Flash的驅動后,就可以操作所有和ONFI兼容的Nand Flash了,整個Nand Flash的兼容性,上層軟件的兼容性,互操作性,就大大提高了。
而且,同樣的,由於任何規范在定義的時候,都會考慮到兼容性和擴展性,ONFI也不例外。針對符合ONFI規范的,寫好的軟件,除了可以操作多家與 ONFI兼容的Nand Flash之外,而對於以后出現的新的技術,新制程的Nand Flash,只要符合ONFI規范,也同樣可以支持,可以在舊的軟件下工作,而不需要由於Nand Flash的更新換代,而更改上層軟件和驅動,這個優勢,由於對於將Nand Flash芯片集成到自己系統中的相關開發人員來說,是個好消息。
LBA Nand Flash,Logical Block Address,邏輯塊尋址的Nand Flash,是Nand Flash大廠之一的Toshiba,自己獨立設計出來的新一代的Nand Flash的規范。
之所以叫做邏輯塊尋址,是相對於之前常見的,普通的Nand Flash的物理塊的尋址來說的。常見的Nand Flash,如果要讀取和寫入數據,所用的對應的地址是對應的:block地址+block內的Page地址+Page內的偏移量 = 絕對的物理地址,
此物理塊尋址,相對來說有個缺點,那就是,由於之前提到的Nand Flash會出現使用過程中出現壞塊,所以,遇到這樣的壞塊,首先壞塊管理要去將此壞塊標記,然后將壞塊的數據拷貝到另一個好的block中,再繼續訪問新的block。
而且數據讀寫過程中,還要有對應的ECC校驗,很多情況下,也都是軟件來實現這部分的工作,即使是硬件的ECC校驗,也要寫少量的軟件,去操作對應寄存器,讀取ECC校驗的結果,當然別忘了,還有對應的負載平衡等工作。
如此的這類的壞塊管理工作,對於軟件來說,很是繁重,而且整個系統實現起來也不是很容易,所以,才催生了一個想法,是否可以把ECC校驗,負載平衡,壞塊管理,全部都放到硬件實現上,而對於軟件來說,我都不關心,只關心有多少個Block供我使用,用於數據讀寫。
針對於此需求,Toshiba推出了LBA邏輯塊尋址的Nand Flash,在Nand Flash存儲芯片之外,加了對應一個硬件控制權Controller,實現了上述的壞塊管理,ECC校驗,負載平衡等工作,這樣使得人家想要用你LBA 的Nand Flash的人,去開發對應的軟件來驅動LBA Nand Flash工作,相對要做的事情,就少了很多,相對來說就是減輕了軟件系統集成方面的工作,提高了開發效率,縮短了產品上市周期。
LBA Nand,最早放出對應的樣片(sample)是在2006年8月。
網上找到一個LBA Nand Flash的簡介:
http://www.toshiba-components.com/prpdf/5678E.pdf
現早已經量產,偶在之前開發過程中,就用過其某款LBA的Nand Flash。
目前網上還找不到免費的LBA的規范。除非你搞開發,和Toshiba簽訂NDA協議后,才可以拿到對應的specification。
關於Toshiba LBA Nand規范,在此多說一點(參考附錄中:lba-core.c):
LBA Nand分為PNP,VFP和MDP三種分區:
- PNP主要用於存放Uboot等啟動代碼
- VFP主要用於存放uImage等內核代碼
- MDP主要用於存放用戶的數據,以及rootfs等內容
在解釋為何會有ONFI和LBA之前,先來個背景介紹:
目前Nand Flash的廠家有Samsung,Toshiba,Intel, Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等。
ONFI的出現,上面已經解釋過了,就是為了統一Nand Flash的接口,使得軟件兼容性更好;
而LBA的出現,是為了減輕軟件方面對Nand Flash的各種管理工作。上面這些解釋,其實只是技術上解釋。
總的來說,ONFI在對於舊的Nand Flash的兼容上,都是相對類似的。
總的來說:
ONFI規范,更注重對於Nand Flash的操作接口方面的定義 + ONFI LBA Nand的定義,而Toshiba LBA規范,主要側重於LBA的Nand的定義。
MTD,是Linux的存儲設備中的一個子系統。其設計此系統的目的是,對於內存類的設備,提供一個抽象層,一個接口,使得對於硬件驅動設計者來 說,可以盡量少的去關心存儲格式,比如FTL,FFS2等,而只需要去提供最簡單的底層硬件設備的讀/寫/擦除函數就可以了。而對於數據對於上層使用者來 說是如何表示的,硬件驅動設計者可以不關心,而MTD存儲設備子系統都幫你做好了。
對於MTD子系統的好處,簡單解釋就是,他幫助你實現了,很多對於以前或者其他系統來說,本來也是你驅動設計者要去實現的很多功能。換句話說,有了MTD,使得你設計Nand Flash的驅動,所要做的事情,要少很多很多,因為大部分工作,都由MTD幫你做好了。
當然,這個好處的一個“副作用”就是,使得我們不了解的人去理解整個Linux驅動架構,以及MTD,變得更加復雜。但是,總的說,覺得是利遠遠大於弊,否則,就不僅需要你理解,而且還是做更多的工作,實現更多的功能了。
此外,還有一個重要的原因,那就是,前面提到的Nand Flash和普通硬盤等設備的特殊性:
有限的通過出復用來實現輸入輸出命令和地址/數據等的IO接口,最小單位是頁而不是常見的bit,寫前需擦除等,導致了這類設備,不能像平常對待硬盤等操作一樣去操作,只能采取一些特殊方法,這就誕生了MTD設備的統一抽象層。
MTD,將Nand Flash,nor flash和其他類型的flash等設備,統一抽象成MTD設備來管理,根據這些設備的特點,上層實現了常見的操作函數封裝,底層具體的內部實現,就需要 驅動設計者自己來實現了。具體的內部硬件設備的讀/寫/擦除函數,那就是你必須實現的了。
表 1.5. MTD設備和硬盤設備之間的區別
HARD drives | MTD device |
---|---|
連續的扇區 | 連續的可擦除塊 |
扇區都很小(512B,1024B) | 可擦除塊比較大 (32KB,128KB) |
主要通過兩個操作對其維護操作:讀扇區,寫扇區 | 主要通過三個操作對其維護操作:從擦除塊中讀,寫入擦除塊,擦寫可擦除塊 |
壞快被重新映射,並且被硬件隱藏起來了(至少是在如今常見的LBA硬盤設備中是如此) | 壞的可擦除塊沒有被隱藏,軟件中要處理對應的壞塊問題 |
HDD扇區沒有擦寫壽命超出的問題 | 可擦除塊是有擦除次數限制的,大概是104-105次 |
多說一句,關於MTD更多的內容,感興趣的,去附錄中的MTD主頁去看。
關於mtd設備驅動,感興趣的可以去參考附錄中MTD設備的文章,該文章是比較詳細地介紹了整個MTD框架和流程,方便大家理解整個mtd框架和Nand Flash驅動。
關於nand flash,由於各個廠家的read id讀出的內容的定義,都不同,導致,對於讀出的id,分別要用不同的解析方法,下面這段代碼,是我之前寫的,本來打算自己寫信去推薦到Linux MTD內核源碼的,不過后來由於沒搞懂具體申請流程,就放棄了。不過,后來,看到Linux的MTD部分更新了,加了和下面類似的做法。
此處只是為了記錄下來,也算給感興趣的人一個參考吧。
文件:\linux-2.6.28.4\drivers\mtd\nand\nand_base.c
/* * Get the flash and manufacturer id and lookup if the type is supported */ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, struct nand_chip *chip, int busw, int *maf_id) { ... ... chip->chipsize = (uint64_t)type->chipsize << 20; /* 針對不同的MLC和SLC的nand flash,添加了不同的解析其ID的方法 */ /* Newer devices have all the information in additional id bytes */ if (!type->pagesize) { int erase_bits, page_base, block_base, old_50nm, new_40nm; uint8_t id3rd, id4th, id5th, id6th, id7th; /* The 3rd id byte holds MLC / multichip data */ chip->cellinfo = id3rd = chip->read_byte(mtd); /* The 4th id byte is the important one */ id4th = chip->read_byte(mtd); id5th = chip->read_byte(mtd); id6th = chip->read_byte(mtd); id7th = chip->read_byte(mtd); /* printk(KERN_INFO " (ID:%02x %02x %02x %02x %02x %02x %02x) ", id1st, id2nd, id3rd, id4th, id5th, id6th, id7th); */ if (nand_is_mlc(chip->cellinfo)) { /* * MLC: * 50nm has 5 bytes ID, further read ID will periodically output * 40nm has 6 bytes ID */ /* * the 4th byte is not the same meaning for different manufature */ if (NAND_MFR_SAMSUNG == *maf_id) { /* samsung MLC chip has several type ID meanings: (1)50nm serials, such as K9GAG08U0M (2)40nm serials, such as K9LBG08UXD */ /* old 50nm chip will periodically output if read further ID */ old_50nm = (id1st == id6th) && (id2nd == id7th); /* is 40nm or newer */ new_40nm = id6th & 0x07; if ((!old_50nm) && new_40nm) { /* * Samsang * follow algorithm accordding to datasheets of: * K9LBG08UXD_1.3 (40nm), * ID(hex): EC D7 D5 29 38 41 * this algorithm is suitable for new chip than 50nm * such as K9GAG08u0D, * ID(hex): EC D5 94 29 B4 41 */ int bit236; /* Calc pagesize, bit0,bit1: page size */ page_base = (1 << 11); /* 2KB */ mtd->writesize = page_base * (1 << (id4th & BIT01)); block_base = (1 << 17); /* 128 KB */ /* Calc block size, bit4,bit5,bit7: block size */ erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */ erase_bits |= (id4th >> 5) & BIT(2); /* get bit7 and combine them */ mtd->erasesize = block_base * (1 << erase_bits); /* Calc oobsize, bit2,bit3,bit6: oob size */ bit236 = (id4th >> 2) & BIT01; /* get bit2,bit3 */ bit236 |= (id4th >> 4) & BIT(2); /* get bit6 and combine them */ switch (bit236) { case 0x01: mtd->oobsize = 128; break; case 0x02: mtd->oobsize = 218; break; default: /* others reserved */ break; } } else { /* * Samsang * follow algorithm accordding to datasheets of: * K9GAG08U0M (50nm) * this algorithm is suitable for old 50nm chip */ goto slc_algorithm; } } else if (NAND_MFR_TOSHIBA == *maf_id) { /* * Toshiba * follow algorithm guess from ID of TC58NVG3D1DTG00: * Toshiba MLC TC58NVG3D1DTG00 1GB 8bit 1chip * 4K+218 512K+27K 3.3V, (ID:98 D3 94 BA 64 13 42) */ int bit23; /* Calc pagesize, bit0,bit1: page size */ page_base = (1 << 10); /* 1KB */ mtd->writesize = page_base * (1 << (id4th & BIT01)); block_base = (1 << 16); /* 64 KB */ /* Calc block size, bit4,bit5: block size */ erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */ mtd->erasesize = block_base * (1 << erase_bits); /* Calc oobsize, use spare/redundant area bit */ bit23 = (id4th >> 2) & BIT01; /* get bit2,bit3 */ switch (bit23) { case 0x01: mtd->oobsize = 128; break; case 0x02: mtd->oobsize = 218; break; default: /* others reserved */ break; } /* Get buswidth information: x8 or x16 */ busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0; } else if (NAND_MFR_MICRON == *maf_id) { /* * Micron * follow algorithm accordding to datasheets of: * 29F32G08CBAAA */ int spare_area_size_bit; /* Calc pagesize, bit0,bit1: page size */ page_base = (1 << 10); /* 1KB */ mtd->writesize = page_base * (1 << (id4th & 0x03)); block_base = (1 << 16); /* 64 KB */ /* Calc block size, bit4,bit5: block size */ erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */ mtd->erasesize = block_base * (1 << erase_bits); /* Calc oobsize, use spare/redundant area bit */ spare_area_size_bit = (id4th >> 2) & BIT(0); if (spare_area_size_bit) /* special oob */ mtd->oobsize = 218; else /* normal */ mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO; /* Get buswidth information: x8 or x16 */ busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0; } else { /* * Others * FIXME: update follow algrithm, * according to different manufacture's chip's datasheet */ goto slc_algorithm; } } else { /* * SLC, only has 4 bytes ID, further read will output periodically, such as: * Hynix : HY27UG084G2M, only has 4 byte ID, * following read ID is periodically same as the 1st ~ 4th byte, * for HY27UG084G2M is : 0xAD 0xDC 0x80 0x15 0xAD 0xDC 0x80 0x15 ..... */ slc_algorithm: /* Calc pagesize, bit0,bit1: page size */ page_base = (1 << 10); /* 1KB */ mtd->writesize = page_base * (1 << (id4th & BIT01)); block_base = (1 << 16); /* 64 KB */ /* Calc block size, bit4,bit5: block size */ erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */ mtd->erasesize = block_base * (1 << erase_bits); /* Calc oobsize, use fixed ratio */ mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO; /* Get buswidth information: x8 or x16 */ busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0; } } else { /* * Old devices have chip data hardcoded in the device id table */ mtd->erasesize = type->erasesize; mtd->writesize = type->pagesize; mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO; busw = type->options & NAND_BUSWIDTH_16; } /* * 以上內容,主要是更加不同廠家的nand flash的datasheet,一點點總結出來的算法。 * 最新的Linux的MTD部分,已經添加了類似如上部分的代碼。此處貼出來,僅供參考。 */ /* * Check, if buswidth is correct. Hardware drivers should set * chip correct ! */ if (busw != (chip->options & NAND_BUSWIDTH_16)) { printk(KERN_INFO "NAND device: Manufacturer ID:" " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id, nand_manuf_ids[maf_idx].name, mtd->name); printk(KERN_WARNING "NAND bus width %d instead %d bit\n", (chip->options & NAND_BUSWIDTH_16) ? 16 : 8, busw ? 16 : 8); return ERR_PTR(-EINVAL); } ... ... }
下面這部分主要介紹一下,關於硬件的設計和規范,是如何映射到具體的軟件實現的,看了這部分內容之后,你對如何根據硬件的規范去用軟件代碼實現對應的功能,就有了大概的了解了,然后去實現對應的某硬件的驅動,就有了大概的脈絡了。
關於硬件部分的細節,前面其實已經介紹過了,但是為了方便說明,此處還是以讀操作為例去講解硬件到軟件是如何映射的。
再次貼出上面的那個圖:
對於上面的從1到6,每個階段所表示的含義,再簡單解釋一下:
上面的是內容,說的是硬件是如何設計的,而這硬件的設計,即硬件的邏輯時序是如何規定的,對應的軟件實現,也就要如何實現。不過可以看出的是,其中 很多步驟,比如步驟1和步驟4,那都是固定的,而且,即使其中的步驟2和步驟3,即使是不同廠家和不同的Nand Flash芯片,除了要寫入的列地址和行地址可能不同之外,也都是邏輯一樣的,同樣地,步驟5和6,也都是一樣的,唯一不同的,是每家不同的Nand Flash控制器不同,所以具體到步驟6的時候,去讀出數據的方式不同,所以,那一部分肯定是你要實現Nand Flash驅動的時候要自己實現的,而對應的其他幾個公有的步驟呢,就有了Linux的MTD層幫你實現好了,所以,下面就來介紹一下,關於讀取一個 Nand Flash的頁Page,Linux的MTD層,是如何具體的幫你實現的:
關於Nand Flash的讀操作,即讀取一頁的數據,這樣的讀數據的操作,很明顯,是從上層文件系統傳遞過來的,其細節我們在此忽略,但是要知道,上層讀取數據的請求,傳遞到了MTD這一層,其入口是哪個函數,然后我們才能繼續往下面分析細節。
關於下面所要的介紹的代碼,如果沒有明確指出,都是位於此文件:
代碼位置:\drivers\mtd\nand\nand_base.c
MTD讀取數據的入口是nand_read,然后調用nand_do_read_ops,此函數主體如下:
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { ............ while(1) { ...... chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);...... ret = chip->ecc.read_page(mtd, chip, bufpoi,
buf += bytes; ...... readlen - = bytes; if (! readlen) break; / * For subsequent reads align to page boundary. */ col = 0; / * Increment page address */ realpage++; page = realpage & chip->pagemask; ...... } ...... } ............ return mtd->ecc_stats.corrected - stats.corrected ? - EUCLEAN : 0; }
對於上述中的函數cmdfunc,一般來說可以不用自己的驅動中實現,而直接使用MTD層提供的已有的函數,nand_command_lp,其細節如下:
static void nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) { ...... / * Command latch cycle */ chip->cmd_ctrl(mtd, command & 0xff, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);if (column ! = - 1 | | page_addr ! = - 1) { int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE; / * Serially input address */ if (column ! = - 1) { / * Adjust columns for 16 bit buswidth */ if (chip->options & NAND_BUSWIDTH_16) column >>= 1; chip->cmd_ctrl(mtd, column, ctrl);
ctrl &= ~NAND_CTRL_CHANGE; chip->cmd_ctrl(mtd, column >> 8, ctrl); } if (page_addr ! = - 1) { chip->cmd_ctrl(mtd, page_addr, ctrl);
chip->cmd_ctrl(mtd, page_addr >> 8, NAND_NCE | NAND_ALE); / * One more address cycle for devices > 128MiB */ if (chip->chipsize > (128 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16, NAND_NCE | NAND_ALE); } } chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); / * * program and erase have their own busy handlers * status, sequential in, and deplete1 need no delay */ switch (command) { ...... case NAND_CMD_READ0: chip->cmd_ctrl(mtd, NAND_CMD_READSTART, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); / * This applies to read commands */ default: ...... } / * Apply this short delay always to ensure that we do wait tWB in * any case on any machine. */ /* */ ndelay(100);
nand_wait_ready(mtd);
}
對於之前的的函數
read_page
,一般來說也可以不用自己的驅動中實現,而直接使用MTD層提供的已有的函數,nand_read_page_hwecc
,該函數所要實現的功能,正是上面余下沒介紹的6,即一點點的讀出我們要的數據:
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int page) { ...... for (i = 0; eccsteps; eccsteps-- , i += eccbytes, p += eccsize) { chip->ecc.hwctl(mtd, NAND_ECC_READ); chip->read_buf(mtd, p, eccsize);chip->ecc.calculate(mtd, p, &ecc_calc[i]); } ...... }
上面的read_buf,就是真正的去讀取數據的函數了,由於不同的Nand Flash controller控制器所實現的方式不同,所以這個函數必須在你的Nand Flash驅動中實現,即MTD層,能幫我們實現的都實現了,不能實現的,那肯定要你的驅動自己實現。
對於我們這里的s3c2410的例子來說,就是s3c2410_nand_read_buf
:
文件位置:\drivers\mtd\nand\s3c2410.c
static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) { struct nand_chip *this = mtd->priv; readsb(this->IO_ADDR_R, buf, len);}
可以看出,此處的實現相當地的簡單,就是讀取對應的IO的地址,然后就可以把數據讀出來就可以了。
不過,要注意的是,並不是所有的驅動都是這么簡單,具體情況則是不同的Nand Flash控制器對應不同實現方法。
至此,關於整個的Nand Flash的讀取一頁的數據的操作,是如何將硬件的邏輯時序圖,映射到對應的軟件的實現的,就已經介紹完了。而看懂了這個過程,你才會更加明白,原來 MTD層,已經幫助我們實現了很多很多通用的操作所對應的軟件部分,而只需要我們實現剩下那些和具體硬件相關的操作的函數,就可以了,可以說大大減輕了驅 動開發者的工作量。
因為,如果沒了MTD層,那么上面那么多的函數,幾乎都要我們自己實現,單單是代碼量,就很龐大,而且再加上寫完代碼后的驅動測試功能是否正常,使得整個驅動開發,變得難的多得多。
在介紹具體如何寫Nand Flash驅動之前,我們先要了解,大概的整個系統,和Nand Flash相關的部分的驅動工作流程,這樣,對於后面的驅動實現,才能更加清楚機制,才更容易實現,否則就是,即使寫完了代碼,也還是沒搞懂系統是如何工作的了。
讓我們以最常見的,Linux內核中已經有的三星的Nand Flash驅動,來解釋Nand Flash驅動具體流程和原理。
此處是參考2.6.29版本的Linux源碼中的\drivers\mtd\nand\s3c2410.c
,以2410為例。
在Nand Flash驅動加載后,第一步,就是去調用對應的init
函數,s3c2410_nand_init
,去將在Nand Flash驅動注冊到Linux驅動框架中。
驅動本身,真正開始,是從probe函數,s3c2410_nand_probe->s3c24xx_nand_probe,
在probe過程中,去用clk_enable
打開Nand Flash控制器的clock時鍾,用request_mem_region
去申請驅動所需要的一些內存等相關資源。然后,在s3c2410_nand_inithw
中,去初始化硬件相關的部分,主要是關於時鍾頻率的計算,以及啟用Nand Flash控制器,使得硬件初始化好了,后面才能正常工作。
需要多解釋一下的,是這部分代碼:
for (setno = 0; setno < nr_sets; setno++, nmtd++) { pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info); s3c2410_nand_init_chip(info, nmtd, sets);nmtd->scan_res = nand_scan_ident(&nmtd->mtd,(sets) ? sets->nr_chips : 1);
if (nmtd->scan_res == 0) { s3c2410_nand_update_chip(info, nmtd); nand_scan_tail(&nmtd->mtd);
s3c2410_nand_add_partition(info, nmtd, sets);
} if (sets != NULL) sets++; }
調用init chip去掛載你的nand 驅動的底層函數到Nand Flash的結構體中,以及設置對應的ecc mode,掛載ecc相關的函數 |
|
scan_ident,掃描nand 設備,設置Nand Flash的默認函數,獲得物理設備的具體型號以及對應各個特性參數,這部分算出來的一些值,對於Nand Flash來說,是最主要的參數,比如nand falsh的芯片的大小,塊大小,頁大小等。 |
|
scan tail,從名字就可以看出來,是掃描的后一階段,此時,經過前面的scan_ident,我們已經獲得對應Nand Flash的硬件的各個參數,然后就可以在scan tail中,根據這些參數,去設置其他一些重要參數,尤其是ecc的layout,即ecc是如何在oob中擺放的,最后,再去進行一些初始化操作,主要 是根據你的驅動,如果沒有實現一些函數的話,那么就用系統默認的。 |
|
add partion,根據你的Nand Flash的分區設置,去分區 |
等所有的參數都計算好了,函數都掛載完畢,系統就可以正常工作了。
上層訪問你的nand falsh中的數據的時候,通過MTD層,一層層調用,最后調用到你所實現的那些底層訪問硬件數據/緩存的函數中。
關於上面提到的,在nand_scan_tail的時候,系統會根據你的驅動,如果沒有實現一些函數的話,那么就用系統默認的。如果實現了自己的函數,就用你的。
估計很多人就會問了,那么到底我要實現哪些函數呢,而又有哪些是可以不實現,用系統默認的就可以了呢。
此問題的,就是我們下面要介紹的,也就是,你要實現的,你的驅動最少要做哪些工作,才能使整個Nand Flash工作起來。
其實,要了解,關於驅動框架部分,你所要做的事情的話,只要看看三星的整個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, }, };
而對於底層硬件操作的有些函數,總體上說,都可以在上面提到的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:
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;
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; }
s3c2410_nand_write_buf 和 s3c2410_nand_read_buf:這是兩個最基本的操作函數,其功能,就是往你的Nand Flash的控制器中的FIFO讀寫數據。一般情況下,是MTD上層的操作,比如要讀取一頁的數據,那么在發送完相關的讀命令和等待時間之后,就會調用到 你底層的read_buf,去Nand Flash的FIFO中,一點點把我們要的數據,讀取出來,放到我們制定的內存的緩存中去。寫操作也是類似,將我們內存中的數據,寫到Nand Flash的FIFO中去。 |
|
s3c2410_nand_select_chip : 實現Nand Flash的片選。 |
|
Nand Flash控制器中,一般都有對應的數據寄存器,用於給你往里面寫數據,表示將要讀取或寫入多少個字節(byte,u8)/字(word,u32) ,所以,此處,你要給出地址,以便后面的操作所使用 |
|
s3c2410_nand_hwcontrol:給底層發送命令或地址,或者設置具體操作的模式,都是通過此函數。 |
|
s3c2410_nand_devready:Nand Flash的一些操作,比如讀一頁數據,寫入(編程)一頁數據,擦除一個塊,都是需要一定時間的,在命發送完成后,就是硬件開始忙着工作的時候了,而硬件 什么時候完成這些操作,什么時候不忙了,變就緒了,就是通過這個函數去檢查狀態的。 一般具體實現都是去讀硬件的一個狀態寄存器,其中某一位是否是1,對應着是出於“就緒/不忙”還是“忙”的狀態。這個寄存器,也就是我們前面分析時序圖中的R/B#。 |
|
s3c2410_nand_calculate_ecc:如果是上面提到的硬件ECC的話,就不用我們用軟件去實現校驗算法了,而是直接去讀取硬件產生的ECC數值就可以了。 |
|
s3c2410_nand_correct_data:當實際操作過程中,讀取出來 的數據所對應的硬件或軟件計算出來的ECC,和從oob中讀出來的ECC不一樣的時候,就是說明數據有誤了,就需要調用此函數去糾正錯誤。對於現在SLC 常見的ECC算法來說,可以發現2位,糾正1位。如果錯誤大於1位,那么就無法糾正回來了。一般情況下,出錯超過1位的,好像幾率不大。至少我看到的不是 很大。更復雜的情況和更加注重數據安全的情況下,一般是需要另外實現更高效和檢錯和糾錯能力更強的ECC算法的。 |
|
此處,多數情況下,你所用的Nand Flash的控制器,都是支持硬件ECC的,所以,此處設置硬件ECC(HW_ECC) ,也是充分利用硬件的特性,而如果此處不用硬件去做的ECC的話,那么下面也會去設置成NAND_ECC_SOFT,系統會用默認的軟件去做ECC校驗, 相比之下,比硬件ECC的效率就低很多,而你的Nand Flash的讀寫,也會相應地要慢不少 |
|
s3c2410_nand_enable_hwecc: 在硬件支持的前提下,前面設置了硬件ECC的話,要實現這個函數,用於每次在讀寫操作前,通過設置對應的硬件寄存器的某些位,使得啟用硬件ECC,這樣在 讀寫操作完成后,就可以去讀取硬件校驗產生出來的ECC數值了。 |
當然,除了這些你必須實現的函數之外,在你更加熟悉整個框架之后,你可以根據你自己的Nand Flash的特點,去實現其他一些原先用系統默認但是效率不高的函數,而用自己的更高效率的函數替代他們,以提升你的Nand Flash的整體性能和效率。
目錄
此處記錄一些和Nand Flash相關的一些資料:
對於Nand Flash,目前市場上可以看到很多家的Nand Flash的芯片,每家都有各自的型號,即對應的id或part number。
此處整理記錄一下,各個廠家的Nand Flash的命名規則。
[5] NAND和NOR的比較
[9] ONFI官網
[10] ONFI 2.2的Spec
[12] LBA Nand Flash的簡介
[13] Toshiba TH58NS512DC
[14] Samsung K9F5608U0B
[16] lba-core.c