EDA365电子论坛网

标题: Linux MTD系统剖析 [打印本页]

作者: mutougeda    时间: 2020-4-20 09:41
标题: Linux MTD系统剖析
MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。0 A' R7 F+ ^3 ]& c+ {% @4 W0 V) {

" V5 b# c$ `6 D/ r9 X) `& p
9 |6 s8 C+ _( S' L' a* T% Q" K/ Y$ G2 l7 |
如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。
; ]: X$ h6 H/ @! k
4 `, C. {" `6 x1 H6 }Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。
8 e% {. S5 R  F6 ?& M3 \5 }) C6 N1 h
6 f2 }8 b/ S5 k! J3 v+ |9 {MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c:  MTD原始设备接口相关实现,mtdpart.c :  MTD分区接口相关实现。' R3 x; F. A$ o" X: `# O3 W5 O
! V7 t0 L" G$ D! r8 j: I$ O  S4 K
MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c :  MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。0 R5 A3 K" a) W0 c! H
& k/ E$ G. w/ _) i: b
设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备
& g* W; a3 x$ p4 f* H; P& c' H+ L; X
MTD数据结构:
: d; V* s' W* @- G, b5 b6 y  x1 o- T3 s) R0 F
1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:7 B$ E- O3 w2 W. T
8 [: x1 v& z- \, w7 [
1 P9 y6 i, b) ]0 D3 D/ [. V0 M1 V/ i
struct mtd_info *mtd_table[MAX_MTD_DEVICES];2 ?- L5 k& _: Q$ ^" _
2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。
$ C$ o/ F" u8 I; M
, Z* c! G% ^6 |* Sstruct mtd_part {
/ T# W6 o9 ?1 b        struct mtd_info mtd;            /* 分区信息, 大部分由master决定 */
+ o' I4 \+ j0 J6 Q3 u& e3 j        struct mtd_info *master;        /* 分区的主分区 */
8 V# p. |% h: c. M' j! R; g        uint64_t offset;                        /* 分区的偏移地址 */( k8 S4 H4 |$ q  U
        int index;                                        /* 分区号 (Linux3.0后不存在该字段) */
8 R$ k: j* @2 v) d7 c$ {' V        struct list_head list;                /* 将mtd_part链成一个链表mtd_partitons */
4 J- y3 H4 g* {; e        int registered;2 B+ r9 d+ {& B+ I- W# d* x
};) I' A- \% B& z! ?* I  l! n9 s; H, o" H
mtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。
8 g# z4 Q$ j4 P5 Q+ C5 B4 D2 C# m6 m2 R9 O) a
struct mtd_info {
5 P, d9 Y7 T; F7 L  Y: Q) L. i  s# `" C        u_char type;             /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */
  A& A5 g" B. R9 i& w1 a' i        uint32_t flags;             /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */
% L2 J$ m, w1 w  u- h+ J" O        uint64_t size;             /* mtd设备的大小 */4 P: l3 c7 y: j8 o9 a7 s
        uint32_t erasesize;         /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */
2 m1 I3 T3 n3 N" f* _5 x; J# z        uint32_t writesize;         /* 写大小, 对于norFlash是字节,对nandFlash为一页 */
6 r1 c! E+ ~; t        uint32_t oobsize;    /* OOB字节数 */# E) ^5 a6 ^. r( |
        uint32_t oobavail;   /* 可用的OOB字节数 */
: F/ Y8 o4 @# I3 o" E        unsigned int erasesize_shift;        /* 默认为0,不重要 */
* b2 n0 M4 Y; U. X, F        unsigned int writesize_shift;        /* 默认为0,不重要 */) A3 E  h, L* Z6 S, ~
        unsigned int erasesize_mask;        /* 默认为1,不重要 */
+ X5 M& ~9 s; m; Q; I8 o' S( m3 w        unsigned int writesize_mask;        /* 默认为1,不重要 */
, P. W* ^9 y4 k9 b" B! f3 @        const char *name;                                /* 名字,   不重要*/8 e& r, C5 G/ Z! v4 B4 P
        int index;                                                /* 索引号,不重要 */- s( V3 Q; n+ f* O5 B8 V
        int numeraseregions;                        /* 通常为1 */
! ^* _( b, S1 @' a* i        struct mtd_erase_region_info *eraseregions;        /* 可变擦除区域 */+ r5 Z$ k' x( A3 m/ t5 F) M
       
2 ^& D7 ~7 i/ x. M: j$ Z0 ~        void *priv;                /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */
1 D, E6 ?) K( `) Y/ g7 s        struct module *owner;        /* 一般设置为THIS_MODULE */
: b3 z( t: l/ P# o5 @       
2 f( G5 Y7 j. j6 e$ o' N$ c6 i& Y7 f! C        /* 擦除函数 */
1 ?8 o5 s( J' d0 f% T) u  @/ y% {        int (*erase) (struct mtd_info *mtd, struct erase_info *instr);$ x' d+ ~6 S- d5 l: s7 n* p3 r  v6 F" ^
7 E! a. t) H) X! H, u4 C5 L
        /* 读写flash函数 */
" a. d9 ~; [6 V        int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
8 j0 Y4 G( v; J* L. @5 J        int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);3 ?. V- {8 y: a8 J. e. R. x4 v1 u6 H7 J
9 y" z! v' I8 Q' @; x$ `+ k
        /* 带oob读写Flash函数 */
: y9 v# ~6 v" a& j) b6 ~. A# l8 G        int (*read_oob) (struct mtd_info *mtd, loff_t from,/ ?$ }& @: a) o0 D0 ], W* F$ j: v
                         struct mtd_oob_ops *ops);
4 V' a/ ]& J. `$ ]# ~( i        int (*write_oob) (struct mtd_info *mtd, loff_t to,
- i# v: w; K+ Q% }. T: l                         struct mtd_oob_ops *ops);' i, V" O' Z1 G! s/ D/ E7 p

! ]2 G; q' T) O( e: C        int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);4 O2 T. \$ q% {  H1 k6 Z4 f
        int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
* C: O6 I6 Q1 F        int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
" t' V7 P; _. Z. X( r        int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);5 h9 D% F1 ?" R
        int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);! m  z% e$ x1 X% y& J
        int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);8 k1 _* D4 k$ [6 t$ |& \' ]

1 ^2 _% s/ ]% s) D  R  k' W+ I        int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
* t5 C5 J2 ?4 }        int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);: A# l8 {( D: k) Z
        /* Sync */
; O' o6 [3 N" K        void (*sync) (struct mtd_info *mtd);
6 [$ b. V( O' ^$ v  Y4 x/ u! }
7 t# [8 \# g! j        /* Chip-supported device locking */
6 ?) q9 B, U; B  R0 A6 V        int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
" e6 j6 `. q( Q- a$ I        int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
! J; E8 u, l. G* [
1 M  K  ]" ~, r6 _6 g        /* 电源管理函数 */0 L( x0 Z3 }& ?* l. r# C
        int (*suspend) (struct mtd_info *mtd);7 }: `3 h) F; w  [9 T1 ^
        void (*resume) (struct mtd_info *mtd);
. n1 M+ i, ~# \% e; h3 [0 y0 |+ z7 y1 \
        /* 坏块管理函数 */9 L6 q' W1 M  y! U) M
        int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
! {# N5 s0 @. ~& z: F        int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);3 x9 [7 U5 P9 Z+ [: j  |3 v* Y

, i/ Q: n3 D0 x8 u1 R8 B) U        void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);! F! j' e# p  \  }5 P% L% c  o
        unsigned long (*get_unmapped_area) (struct mtd_info *mtd,3 t3 |% R: |* {9 O# \, {. L6 K
                                            unsigned long len,
% Q' D! H' t% \" f! F+ T                                            unsigned long offset,8 Q& r5 l  _& B: W
                                            unsigned long flags);
( f% e; x' i  g* L+ ~        struct backing_dev_info *backing_dev_info;
* [+ o5 f& G6 A! c        struct notifier_block reboot_notifier;  /* default mode before reboot */! S, i2 _( G9 W; |

9 M4 _( `6 Z( i9 Y( s+ X: X3 G* {, \        /* ECC status information */
# k- E* H( z8 k. n- K        struct mtd_ecc_stats ecc_stats;
5 j7 q* a) B+ N) X& A        int subpage_sft;
: W' A  q% \& k( F8 Y        struct device dev;+ j6 N* [, i1 D* w' o' T2 e: S$ \
        int usecount;
1 _! g* w1 k2 ~8 h" I2 f5 v! r        int (*get_device) (struct mtd_info *mtd);
: Z& K  O8 Y- S3 F        void (*put_device) (struct mtd_info *mtd);, d' a/ i# t( l) V/ ~3 d
};) _1 ~2 F* U& W5 \1 E2 F
mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。# u: |& N0 z/ Z! E
如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。
  z+ c' p+ I9 j$ a7 X4 t9 C  j, u0 D! S) M

3 @' a; q8 \  p+ _int add_mtd_device(struct mtd_info *mtd)
3 Z8 Y' ^9 L/ u4 p$ Eint del_mtd_device (struct mtd_info *mtd)
4 ~) w( r$ s, h; e* t$ g7 [如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。& b. N7 [* W% c- e6 ]
int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)* b* K, T6 h0 ?( |3 ^
int del_mtd_partitions(struct mtd_info *master)
! M% F5 ^) J$ M: q" N! p2 U) h其中mtd_partition结构体表示分区的信息4 \: _2 ]8 _4 z% @. [  _$ Z2 L* R9 `
struct mtd_partition {3 m2 Y$ Q# a% u0 c
        char *name;                                /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */
# r  p( ^0 T1 _+ r! w( l4 F! d        uint64_t size;                        /* 分区大小 */$ a; `7 k3 [$ g6 x8 q
        uint64_t offset;                /* 分区偏移值 */# t+ F! A; u% V9 m- p9 h
        uint32_t mask_flags;        /* 掩码标识,不重要 */
+ \: J6 j5 K# G; A, s- o, f        struct nand_ecclayout *ecclayout;        /* OOB布局 */: b3 W  E! h3 h/ \& H
        struct mtd_info **mtdp;                /* pointer to store the MTD object */
8 b# h6 Y3 ~& A( h$ o};
5 ]/ g) K: c0 ]- o( A其中nand_ecclayout结构体:
9 m2 d6 m3 a" G* Sstruct nand_ecclayout {
  ?4 b1 v  J" s        __u32 eccbytes;                /* ECC字节数 */
0 M$ A; B$ s& e$ t# H- L0 }        __u32 eccpos[64];        /* ECC校验码在OOB区域存放位置 */% u' m1 h0 \) Q. A! F0 D" e( Y
        __u32 oobavail;                / q2 O3 N9 h" D$ f) S2 m
        /* 除了ECC校验码之外可用的OOB字节数 */! l; x9 W$ R2 Q" s% n+ `
        struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
5 G& y, k- I' B};  x7 O+ s- G/ `& `8 \3 t! O
关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。
' S/ }5 x" u( f" b) uMTD设备层:
% G: x/ p6 z: W  u* y$ }% y8 M* X: dmtd字符设备接口:; b2 i$ c) ^$ a8 l) ?* S( m& [

! S8 S3 m! G' B& A" j, L# ^! j/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) ( [/ A  x: Y$ A% Q

% n; D. i) A# ]5 h5 x5 tmtd块设备接口:
* e+ j3 ^) @; F2 v+ f& e: L1 t  v! y: V' y
/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。5 U0 a3 n& k5 o$ o. k* M
MTD硬件驱动层:$ L5 I* T/ E& {3 l' H; q' \
7 |( _4 x& e/ |0 _7 l
Linux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。( t' x- k* h. ^& v7 q* b
1 w5 o' N6 \% m
MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。: _7 D/ n: W* R2 I

/ T! ^4 ]8 B; k1 [4 ^4 t! R- k# m2 ^
struct nand_chip {. b, w0 `7 }+ ~" b! s
        void  __iomem        *IO_ADDR_R;                /* 读8位I/O线地址 */, b5 P; E: h. s7 Q
        void  __iomem        *IO_ADDR_W;                /* 写8位I/O线地址 */
! S2 m/ ?: S# `1 d# ^$ D% Y
7 x5 Q' M) S% X* D; B6 |! m        /* 从芯片中读一个字节 */
+ g9 E. U& |7 b; K4 }        uint8_t        (*read_byte)(struct mtd_info *mtd);               
7 t! ~" k) q; N4 }' @/ \, H        /* 从芯片中读一个字 */
' F7 j0 l$ D& N! L3 q; a        u16                (*read_word)(struct mtd_info *mtd);                & e2 A) C# H% D$ f
        /* 将缓冲区内容写入芯片 */& S* H# x) q, X* G0 b' w
        void        (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);       
. T- w2 q$ Z# A3 T  S" u! J' D" J        /* 读芯片读取内容至缓冲区/ */6 t; j$ \. t# ?- a
        void        (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
  z* a% ~; [  I, q" O! q" n5 N        /* 验证芯片和写入缓冲区中的数据 */+ h# X: ^! Q% p6 h8 A9 G% T; u
        int                (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
$ ?. Q. j% `* r% G        /* 选中芯片 */: t( q7 q1 h. K: L) `
        void        (*select_chip)(struct mtd_info *mtd, int chip);' [  C9 m' Q- R
        /* 检测是否有坏块 */
) k+ J" ?# \+ ^! u6 T8 y& p        int                (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);3 [2 {1 W: k$ A
        /* 标记坏块 */4 y# [# |, u& ]' P  V0 ]7 ]
        int                (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
4 M5 p( W7 D' u; ^        /* 命令、地址、数据控制函数 */
* y3 Y1 W9 w! p4 N5 v! W        void        (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);9 H) ?( ?9 V% [0 f
        /* 设备是否就绪 */
$ C1 T+ \) ]; C# H8 ]        int                (*dev_ready)(struct mtd_info *mtd);$ D/ H9 L4 l( G# Z- M) y2 i- H0 f
        /* 实现命令发送 */$ r; O$ B4 s1 V6 p
        void        (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
* T7 O$ Y0 Q% Q0 s& u" F        int                (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);/ s+ E) |% n0 I# P  S+ @3 V
        /* 擦除命令的处理 */6 ~; H' k+ V5 ~9 i  M: P
        void        (*erase_cmd)(struct mtd_info *mtd, int page);/ \( Y% y$ F2 p, @( b/ \4 {
        /* 扫描坏块 */
1 A- @$ z3 n  _2 j  }  s        int                (*scan_bbt)(struct mtd_info *mtd);
5 _$ Z: p5 |2 z2 t% T  ~# T; f        int                (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
' E3 E; X- Y5 k& z/ c) o        /* 写一页 */" @2 h# W9 `- A% Q
        int                (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
& h* O1 C% ^! F( _% R                                      const uint8_t *buf, int page, int cached, int raw);: `4 n# z. y9 i/ k% D1 ^
/ S: L* l1 |3 m' _. A4 K: o
        int                chip_delay;                        /* 由板决定的延迟时间 */; @7 V8 f  F9 f( u! u
        /* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */# v0 z& H, S& x) T( ?* W
        unsigned int        options;        0 C9 k0 r% [9 Q% v0 _
& X' U, S/ N3 Q
        /* 用位表示的NAND芯片的page大小,如某片NAND芯片
) e3 K+ U$ _- y7 j1 k         * 的一个page有512个字节,那么page_shift就是9
1 R' }4 A/ F! r7 O- v; k/ ?* L         */2 M* [; `* B4 u  x) r% t  v: i& u
        int                 page_shift;
" i' w& G8 z! R+ t5 Q9 G8 H        /* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可2 U8 J# e" m2 J' m
         * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14
+ I& }2 D  K: `1 x         */
$ y$ {7 O( b: A+ w! }" {9 u& L        int                 phys_erase_shift;
: }1 b  v( V7 v! w( \        /* 用位表示的bad block table的大小,通常一个bbt占用一个block,4 O+ y$ a5 o( G8 |& ^
         * 所以bbt_erase_shift通常与phys_erase_shift相等
, K( v# A% ?. y6 `; U          */
3 a0 V: M. B1 x& [4 `. W* r8 k        int                 bbt_erase_shift;; F0 v2 _/ |) d" t/ L
        /* 用位表示的NAND芯片的容量 */' S5 U5 g* x! ^5 b. Y2 @
        int                 chip_shift;: _; q1 j9 `+ C) e
        /* NADN FLASH芯片的数量 */
# Y* X6 z6 p* f9 w        int                 numchips;
, F0 w3 \0 c1 h3 V. m: Z3 Z4 c6 |        /* NAND芯片的大小 */$ j" j6 `2 k8 c5 N+ }, U9 `0 a
        uint64_t chipsize;0 e. [2 B2 O+ L- |
        int                 pagemask;
  U, o/ B* ^( v% b        int                 pagebuf;2 _% P7 C: O8 N$ P  {0 e
        int                 subpagesize;
' L9 i  N; G$ [  x% A        uint8_t         cellinfo;+ e) c' ?# ?/ K/ q: i9 P
        int                 badblockpos;
9 c# [4 F- F. y) v# ], A        nand_state_t        state;
8 L- n- K5 O$ D: o        uint8_t                *oob_poi;5 m1 _3 `9 H1 }5 l
        struct nand_hw_control  *controller;  `9 n! q6 i2 r  t
        struct nand_ecclayout        *ecclayout;        /* ECC布局 */
: w$ U) |- V2 c: M4 h5 f        ( R* d0 a' y4 s3 R6 N: v0 g
        struct nand_ecc_ctrl ecc;        /* ECC校验结构体,里面有大量的函数进行ECC校验 */
4 D: h  U' ?  V8 z0 I        struct nand_buffers *buffers;! r: \  e8 x1 [! f7 y
        struct nand_hw_control hwcontrol;0 }3 _; [' t! I0 X9 a, ]6 r9 i
        struct mtd_oob_ops ops;
  W/ A, m' V- m, |; B) R        uint8_t                *bbt;
4 }0 x8 }3 |0 h        struct nand_bbt_descr        *bbt_td;% B- {( F/ A1 K# r4 u) Q" v
        struct nand_bbt_descr        *bbt_md;
- }! B( g" }5 H/ A' h" [        struct nand_bbt_descr        *badblock_pattern;
9 K$ m# x/ u+ z        void                *priv;
, v! T/ J% I! `3 e5 P  y};
9 p/ `# r4 o3 j& ~2 ~
! ?0 t+ q; f' d0 o最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。0 I  I6 B" b/ h& x! d

9 }$ h& K9 h. Z3 Z1 x: m/ t + l3 W- p4 g/ \8 }! l. i& V
& R. i- }; Y" y

2 k( X: t  x7 r5 _
作者: yin123    时间: 2020-4-20 13:27
Linux MTD系统剖析
作者: NNNei256    时间: 2020-4-21 14:23
Linux MTD系统剖析




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2