WinCE NAND flash - FAL
From ESSLabWiki
1. Introduction
Flash與一般常見的Disk不同,其特性是無法重複對同一塊記憶體位置去做Write的動作,必須要Erase那塊記憶體位置才可以做Write的動作。因此一般的File System,如FAT16、FAT32、NFTS…,無法直接在Flash Memory上使用;若是想要沿用這些File System,則必須透過一層Translation Layer來將Logical Block Address對應到實體的Flash Memory的位置,並透過一些機制能讓系統能把Flash Memory當作一般的硬碟一樣處理,在Win CE的平台上,我們稱這層為FTL(Flash Translation Layer)。
FTL最原始的應用,為使用於NOR Flash,然而目前的市場價格,大容量的NOR Flash的成本,遠高於同樣容量的NAND flash ,因此有NFTL(NAND Flash Translation Layer)的產生,NFTL主要想法與FTL相似,主要差別在於是用在NAND Flash上。
Figure 1. Architecture
本文件將介紹WinCE6.0在與Flash相關的Data structure、RAM上的Data structure,以及Win CE如何在Flash作Garbage Collection、Wear-Leveling,最後說明如何在Win CE的基本系統上,註冊一個Flash Device。
2. System Architecture
下圖為Windows CE 平台中,Flash Memory驅動的系統架構
Figure 2. Flash Architecture
先對本篇的重點:FAL及FMD做基本定義的說明如下:
FMD(FLASH Media Driver)可針對特定廠商的Flash作Driven、Read、Write、Erase等動作,實際去對Flash做讀寫的操作。
FAL(FLASH Abstraction Layer):File System對Flash讀寫,必須透過此層操作。而此層則再根據FMD所提供的Interface再對Flash做讀寫。
參考Figure 1再來看此圖的行為;File System為了對Flash操作,必須透過FAL對FMD(Driver)做操作。因此面對各家Flash來說,只需修改FMD即可,而不用再行修改 FAL的Interface即可對Flash做Read、Write、Erase等基本的操作。因此可得到一個結論,若是有一個產品裡的Flash換成了另一廠商的,因Flash對FAL的interface是一致的,那麼只要抽換FMD則其它的程式都不用再修改即可使用。
那麼FMD對FAL所提供的interface及其作用後面會再詳細說明,此處便不再贅述。
FAL存在兩個Table(DLUT、Secondary Table)以提供Translation的動作,其中也存在兩條List一個存Free Sector,一個存Dirty Block來分別提供Free Sector以及可幫助Garbage Collection的作用,詳細的流程及使用後續會再行說明。
3. Terminology
以下說明一些在本篇會出現的基本名詞及其定義
在Flash中可能會有多個Region,在實體上會如下圖:
Figure 3. Region at Flash
而Region中會存有多個Block,其示意圖如下:
Figure 4. Block at Region
而Block中也有多個Sector,其示意圖如下:
Figure 5. Sector at Block
但這裡要注意的是,Win CE是採用Sector Mapping的方式,因此取Block動作時,必須依所給的Sector Address算出它是屬於那一個Block。
這裡再說明它在各Term的情況:
對Region而言,它只紀錄了它有多少個Block,且一個Block有多少個Sector。
對Block而言,它的起始位置就是第一個Sector(以Figure 5來看,就是Sector 1)。但是這裡要說明一下Block裡面是如何去算它第一個Sector的位置。假設一個Block中有10個Sector,那麼下一個Block的第一個Sector就是Sector 11。
對File System而言,所看到的Sector Address都是Logical Address;當File System想取一個在Flash上的資料,必須要給FAL那個資料的Logical Address,而FAL要去取所需的資料,需根據Logical Address解析是位於Mapping Table的位置,再依據Table中的位置取出Physical Address,再交由FMD去取出在Flash上的Data;FAL再將取得的Data交給送出Request的System。這中間更詳細的細節,後面章節會再解釋。
4. Address Translation
一個Logical Address要如何快速找到Physical Address,靠的就是Mapping Table。有了這個就可以很方便且快速的找到所需的Address(Sector)。本段要介紹如何Create Mapping Table以及如何Mapping。
4.1 In-RAM Data Structures
PUCHAR m_pDynamicLUT[MASTER_TABLE_SIZE]; DWORD m_cbPhysicalAddr;
BOOL m_bIsNumSectorsPerSecTableLog2;
DWORD m_dwNumSectorsPerSecTable;
DWORD m_dwSecondaryTableSize;
DWORD m_dwStartLogSector;
DWORD m_dwNumLogSectors;
DWORD m_dwStartPhysSector;
DWORD m_dwNumPhysSectors;
這裡主要的動作是在處理及維護 DLUT Table和Secondary Table;其用途是為了快速的從Logical Address來找到Physical Address;是做Translation一個重要的Table。
m_pDynamicLUT[MASTER_TABLE_SIZE]:
這裡MASTER_TABLE_SIZE是256。因此可知一開始便給定Table的大小。這個Variable是Dynamic Look-Up Table (DLUT),也就是Mapping Table。
m_bIsNumSectorsPerSecTableLog2:
判斷Sector的個數在Secondary Table中是否是power of 2,會影響計算Table Size以及Secondary ID。若它是2的倍數,可直接Shift取值,節省做除法取值的時間
m_dwNumSectorsPerSecTable:
在Secondary table中可紀錄多少個Sector
m_dwSecondaryTableSize:
Secondary Table的Size。根據有多少個Logical Sector及Physical Address的長度來決定Size的大小
m_dwStartLogSector:
這個Logical Block的起始Sector的位置
m_dwNumLogSectors:
Logical Sector的個數
m_dwStartPhysSector:
這個Physical Block的起始Sector的位置
m_dwNumPhysSectors:
Physical Block的Sector的個數
4.2 On-Flash Data Structures
typedef struct _FlashInfoEx
{
DWORD cbSize;
FLASH_TYPE flashType;
DWORD dwNumBlocks;
DWORD dwDataBytesPerSector;
DWORD dwNumRegions;
FlashRegion region[1];
}FlashInfoEx, *PFlashInfoEx;
typedef struct _FlashInfo
{
FLASH_TYPE flashType;
DWORD dwNumBlocks;
DWORD dwBytesPerBlock;
WORD wSectorsPerBlock;
WORD wDataBytesPerSector;
}FlashInfo, *PFlashInfo;
flashType:在init之後這裡要取得Flash Type看是NOR亦或NAND。
dwNumBlocks:看這Flash中有多少個Block
dwBytesPerBlock:一個Block有多少Bytes
wSectorsPerBlock:一個Block有多少個Sector
wDataBytesPerSector:一個Sector有多少個Bytes
這裡是取得Flash的基本Info,之後再去算這個Flash內有多少Block。而FlashInfoEx與FlashInfo的差別是Region的部分,由於看的NAND flash的部分只用一個Region,所以,下面都以一個Region做介紹。
Block有以下幾種Status, 根據不同的status來決定Sector會被放在那一個List裡。
BLOCK_STATUS_UNKNOWN:其它無法判斷的情況
BLOCK_STATUS_BAD:這個Block是Bad Block。
BLOCK_STATUS_READONLY:這個Block是Read Only。
BLOCK_STATUS_RESERVED:這個Block被Reserved。
另外,在取Block Status時,FAL是用Block ID跟FMD取Block Status,而FMD是在得到這個Block ID之後,去算出此Block在Flash中的第一個Sector是第幾個Sector,再去取出它的Status給FAL。
以下圖為例,若是一個Block有10個Sector,那麼Block 2的第一個Sector就是Sector 11。因此,FMD便是取Sector 11的Status交還給FAL。
Figure 6. Get Block Status
4.3 How to Create Mapping Table
這裡介紹在Win CE所使用的Mapping Table。它是去Scan Flash上Info,先取出Region、Flash Type。一個Region內有多個Block,每一個Region都有各自一個獨立的Table ,Region裡有多個Block。
Region的data Structure如下
typedef struct _FlashRegion
{
REGION_TYPE regionType;
DWORD dwStartPhysBlock;
DWORD dwNumPhysBlocks;
DWORD dwNumLogicalBlocks;
DWORD dwSectorsPerBlock;
DWORD dwBytesPerBlock;
DWORD dwCompactBlocks;
}FlashRegion, *PFlashRegion;
dwStartPhysBlock:在這個Region中Physical Block的index值,這裡的初始值是0。也就是說若是這個Flash中有兩個Region,而每個Region中有10個Block,則第一個 Region的Start Physical Block為0,第二個Region的Start Physical Block為10。
dwNumPhysBlocks:有多少個Block在這個Region裡
dwSectorsPerBlock:Block中有多少個Sector
dwCompactBlocks:在Compactor裡定義其值為2,之後在Compaction會看到
這裡是做Sector Mapping。因此Table是依一個個Sector去建成的。先取Block,再取Block中的所有Sector的Info(這裡取出的只有 Spare area的資料);依據這個Sector status再決定它是放入一般的List (在這裡亦有分Free, Read only) 或是Dirty list。其它有info的sector則是去做Mapping Table的動作。其中要注意的是,由於Write是以Sector為單位在寫,所以存Free List是以Sector為單位在存,而Erase是以Block為單位,所以Dirty List是以Block為單位在存。
建Table的psuedo-code如下,
for (each Block)
{
for (each Sector)
{
Read Sector Info (spare area)
if (Sector is free)
{
add into m_list(Free)
}
if (Sector is dirty)
{
add into dirty_list
}
if (Sector is mapped)
{
if (Block is Read_Only && Sector is first sector on Block)
{
add into m_list(Read_only);
for (each Sector)
mapping the Table : L2P
break;
}
mapping the Table : L2P
}
} //for (each Sector)
}// for (each Block)
接下來說明Mapping Table的流程:
DLUT(Dynamic Look Up Table)指的是Master Table,有256個entry,一個entry對映一個Secondary Table。沒有用到的Sector(Free Sector)不會在Table中去建立,採取On Demand的方式。
一個有資料的Sector會存有之前在系統中所代表其位置的Sector Logical Address,若是要找Secondary Tables並未建立,系統才會去建其Table,並根據Logical Address算出它在Secondary Table中的位置,再將其Physical Address存入至Table中。
Figure 7. DLUT and Secondary Table
下圖是詳細的建立Table的過程:
1. 根據給的Logical Sector先去算出Secondary Table ID(此圖假設算出來的ID = 1),再查詢是否已有Secondary Table,若該table不存在,系統會在此時Allocate一個新的Secondary Table。
2. 就先前讀出的Sector’s Spare Area的資料來建Secondary Table。再來便是計算在Secondary Table中的位置
3. 取出後再把Physical Sector Address存到這個位置中:
Figure 8. Create Logical to Physical Sector Address
4.4 Read
它做的流程可參考Figure 4來解釋,。根據Sector’s Logical Address算出在DLUT是第幾個entry,再依據Entry就可以取得相對應的Secondary Table,再利用Logical Address做計算取出在這個Secondary Table中的位置,之後便可取得Physical Address。而FMD就可根據這個Address來取出位於Flash的資料。
4.5 Write
在Write之前必須在Free list中取一個可用的Sector,若是Sector不足,便會啟動Compaction機制,以取到足夠的Sector,以便完成 Write。
在Write之前先將Sector Info Mark成處理中,再去寫Sector info;寫完info確定該Sector可以寫入,再將Sector Info 標示為處理完,與Data一起寫入。最後更新Mapping Table以及Logical Address對映的Physical Address。
而之前舊的資料,將其update Sector info設定為Dirty;此外會算出此Sector位於哪一個Block,並將該Block Dirty的Sector個數加1。這邊要請讀者注意的是,它是SLC的動作,它可以允許一次這個動作,而MLC是不允許此值直接變更寫回。
5. Garbage Collection (Compaction)
Garbage Collection,在WinCE中的命名是Compaction。簡單的說,其目的是回收被無效資料所佔據的空間,且一次可回收愈多愈好。
5.1 What’s Garbage Collection?
由於Flash無法在更新資料時寫回相同的位置(除非先將該位置Erase),所以採Outplace的方式寫回資料。Outplace是將更新的資料寫入在不同的頁面中,來避免每次更新資料就必須進行抹除的動作的Overhead。
那麼原來不能直接寫回而變成無用舊資料的Sector,便需要有policy去將它釋放成為可用的Sector,這個policy便是Garbage Collection
5.2 Garbage Collection Policy
在這個FAL中會存在兩條List,分別去紀錄Free Sector及Dirty Block。這兩條List所紀錄的大小不一樣是由於寫是Sector為單位去寫,而Erase是以Block為單位在做。
上面所提到的直接選Dirty Block,便是直接去取Dirty List裡Dirty Sector最多的一個Block來當做Victim Block。而Random選Block的方式,是取第一個Physical Block做Base,用1~32做Random去除Physical Block的數量取餘數;Base加上餘數之後的值為Block ID,這個Block即為Victim Block。
Compaction的時機是當Free Sector不足時做,且會做到這個Compaction完成才離開,這裡稱它是Synchronous Compaction。例如:一個Block有20個Sector,那麼Free Sector最少要有40個才夠。另一個情況是,當你要寫的Data的Sector個數若是比目前Free Sector個數多時,它也會強制執行Compaction直到有足夠的Free Sector,得以完成Write Data的任務才會結束。
另一個時機是當Flash目前的Dirty Sector數目Free Sector多(但此情況還是Free Sector的個數比限制的下限個數多),但是它的Priority低,是Background Compaction這裡稱它是Asynchronous Compaction,不會強制必須要Compaction結束才會離開。
6. Wear-Leveling
6.1 What is Wear-Leveling?
因為Flash的每個Block有Erase次數的限制,當某個Block Erase次數過多,可能會造成該Block存取速度變慢,嚴重時甚至會造成該Block毀損。因此為了避免同一區塊過度存取而造成毀損,且能夠平均每個 Block Erase的的次數,此機制稱為Wear-Leveling。
6.2 How WL is done in FAL
這裡Critical是指當Free Sector不足時的情況,所以會直接取Dirty List裡的Block去做Compact。其它情況下就使用Random的方式,下面就直接介紹這兩種policy。
1. Critical = True
從Dirty List裡取一個Dirty Sector最多的Block來Earse
Bool value 不更改。
2. Critical = False
是用Random的方式,用Static Bool value做交替的條件,Bool value的初始值為False。
若第一次是Random方式選(Bool Value = True),且更改Bool value
則第二次從Dirty List裡選(Bool Value = False),亦更改Bool value
此外,若是Random選出來的Block是Free或是Invalid就從Dirty List裡選。
Random使用範例如下:
Sequence(S)
1
2
3
4
5
Execute
Dirtiest
Random
Dirtiest
Random
Dirtiest
Free Block
N
N
N
Y
N
Re-Execute
Dirtiest
S 1:第一次是Bool value = False,所以從Dirty List裡取,且更改Bool value為True
S 2:第二次是Bool value = True,所以取Random,且更改Bool value為False
S 3:第三次是Bool value = False,從Dirty List裡取,且更改Bool value為True
S 4:第四次是Bool value = True,所以取Random,且更改Bool value為False。但取出的Block為Free Block所以改取Dirty List。
S 5:第五次是Bool value = False,從Dirty List裡取,且更改Bool value為True
綜合使用範例如下:
C = Critical, D = Dirty Block, R = Random Block,
B = bRandom-表示T下一次要選Random或是從Dirty List裡取Victim
(請看Critical = False的範例及說明)
若C的順序如下:
TFFTFFF B = F (init)
則選的Block的流程如下:
Critical
Choose Block
B (Change)
C = T
D
B = F(no change)
C = F
D
B = T
C = F
R→若選出Block is Free
則再從D選
B = F
C = T
D
B = F (no change)
C = F
D
B = T
C = F
R
B = F
C = F
D
B = T
7. FMD/FAL interface
7.1 FMD給FAL的函式介面
typedef struct _FMDInterface
{
DWORD cbSize;
PFN_INIT pInit;
PFN_DEINIT pDeInit;
PFN_GETINFO pGetInfo;
PFN_GETBLOCKSTATUS pGetBlockStatus;
PFN_SETBLOCKSTATUS pSetBlockStatus;
PFN_READSECTOR pReadSector;
PFN_WRITESECTOR pWriteSector;
PFN_ERASEBLOCK pEraseBlock;
PFN_POWERUP pPowerUp;
PFN_POWERDOWN pPowerDown;
PFN_GETPHYSSECTORADDR pGetPhysSectorAddr;
PFN_OEMIOCONTROL pOEMIoControl;
} FMDInterface, *PFMDInterface;
以上FMD Function其本來是以FMD_做Prefix,以pGetBlockStatus為例,其在FMD.cpp的naming是 FMD_GetBlockStauts;但為了某些使用技巧,因此將其包裝成Structure使其得以用Function Point的方式使用,以下說Function的用途:
pInit:
當Flash Device要initial時對Device做初始化的動作。找到其支援的chip加以進行初始化,以及返回一個FMD handle。
pDeInit:
取得FMD handle以釋放一些用到的資源,關掉chip controller。
pGetInfo:
該函數用於取得Flash的資訊。其中pFlashInfo是一個包含Flash資訊的結構。
pFlashInfo->flashType:Flash的類型。
pFlashInfo->wDataBytesPerSector:一個Sector多少個Bytes。
pFlashInfo->dwNumBlocks:Flash中總共有多少個Block。
pFlashInfo->wSectorsPerBlock:每個Block中包含多少個Sector。
pFlashInfo->dwBytesPerBlock:每個Block中包含多少個Bytes。
pGetBlockStatus:
為取得某一個block的狀態。參數為Block Address。由於Flash中可能有Bad Block,所以首先會檢查目前是否為Bad Block,是取得第一個Sector的Status即可知道。如果發現該塊是Bad Block,應該返回BLOCK_STATUS_BAD。如果不是Bad Block,需要讀取這個塊的起始Sector的Sector Info。如果讀該Sector Info出錯,應該返回BLOCK_STATUS_UNKNOWN。
pSetBlockStatus:
設定某個block的狀態,第一個參數是Block位址,第二個是要設定的狀態。在這個函數中,首先檢查dwStatus是不是 BLOCK_STATUS_BAD,如果是就對作Bad Block標記,然後返回FALSE。如果不是,就將dwStatus寫到該block的第一個Sector的info中。
pReadSector:
用於讀Flash上的一個Sector。其中傳入的參數值代表如下:
startSectorAddr:
Sector的起始位址,就是從哪個Sector開始。
pSectorBuff:
讀出的每一個Sector的資料都存放在這個buffer中。
pSectorInfoBuff:
一般每個Sector的資訊會被保存在Flash的資料中。從Flash的資料將該Sector的相關資訊讀出來,存放在這個buffer中。這些資料也就是Spare area。
dwNumSectors:
讀取多少個Sector。
pWriteSector:
意義與上面相同,此處是對Write。
pEraseBlock:
為Erase block,參數為第幾個block。
pPowerUp:
恢復Flash設備電源
pPowerDown:
關閉Flash設備電源
pGetPhysSectorAddr:
取得在Flash上它physical Address。
pOEMIoControl:
就像很多的IOControl函數一樣,根據不同的case,實現相應的功能。針對NAND Flash來說,這裡面不一定都需要implement。事實上,如果什麼都沒有implement,也不影響使用。
7.2 FAL給File System的函式介面
DSK_Init:
這裡會先去initial FMD,以便取得Flash基本資料。之後再去initial FAL,以取得對Flash操作的interface,還有建立Translation Table。
DSK_Deinit:Free FAL object再Free FMD
DSK_Open:這裡並未implement只有debug message。
DSK_Close:這裡並未implement只有debug message,return TRUE。
DSK_Read:Not Used。
DSK_Write:Not Used。
DSK_Seek:Not support。
DSK_PowerDown:直接Call FMD的PowerDown function。
DSK_PowerUp:直接Call FMD的PowerUp function。
DSK_IOControl:
對Flash的操作皆是透過這支Function。先根據進來的Control Code再去做相應的檢查;例如對Flash取得其Information(GetInfo),要確認傳進來的Buffer以及Size是否正確,無誤之後再去做相應的動作。
因此上述的Open、Close、Read、Write皆是由此Function做處理。
8. 與系統註冊一個Device
8.1 FAL、FMD
在 WinCE 裡的Flash driven實作是由FAL及FMD所組成(Figure 1),FAL以Library的型式,它對外的interface其Prefix為DSK_開頭(7.2章有介紹),而FMD是Dynamic Link的型式,其Prefix則是FMD_開頭(7.1章有介紹)。要在WinCE上需要MSflash.lib來趨動,這會需要有fal.lib及 fmd.dll,才可以在WinCE裡使用這個Flash。FAL也要有一個FMD才可以在WinCE上Run一個Device,否則在WinCE而言,也只是提供了一個Interface而沒有可使用的Device。
FAL是由下列File組成的(在此僅列.Cpp)
1. Compactor – 提供Compactor function
2. Fal – Build Mapping Table以及對FMD Layer下Read、Write、Compactor、Format的動作,實際對Flash做Read、Write、Erase還是FMD Layer(架構請見Figure 1)
3. falmain – 提供給File System的Interface
4. log2physmap – Mapping Table之Update及取得Physical Sector Address。
5. sectormgr – 對Sector之管理,諸如對Free-List及Dirty-List之處理(管理),以及提供對Sector操作的Function
以下做的動作是將系統中FAL(Library)從系統中抽出來,再加上修改過的Flash FMD(Driver),將其在Simulator上得以被註冊顯示在WinCE的環境裡。
8.2如何與系統註冊一個Storage
Step 1.
在建好一個OSDesign時,請先Build整個環境(約二十至四十分鐘不等),若是先做下面的動作再一起Build,會出現Error。
Step 2.
加入Project在your clone emulator (這裡是Clone自Device Emulator : ARMV4I。Clone name : ARVV4IBase)
下的SRC->Drivers (path : C:/WINCE600/PLATFORM/ARMV4IBase/SRC/DRIVERS)
以下是顯示在Visual Studio的情況。
這裡要改下列的數個File
Dirs:C:/WINCE600/PLATFORM/your clone emulator/SRC/DRIVES裡,加入新加的Project name(Driver name)。
Example:(# -- 注解)
# @CESYSGEN ENDIF CE_MODULES_SHOWFAL
FALSHOW /
FMDSHOW /
Platfrom.bib:其目的是要定義那些是要包在OS的Image內,因此要加入新的FMD。
Example:
IF BSP_FMDSHOW1
FMDSHOW1.dll $(_FLATRELEASEDIR)/FMDSHOW1.dll NK SHK
ENDIF
Platfrom.reg:定義冷開機時系統啟始的Registry Key及相關數值,值得一提的是,Storage的IClass是可以重複的。
Example:
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/FMDSHOW1]
"Prefix"="DSK"
"Dll"="FMDSHOW1.dll"
"IClass"=multi_sz:"{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"
之後若是修改Platfrom.reg裡的值只需要Build->Advanced BuildCommands->Build Current BSP and Subprojects即可
Source:是存要Link的library或是會使用到的library,這個是每個Project都會有的。
Catalog Item:如此建的FMD才可以在Catalog Items View下的Device Drivers裡看到。
Step 3. 以下的選項記得要選起來
Step 4. 上面的動作都做了之後,選取你新建的FMD再整個Project Build過。再來,便是Attach Device之後就可以看到Win CE emulator的畫面,點取紅色方框的My Device
Step 5.點取紅色方框的Control Panel
Step6.點取紅色方框的Storage Manger
Step 7. 以下便是新加的FMD。
按下Partitions旁的New Button
會出現下列視窗,取一個名字按右上方的OK
會回到上一個畫面,選Properties
Step 8. 先選Dismount。
其它的Button會Enable
選Format Button
Step 9. Choose Start Button
之後會跳一個訊問Message Box,選確定便會開始Format。完成後按下OK
Step 10. 回到Partition Properties的畫面,選Mount Button,按下右上方的OK
回到Storage Properties畫面後,按下右上方的OK
Step 11. 回到My Device畫面就可以看到產生了一個新的Storage。
以上便是去Create一個新的Flash Device的簡單流程。
上面这篇分析很清晰,不过比较零散,大致的类的练习随意画了个图:
一个FAL实例针对一个Region区域,有些概念需要清楚,Wince的Sector是底层返回的页的大小,不要混淆。
针对大的Nand Flash FAL+FMD绝对不是好的实现方式,上电建立映射表的过程太慢,如果将每个页信息放入一个集中的地方,又难免导致该处使用过度。
使用MDD+PDD可能会更好,还没实验,之后做对比。