天天看點

Linux MTD系統剖析【轉】

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。

MTD,Memory Technology Device即記憶體技術裝置,在Linux核心中,引入MTD層為NOR FLASH和NAND FLASH裝置提供統一接口。MTD将檔案系統與底層FLASH存儲器進行了隔離。

Linux MTD系統剖析【轉】

如上圖所示,MTD裝置通常可分為四層,從上到下依次是:裝置節點、MTD裝置層、MTD原始裝置層、硬體驅動層。

Flash硬體驅動層:Flash硬體驅動層負責對Flash硬體的讀、寫和擦除操作。MTD裝置的Nand Flash晶片的驅動則drivers/mtd/nand/子目錄下,Nor Flash晶片驅動位于drivers/mtd/chips/子目錄下。

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字元裝置和塊裝置 

MTD資料結構:

1.Linux核心使用mtd_info結構體表示MTD原始裝置,這其中定義了大量關于MTD的資料和操作函數(後面将會看到),所有的mtd_info結構體存放在mtd_table結構體資料裡。在/drivers/mtd/mtdcore.c裡:

Linux MTD系統剖析【轉】

struct mtd_info *mtd_table[MAX_MTD_DEVICES];  

2.Linux核心使用mtd_part結構體表示分區,其中mtd_info結構體成員用于描述該分區,大部分成員由其主分區mtd_part->master決定,各種函數也指向主分區的相應函數。

Linux MTD系統剖析【轉】

struct mtd_part {  

    struct mtd_info mtd;        /* 分區資訊, 大部分由master決定 */  

    struct mtd_info *master;    /* 分區的主分區 */  

    uint64_t offset;            /* 分區的偏移位址 */  

    int index;                  /* 分區号 (Linux3.0後不存在該字段) */  

    struct list_head list;      /* 将mtd_part鍊成一個連結清單mtd_partitons */  

    int registered;  

};  

mtd_info結構體主要成員,為了便于觀察,将重要的資料放在前面,不大重要的編寫在後面。

Linux MTD系統剖析【轉】

struct mtd_info {  

    u_char type;         /* MTD類型,包括MTD_NORFLASH,MTD_NANDFLASH等(可參考mtd-abi.h) */  

    uint32_t flags;      /* MTD屬性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可參考mtd-abi.h) */  

    uint64_t size;       /* mtd裝置的大小 */  

    uint32_t erasesize;  /* MTD裝置的擦除單元大小,對于NandFlash來說就是Block的大小 */  

    uint32_t writesize;  /* 寫大小, 對于norFlash是位元組,對nandFlash為一頁 */  

    uint32_t oobsize;    /* OOB位元組數 */  

    uint32_t oobavail;   /* 可用的OOB位元組數 */  

    unsigned int erasesize_shift;   /* 預設為0,不重要 */  

    unsigned int writesize_shift;   /* 預設為0,不重要 */  

    unsigned int erasesize_mask;    /* 預設為1,不重要 */  

    unsigned int writesize_mask;    /* 預設為1,不重要 */  

    const char *name;               /* 名字,   不重要*/  

    int index;                      /* 索引号,不重要 */  

    int numeraseregions;            /* 通常為1 */  

    struct mtd_erase_region_info *eraseregions; /* 可變擦除區域 */  

    void *priv;     /* 裝置私有資料指針,對于NandFlash來說指nand_chip結構體 */  

    struct module *owner;   /* 一般設定為THIS_MODULE */  

    /* 擦除函數 */  

    int (*erase) (struct mtd_info *mtd, struct erase_info *instr);  

    /* 讀寫flash函數 */  

    int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  

    int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  

    /* 帶oob讀寫Flash函數 */  

    int (*read_oob) (struct mtd_info *mtd, loff_t from,  

             struct mtd_oob_ops *ops);  

    int (*write_oob) (struct mtd_info *mtd, loff_t to,  

    int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);  

    int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  

    int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);  

    int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  

    int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  

    int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);  

    int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);  

    int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  

    /* Sync */  

    void (*sync) (struct mtd_info *mtd);  

    /* Chip-supported device locking */  

    int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);  

    int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);  

    /* 電源管理函數 */  

    int (*suspend) (struct mtd_info *mtd);  

    void (*resume) (struct mtd_info *mtd);  

    /* 壞塊管理函數 */  

    int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);  

    int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);  

    void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);  

    unsigned long (*get_unmapped_area) (struct mtd_info *mtd,  

                        unsigned long len,  

                        unsigned long offset,  

                        unsigned long flags);  

    struct backing_dev_info *backing_dev_info;  

    struct notifier_block reboot_notifier;  /* default mode before reboot */  

    /* ECC status information */  

    struct mtd_ecc_stats ecc_stats;  

    int subpage_sft;  

    struct device dev;  

    int usecount;  

    int (*get_device) (struct mtd_info *mtd);  

    void (*put_device) (struct mtd_info *mtd);  

mtd_info結構體中的read()、write()、read_oob()、write_oob()、erase()是MTD裝置驅動要實作的主要函數,幸運的是Linux大牛已經幫我們實作了一套适合大部分FLASH裝置的mtd_info成員函數。

如果MTD裝置隻有一個分區,那麼使用下面兩個函數注冊和登出MTD裝置。

Linux MTD系統剖析【轉】

int add_mtd_device(struct mtd_info *mtd)  

int del_mtd_device (struct mtd_info *mtd)  

如果MTD裝置存在其他分區,那麼使用下面兩個函數注冊和登出MTD裝置。

Linux MTD系統剖析【轉】

int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)  

int del_mtd_partitions(struct mtd_info *master)  

其中mtd_partition結構體表示分區的資訊

Linux MTD系統剖析【轉】

struct mtd_partition {  

    char *name;             /* 分區名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */  

    uint64_t size;          /* 分區大小 */  

    uint64_t offset;        /* 分區偏移值 */  

    uint32_t mask_flags;    /* 掩碼辨別,不重要 */  

    struct nand_ecclayout *ecclayout;   /* OOB布局 */  

    struct mtd_info **mtdp;     /* pointer to store the MTD object */  

其中nand_ecclayout結構體:  

struct nand_ecclayout {  

    __u32 eccbytes;     /* ECC位元組數 */  

    __u32 eccpos[64];   /* ECC校驗碼在OOB區域存放位置 */  

    __u32 oobavail;       

    /* 除了ECC校驗碼之外可用的OOB位元組數 */  

    struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];  

關于nand_ecclayout結構體執行個體,更多可參考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64執行個體。

MTD裝置層:

mtd字元裝置接口:

/drivers/mtd/mtdchar.c檔案實作了MTD字元裝置接口,通過它,可以直接通路Flash裝置,與前面的字元驅動一樣,通過file_operations結構體裡面的open()、read()、write()、ioctl()可以讀寫Flash,通過一系列IOCTL 指令可以擷取Flash 裝置資訊、擦除Flash、讀寫NAND 的OOB、擷取OOB layout 及檢查NAND 壞塊等(MEMGETINFO、MEMERASE、MEMREADOOB、MEMWRITEOOB、MEMGETBADBLOCK IOCRL) 

mtd塊裝置接口:

/drivers/mtd/mtdblock.c檔案實作了MTD塊裝置接口,主要原理是将Flash的erase block 中的資料在記憶體中建立映射,然後對其進行修改,最後擦除Flash 上的block,将記憶體中的映射塊寫入Flash 塊。整個過程被稱為read/modify/erase/rewrite 周期。 但是,這樣做是不安全的,當下列操作序列發生時,read/modify/erase/poweroff,就會丢失這個block 塊的資料。

MTD硬體驅動層:

Linux核心再MTD層下實作了通用的NAND驅動(/driver/mtd/nand/nand_base.c),是以晶片級的NAND驅動不再需要實作mtd_info結構體中的read()、write()、read_oob()、write_oob()等成員函數。

MTD使用nand_chip來表示一個NAND FLASH晶片, 該結構體包含了關于Nand Flash的位址資訊,讀寫方法,ECC模式,硬體控制等一系列底層機制。

Linux MTD系統剖析【轉】

struct nand_chip {  

    void  __iomem   *IO_ADDR_R;     /* 讀8位I/O線位址 */  

    void  __iomem   *IO_ADDR_W;     /* 寫8位I/O線位址 */  

    /* 從晶片中讀一個位元組 */  

    uint8_t (*read_byte)(struct mtd_info *mtd);       

    /* 從晶片中讀一個字 */  

    u16     (*read_word)(struct mtd_info *mtd);       

    /* 将緩沖區内容寫入晶片 */  

    void    (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);      

    /* 讀晶片讀取内容至緩沖區/ */  

    void    (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);  

    /* 驗證晶片和寫入緩沖區中的資料 */  

    int     (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);  

    /* 選中晶片 */  

    void    (*select_chip)(struct mtd_info *mtd, int chip);  

    /* 檢測是否有壞塊 */  

    int     (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);  

    /* 标記壞塊 */  

    int     (*block_markbad)(struct mtd_info *mtd, loff_t ofs);  

    /* 指令、位址、資料控制函數 */  

    void    (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);  

    /* 裝置是否就緒 */  

    int     (*dev_ready)(struct mtd_info *mtd);  

    /* 實作指令發送 */  

    void    (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);  

    int     (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);  

    /* 擦除指令的處理 */  

    void    (*erase_cmd)(struct mtd_info *mtd, int page);  

    /* 掃描壞塊 */  

    int     (*scan_bbt)(struct mtd_info *mtd);  

    int     (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);  

    /* 寫一頁 */  

    int     (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,  

                      const uint8_t *buf, int page, int cached, int raw);  

    int     chip_delay;         /* 由闆決定的延遲時間 */  

    /* 與具體的NAND晶片相關的一些選項,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */  

    unsigned int    options;      

    /* 用位表示的NAND晶片的page大小,如某片NAND晶片 

     * 的一個page有512個位元組,那麼page_shift就是9  

     */  

    int      page_shift;  

    /* 用位表示的NAND晶片的每次可擦除的大小,如某片NAND晶片每次可 

     * 擦除16K位元組(通常就是一個block的大小),那麼phys_erase_shift就是14 

    int      phys_erase_shift;  

    /* 用位表示的bad block table的大小,通常一個bbt占用一個block, 

     * 是以bbt_erase_shift通常與phys_erase_shift相等 

    int      bbt_erase_shift;  

    /* 用位表示的NAND晶片的容量 */  

    int      chip_shift;  

    /* NADN FLASH晶片的數量 */  

    int      numchips;  

    /* NAND晶片的大小 */  

    uint64_t chipsize;  

    int      pagemask;  

    int      pagebuf;  

    int      subpagesize;  

    uint8_t  cellinfo;  

    int      badblockpos;  

    nand_state_t    state;  

    uint8_t     *oob_poi;  

    struct nand_hw_control  *controller;  

    struct nand_ecclayout   *ecclayout; /* ECC布局 */  

    struct nand_ecc_ctrl ecc;   /* ECC校驗結構體,裡面有大量的函數進行ECC校驗 */  

    struct nand_buffers *buffers;  

    struct nand_hw_control hwcontrol;  

    struct mtd_oob_ops ops;  

    uint8_t     *bbt;  

    struct nand_bbt_descr   *bbt_td;  

    struct nand_bbt_descr   *bbt_md;  

    struct nand_bbt_descr   *badblock_pattern;  

    void        *priv;  

最後,我們來用圖表的形式來總結一下,MTD裝置層、MTD原始裝置層、FLASH硬體驅動層之間的聯系。

Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】
Linux MTD系統剖析【轉】

【新浪微網誌】 張昺華--sky

【twitter】 @sky2030_

【facebook】 張昺華 zhangbinghua

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.

繼續閱讀