1. 介紹
MTD,即Memory Technology Device,值得是內存技術設備
字符設備和塊設備的區別在於前者只能被順序讀寫,后者可以隨機訪問;同時,兩者讀寫數據的基本單元不同
字符設備: 以字節為基本單位,在Linux中,字符設備實現的比較簡單,不需要緩沖區即可直接讀寫,內核例程和用戶態API一一對應,用戶層的Read函數直接對應了內核中的Read例程,這種映射關系由字符設備的file_operations維護
塊設備: 以塊為單位接受輸入和返回輸出,對這種設備的讀寫是按塊進行的,其接口相對於字符設備復雜,沒有直接到塊設備層,而是直接到文件系統層,然后再由文件系統層發起讀寫請求
由於塊設備的I/O性能與CPU相比很差,因此,塊設備的數據流往往會引入文件系統的cache機制
注意:MTD設備既非塊設備也不是字符設備,但可以同時提供字符設備和塊設備接口來操作
2. 概述
Linux中MTD的所有源碼位於/drivers/mtd子目錄下
MTD設備通常可分為四層,從上到下依次是:設備節點、MTD設備層、MTD原始設備層和硬件驅動層
硬件驅動層負責在init時驅動Flash硬件並建立從具體設備到MTD原始設備映射關系
TIP: 映射關系通常包括 分區信息、I/O映射及特定函數的映射
drivers/mtd/chips : CFI/JEDEC接口通用驅動
drivers/mtd/nand : NAND通用驅動和部分底層驅動程序
drivers/mtd/maps : NOR Flash映射關系相關函數
drivers/mtd/devices : NOR Flash底層驅動
MTD原始設備,用於描述MTD原始設備的數據結構是mtd_info,它定義了大量的關於MTD的數據和操作函數。
mtdcore.c : MTD原始設備接口相關實現
mtdpart.c : MTD分區接口相關實現
MTD設備層,基於MTD原始設備,linux系統可以定義出MTD的塊設備(主設備號31)和字符設備(設備號90)。
mtdchar.c : MTD字符設備接口相關實現
mtdblock.c : MTD塊設備接口相關實現
設備節點,通過mknod在/dev子目錄下建立MTD塊設備節點(主設備號為31)和MTD字符設備節點(主設備號為90)
通過訪問此設備節點即可訪問MTD字符設備和塊設備
3. 數據結構
MTD重要的數據結構包括mtd_info、mtd_part、mtd_partition、map_info、nand_chip、nand_ecclayout
mtd_info表示mtd原始設備, 所有mtd_info結構體被存放在mtd_info數組mtd_table中
成員 | 作用 |
type | mtd類型, 包括MTD_NORFLASH,MTD_NANDFLASH等(See mtd-abi.h) |
flags | 標志位, MTD_WRITEABLE,MTD_NO_ERASE等(See mtd-abi.h) |
size | mtd設備的大小 |
erasesize | 主要的擦除大小, 即Flash的塊大小 (tip: mtd設備可能有多個erasesize) |
writesize | 寫大小, 對於norFlash是字節,對nandFlash為一頁 |
oobsize | 每塊oob數據量, eg 16 |
oobavail | |
name | 命名 |
index | |
ecclayout | nand_ecclayout結構體指針, 表示的是ecc布局,可參考硬件手冊的OOB中ecc布局 |
numeraseregions | 可變擦除區域的數目, 通常為1 |
eraseregions | mtd_erase_region_info結構體指針, 可變擦除區域 |
erase | 擦除Flash函數 |
read/write | 讀寫Flash函數 |
read_oob/write_oob | 帶oob讀寫Flash函數 |
suspend/resume | Power Management functions |
priv | 私有數據, cfi接口flash指向map_info結構, 或指向自定義flash相關結構體 |
mtd_part表示MTD分區,其中包含了mtd_info,每一個分區都是被看成一個MTD原始設備
成員 | 作用 |
mtd | 分區信息, 大部分由master決定 |
master | 分區的主分區 |
offset | 分區的偏移地址 |
index | 分區號 (3.0后不存在該字段) |
list | 將mtd_part鏈成一個鏈表mtd_partitons |
mtd_partition的主要數據結構
成員 | 作用 |
name | |
size | |
offset | |
mask_flags | |
ecclayout | |
mtdp |
map_info的主要數據結構
成員 | 作用 |
name | 名稱 |
size | 大小 |
phys | 物理地址 |
bankwidth | 總線寬度(in octets) |
virt | 虛擬地址,通常通過ioremap將物理地址進行映射得到 |
read/copy_from/write/copy_to | 讀寫函數 |
map_priv_1/map_priv_2 | 驅動可用的私有數據 |
nand_chip的主要數據結構
成員 | 作用 |
IO_ADDR_R/IO_ADDR_W | 讀/寫8根io線的地址 |
read_byte/read_word | 從芯片讀一個字節/字 |
read_buf/write_buf | 讀芯片讀取內容至緩沖區/將緩沖區內容寫入芯片 |
verify_buf | |
select_chip | |
block_bad | 檢查是否壞塊 |
block_markbad | 標識壞塊 |
cmd_ctrl | 硬件相關控制函數 |
init_size | |
dev_ready | |
cmdfunc | 命令處理函數 |
waitfunc | |
erase_cmd | 擦除命令 |
scan_bbt | 掃描壞塊 |
errstat | |
write_page | |
options | 與具體的NAND 芯片相關的選項, 如NAND_USE_FLASH_BBT等(nand.h) |
page_shift | |
ecclayout | nand_ecclayout類型結構體, ECC布局信息 |
ecc | nand_ecc_ctrl類型結構體, ECC控制結構 |
nand_ecclayout的主要數據結構
成員 | 作用 |
eccbytes | ecc的字節數(For 512B-per-page, eccbytes is 3) |
eccpos | ecc數據在oob中的位置 |
oobavail | oob中可用的字節數, MTD 會根據其它三個變量自動計算得到 |
oobfree | nand_oobfree類型結構體, 顯示定義空閑的oob 字節 |
4. MTD相關層實現
4.1 MTD設備層
mtd字符設備接口:
mtdchar.c 實現了字符設備接口,通過它,用戶可以直接操作Flash 設備。
Ø 通過read()、write()系統調用可以讀寫Flash。
Ø 通過一系列IOCTL 命令可以獲取Flash 設備信息、擦除Flash、讀寫NAND 的OOB、獲取OOB layout 及檢查NAND 壞塊等(MEMGETINFO、MEMERASE、MEMREADOOB、MEMWRITEOOB、MEMGETBADBLOCK IOCRL)
TIP: mtd_read和mtd_write直接直接調用mtd_info的read 函數,因此,字符設備接口跳過patition這一層
mtd塊設備接口:
主要原理是將Flash的erase block中的數據在內存中建立映射,然后對其進行修改,最后擦除Flash 上的block,將內存中的映射塊寫入Flash 塊。整個過程被稱為read/modify/erase/rewrite 周期。
但是,這樣做是不安全的,當下列操作序列發生時,read/modify/erase/poweroff,就會丟失這個block 塊的數據。
塊設備模擬驅動按照block 號和偏移量來定位文件,因此在Flash 上除了文件數據,基本沒有額外的控制數據。
4.2 MTD原始設備層
MTD原始設備層
4.3 硬件驅動層
4.3.1 NOR Flash驅動結構
Linux系統實現了針對cfi,jedec等接口的通用NOR Flash驅動
在上述接口驅動基礎上,芯片級驅動較簡單
定義具體內存映射結構體map_info,然后通過接口類型后調用do_map_probe()
以h720x-flash.c為例(位於drivers/mtd/maps)
- 定義map_info結構體, 初始化成員name, size, phys, bankwidth
- 通過ioremap映射成員virt(虛擬內存地址)
- 通過函數simple_map_init初始化map_info成員函數read,write,copy_from,copy_to
- 調用do_map_probe進行cfi接口探測, 返回mtd_info結構體
- 通過parse_mtd_partitions, add_mtd_partitions注冊mtd原始設備
Linux實現了通用NAND驅動(drivers/mtd/nand/nand_base.c)
tip: For more, check 內核中的NAND代碼布局
芯片級驅動需要實現nand_chip結構體
MTD使用nand_chip來表示一個NAND FLASH芯片, 該結構體包含了關於Nand Flash的地址信息,讀寫方法,ECC模式,硬件控制等一系列底層機制。
Ø NAND芯片級初始化
主要有以下幾個步驟:
- 分配nand_chip內存,根據目標板及NAND控制器初始化nand_chip中成員函數(若未初始化則使用nand_base.c中的默認函數),將mtd_info中的priv指向nand_chip(或板相關私有結構),設置ecc模式及處理函數
- 以mtd_info為參數調用nand_scan()探測NAND FLash。
nand_scan()會讀取nand芯片ID,並根據mtd->priv即nand_chip中成員初始化mtd_info
- 若有分區,則以mtd_info和mtd_partition為參數調用add_mtd_partitions()添加分區信息
-
Ø MTD對NAND芯片的讀寫
主要分三部分:
A、struct mtd_info中的讀寫函數,如read,write_oob等,這是MTD原始設備層與FLASH硬件層之間的接口;
B、struct nand_ecc_ctrl中的讀寫函數,如read_page_raw,write_page等,主要用來做一些與ecc有關的操作;
C、struct nand_chip中的讀寫函數,如read_buf,cmdfunc等,與具體的NAND controller相關,就是這部分函數與硬件交互,通常需要我們自己來實現。
tip: nand_chip中的讀寫函數雖然與具體的NAND controller相關,但是MTD也為我們提供了默認的讀寫函數,如果NAND controller比較通用(使用PIO模式),那么對NAND芯片的讀寫與MTD提供的這些函數一致,就不必自己實現這些函數。
上面三部分讀寫函數相互配合完成對NAND芯片的讀寫
首先,MTD上層需要讀寫NAND芯片時,會調用struct mtd_info中的讀寫函數,接着struct mtd_info中的讀寫函數就會調用struct nand_chip或struct nand_ecc_ctrl中的讀寫函數,最后,若調用的是struct nand_ecc_ctrl中的讀寫函數,那么它又會接着調用struct nand_chip中的讀寫函數。
eg: 以讀為例
MTD上層會調用struct mtd_info中的讀page函數,即nand_read函數。
接着nand_read函數會調用struct nand_chip中cmdfunc函數,這個cmdfunc函數與具體的NAND controller相關,它的作用是使NAND controller向NAND 芯片發出讀命令,NAND芯片收到命令后,就會做好准備等待NAND controller下一步的讀取。
接着nand_read函數又會調用struct nand_ecc_ctrl中的read_page函數,而read_page函數又會調用struct nand_chip中read_buf函數,從而真正把NAND芯片中的數據讀取到buffer中(所以這個read_buf的意思其實應該是read into buffer,另外,這個buffer是struct mtd_info中的nand_read函數傳下來的)。
read_buf函數返回后,read_page函數就會對buffer中的數據做一些處理,比如校驗ecc,以及若數據有錯,就根據ecc對數據修正之類的,最后read_page函數返回到nand_read函數中。
對NAND芯片的其它操作,如寫,擦除等,都與讀操作類似
Tranlation Layer
邏輯塊地址(Logical Block Address)對應到Flash存儲器的物理位置,使系統能把Flash當作普通的硬盤一樣處理
FTL主要用於NOR Flash;NFTL用於NAND Flash
閃存轉換層要做下面的操作來完成寫請求:
- 將這個扇區所在擦除塊的數據讀到內存中,放在緩存中
- 將緩存中與這個扇區對應的內容用新的內容替換。
- 對該擦除塊執行擦除操作。
- 將緩沖中的數據寫回該擦除塊。