TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。9 @8 K7 E6 n1 w, U# R X1 l
: |; P: h2 C: Y& v: C9 q
. j% n& l5 v/ C8 {
4 o# d, _9 o) x* l' F8 d) v如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。% E7 d# I" m3 g: a2 ^
# @' L9 r2 }' n, c# ?. p5 h
Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。
& o! u ?! u$ y* z: i5 @ w7 |
# c9 V8 F& [1 ~. D; ?. S+ N0 s% ~MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c: MTD原始设备接口相关实现,mtdpart.c : MTD分区接口相关实现。
: b# m3 _- c2 R: y* A/ I$ Y' t: O
( o0 `0 p1 E& O( R! O3 l; iMTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。
0 u) `5 \# F3 j6 e6 q3 ^0 Q( D: E8 E2 S" }; p
设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备
+ ~& |* q+ m8 V1 o) B( [7 r% R; c' I% ], b1 X- o
MTD数据结构:2 k( s5 X W- z4 E9 z9 Q4 V# @
( P+ }" ?) E9 C8 p) j
1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:
7 e8 I$ s1 b5 L" T$ z& b% {5 H d; K2 ?& V
2 }: @' q/ Q, i) Y6 D
struct mtd_info *mtd_table[MAX_MTD_DEVICES];
, c) h. f% h8 @* c2 `" ~% j2 e2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。; m5 L3 s9 V! b0 I: ^
1 X) W4 p: ]8 l- b4 M" G4 e$ @
struct mtd_part {
! K9 `0 `2 I2 B. e struct mtd_info mtd; /* 分区信息, 大部分由master决定 */& e- S) d7 P' e( h3 T2 a, E
struct mtd_info *master; /* 分区的主分区 */
, y: h5 J( F6 c0 j uint64_t offset; /* 分区的偏移地址 */# G, K$ y1 K% O& t3 x
int index; /* 分区号 (Linux3.0后不存在该字段) */
$ I! a5 f3 [. M- W struct list_head list; /* 将mtd_part链成一个链表mtd_partitons */! l2 n4 S4 _& Y2 X" i. a9 f
int registered;4 K. @' ]8 w, M3 I
};% p4 V ]; Q* M( v5 e' S# D. k' n
mtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。
) \2 u8 f' Y- k5 P' h! k
( W G( S( w2 n. P1 V3 H) Fstruct mtd_info {
7 D% q/ t4 F- X0 k8 s, M u_char type; /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */0 D ~( A$ P r
uint32_t flags; /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */" D; w, M* _. c0 i2 D5 {% T
uint64_t size; /* mtd设备的大小 */: k q) E9 }( o# [
uint32_t erasesize; /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */6 p1 j* X% k; q
uint32_t writesize; /* 写大小, 对于norFlash是字节,对nandFlash为一页 *// J+ `+ y$ c) N* H" p6 `
uint32_t oobsize; /* OOB字节数 */) H2 K$ g' T" W) [& [7 z$ q) b
uint32_t oobavail; /* 可用的OOB字节数 */9 q/ C8 t& o1 @; T) D3 K2 {$ w* f
unsigned int erasesize_shift; /* 默认为0,不重要 */0 u$ U: x* _& a) p3 b h
unsigned int writesize_shift; /* 默认为0,不重要 */9 c8 O' X8 N5 ~2 x
unsigned int erasesize_mask; /* 默认为1,不重要 */$ H+ P# ~) A4 u" }% H9 e, J
unsigned int writesize_mask; /* 默认为1,不重要 */% B; _4 k6 K. A# ], @9 C4 j( ?
const char *name; /* 名字, 不重要*/
5 a' D0 c4 J P8 H9 F) J+ c n int index; /* 索引号,不重要 */
+ Y( W0 O+ k) m; ]5 { int numeraseregions; /* 通常为1 */4 |0 ^5 z0 W9 r. ?* i+ P- _
struct mtd_erase_region_info *eraseregions; /* 可变擦除区域 */
$ T/ I- ~; C9 D( f' l) j$ }
' u1 `0 ~. Y: \' e0 J/ L2 U! U void *priv; /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */* R! A7 C V2 R. {! w
struct module *owner; /* 一般设置为THIS_MODULE */# v* W" y6 |! O$ N
, y9 K j9 l( X4 c' t
/* 擦除函数 */
6 ]" h4 X/ }, E. W! U int (*erase) (struct mtd_info *mtd, struct erase_info *instr);" C" I; U. ^, E. r0 y) e! U2 W L
0 E2 Y6 Z: p4 z! J0 C/ T
/* 读写flash函数 */3 }' h7 S3 b% c( \# W1 l: q" Y6 S& ^$ t
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);4 h" J# N' y5 ~- K/ A
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);1 g. L, h$ w0 p8 a$ b# D
, ?, s7 o5 r9 @' `1 D: k
/* 带oob读写Flash函数 */+ {. G/ h F; E$ G [; ], h
int (*read_oob) (struct mtd_info *mtd, loff_t from,
1 f' F- W( c) L! g struct mtd_oob_ops *ops);
) m9 d' L" o4 w! r3 ? int (*write_oob) (struct mtd_info *mtd, loff_t to,
& R7 O' \8 q' T; o+ }8 P struct mtd_oob_ops *ops);2 Q$ h9 b+ L' H# L& j
8 f9 \' X- g4 w9 h: I+ a
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
+ D3 r- o2 w1 J# T$ s int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
8 v- i/ V/ ~0 P3 X$ ?" J- s) ]5 u- z int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
5 R/ @+ s( L1 C' z" w$ v- G( R7 s int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
( p( b% {' q0 o! w int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
( B! J9 u6 \/ v9 S int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
! N8 k6 ?7 V% v- I; H$ }8 R! f: c) |4 l9 c/ {
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
4 n1 t o. S2 m3 a$ _( d int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);1 ?. N% `/ x9 W! Y% L# A5 y( _! W
/* Sync */0 N8 v( `4 T* s( ^: N0 N0 B
void (*sync) (struct mtd_info *mtd);! b: F. H7 ^7 K" Q) w% a1 t+ F9 s% m
8 P# V6 l! F/ ?& j /* Chip-supported device locking */
; o1 A( ~. T, F& k+ q int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);5 S- x+ b3 L- x& [9 q: a5 n9 t
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
8 X4 j+ D% E0 H7 o0 |$ P6 s& Q8 A! Q6 J4 i- g% L7 }
/* 电源管理函数 */
* [9 q- e1 o" Z I8 {2 E int (*suspend) (struct mtd_info *mtd);
4 ~1 X2 e' g. { r9 r void (*resume) (struct mtd_info *mtd);0 E) \/ P, \9 t9 E5 Z- j
& ^! z- c' x& ?* D/ P
/* 坏块管理函数 */
7 p9 ?% z; k1 a int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
; U2 F9 @3 p4 g- Q7 _3 M int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
3 Z+ d( A* c! b" T- U* f% l
# t: F" n6 a; O3 v( l void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);7 X9 I! U: b! j9 b9 Y
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,7 r! v) ]7 b( F# u
unsigned long len,0 k0 N4 N3 A$ `& L) y2 h
unsigned long offset,
/ O$ g7 }" A) E+ J9 V! J8 t unsigned long flags);
8 u1 Q* k5 \! Q4 ? struct backing_dev_info *backing_dev_info;, Z/ U$ w, K. {1 p2 v
struct notifier_block reboot_notifier; /* default mode before reboot */# b" y; o4 N7 i8 B1 {* l) I& N. z
% M5 x7 ^; n( `8 t$ ] v
/* ECC status information */
1 m7 q* l* S) ]( u struct mtd_ecc_stats ecc_stats;, n9 z$ [* a6 R4 H& N! j& {9 x" `! P
int subpage_sft;
7 l* e) M+ l q/ ]0 I& b struct device dev;$ z9 b: m5 N7 m/ J1 i
int usecount;
# h6 Q7 q; e* X! ~1 j int (*get_device) (struct mtd_info *mtd);2 n1 U0 f# v! X; W& i7 M6 m+ K& C" r
void (*put_device) (struct mtd_info *mtd);9 ?! F4 o' V2 X6 o& z7 |
};: v2 Y2 Y0 d: X* g+ {9 n' s$ k
mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。: H: S# D: h |
如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。
( T% \4 {4 F' V6 W+ [
! x0 h9 e, F: p( n! o$ V
. k3 p% y$ ], o5 F5 a6 Yint add_mtd_device(struct mtd_info *mtd)
5 @$ f0 e8 C8 c* [. E7 `int del_mtd_device (struct mtd_info *mtd)- r6 j' X6 b% \3 _$ K
如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。$ h; z o; K% M! f' T1 W
int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)# C* M$ T/ }7 c7 Z
int del_mtd_partitions(struct mtd_info *master)
J* B# {8 E$ j其中mtd_partition结构体表示分区的信息; a. L! e3 u% l: c- r8 G; S+ l: l% @3 P
struct mtd_partition {
( h- U3 |* `' Y. U. W char *name; /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */, I3 ]) ^$ \3 [
uint64_t size; /* 分区大小 */+ U* a; Y! V3 r8 D/ f7 Q
uint64_t offset; /* 分区偏移值 */
: D# m8 l: H$ ]6 T+ l: D# Y uint32_t mask_flags; /* 掩码标识,不重要 */
; N$ W" n' U- R6 v, b" a$ s struct nand_ecclayout *ecclayout; /* OOB布局 */1 Q9 {+ g# F0 `% g4 @
struct mtd_info **mtdp; /* pointer to store the MTD object */9 \: g- m9 b0 m5 ~+ z+ e# a/ [
};! w2 L6 Z% F5 S6 }
其中nand_ecclayout结构体:
* N9 |: y/ t4 Y/ W! G. _ w! K Wstruct nand_ecclayout {2 I/ h- C1 x; g9 b" H' I6 O1 F
__u32 eccbytes; /* ECC字节数 */3 W& T3 M- d! S( c) J
__u32 eccpos[64]; /* ECC校验码在OOB区域存放位置 */- [0 |) K- q2 A3 M
__u32 oobavail;
( r. P z& J) Y' F( H" B /* 除了ECC校验码之外可用的OOB字节数 */( P( A: k2 V* S* E' a" b# c
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
; G6 O" Q+ C, z" ]8 r};
( D" }: ]9 K- a2 o关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。
6 s' |# {4 W8 PMTD设备层:
" a3 y; G$ V5 ^0 t! s4 r6 I* Mmtd字符设备接口:% F& b9 F* t; q4 A
3 Y. ?$ L/ {9 h: E$ Y/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) / ?$ S. T! R& }0 h( L# g1 M
y7 [$ _5 P& }# A) jmtd块设备接口:
- {4 n! x. Q7 T. N' f
/ P& M% t, r4 P" N2 J/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。* h9 [7 Q# M% F& v) g
MTD硬件驱动层:
& u4 e, |: K9 A$ I. j: D2 J
/ B- Q5 l: a" o4 KLinux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。0 A+ u, J$ ]0 Y. ?$ D$ P. ?+ ~; u
& E5 G5 O; N: p: B$ c" f, [- D- g$ l
MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。
3 ~. `# Y) _8 F: R, ~5 s7 n3 \
6 B1 l1 }1 ?* f+ m: u" d8 ~4 O4 _
4 l1 }& D$ v, A, ustruct nand_chip {
T" w# m: q5 ?( b) x void __iomem *IO_ADDR_R; /* 读8位I/O线地址 */
; i8 h* r" r1 ] void __iomem *IO_ADDR_W; /* 写8位I/O线地址 */7 ?8 q" a8 X* j1 L8 x1 O& x
% ~4 R& o/ Z" a# Y& R( K) T$ n
/* 从芯片中读一个字节 */
6 N, }9 p5 v5 a0 R0 I+ H uint8_t (*read_byte)(struct mtd_info *mtd);
0 e& @, M0 W2 A: `) k$ M /* 从芯片中读一个字 */( w, [& J7 e8 R( i
u16 (*read_word)(struct mtd_info *mtd);
: C2 Y+ I0 z" Z: W! k& w /* 将缓冲区内容写入芯片 */6 B6 O" p0 ^3 D) m3 s3 |
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
7 z0 _1 z( Z8 B! r5 V /* 读芯片读取内容至缓冲区/ */5 y* C* F1 S% H y
void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
* m5 ~% _ B) n/ V /* 验证芯片和写入缓冲区中的数据 */6 i1 t" N8 m O
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);/ B" r$ r# M1 N/ b; ]
/* 选中芯片 */
8 g, n$ y! ~( _5 e void (*select_chip)(struct mtd_info *mtd, int chip);
, u* {. b/ ]! k9 s0 S: C/ L /* 检测是否有坏块 */
2 H' s& C, J! Q" `) k/ |+ h int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);/ T: U% j8 v/ n X Y
/* 标记坏块 */
; F9 e$ t# T j5 V0 _% ~ int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
8 B: F- i- X& @; {) h% `5 l+ v- _ /* 命令、地址、数据控制函数 */: J0 ~- A2 d) w, f: Z Y
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
; T; d4 Y M+ `" ]0 x /* 设备是否就绪 */
0 c2 u8 {8 c+ |, A0 Y7 ?( w int (*dev_ready)(struct mtd_info *mtd);
7 k" |7 _5 v7 Q3 m /* 实现命令发送 */
1 B9 `# h5 `; Z+ b: M$ t void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
) x+ y& O# D% F+ l2 m. N& C- u int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
0 s: Z, N3 Z2 } /* 擦除命令的处理 */
" X# d- n# d d& W& _ void (*erase_cmd)(struct mtd_info *mtd, int page);4 G! h% V( y0 Z( ?" Z5 m
/* 扫描坏块 */
8 D) P3 i. |, y int (*scan_bbt)(struct mtd_info *mtd);
; E/ `2 Q! ^2 y4 M x/ W0 S int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
% h. ?0 \, g" u z; s9 H /* 写一页 */
, `! R/ Z9 w7 `* T* j- s int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
7 K5 A% i6 H+ u7 c/ q" T9 y const uint8_t *buf, int page, int cached, int raw);; U- {! ]( z4 y# G( L/ _
) u4 s* E9 }. x& t0 ?7 I6 `
int chip_delay; /* 由板决定的延迟时间 */& H. k r6 Z: f# P: p1 q
/* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */
- `$ v$ ~* K2 }; P- R unsigned int options; ; e1 E4 ]' [4 P: S, I
1 B, m# v, U6 \( o* h% l/ o /* 用位表示的NAND芯片的page大小,如某片NAND芯片
6 ~& s' j* T( ?8 x' a p * 的一个page有512个字节,那么page_shift就是9 0 E/ i+ B2 K/ K+ s" V Q; W: T9 i
*/
9 S! r" {) t6 X8 O int page_shift;4 x0 ~9 S$ U% a3 {8 l
/* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
8 H. G8 `8 D. k/ l; W# z * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14
) |) l' A: \2 d% s */1 `+ \5 a1 y; |: R
int phys_erase_shift;+ h5 u5 _7 M4 H( g* J) N# Z
/* 用位表示的bad block table的大小,通常一个bbt占用一个block," ^+ y" H/ a2 w
* 所以bbt_erase_shift通常与phys_erase_shift相等
6 q3 y% x8 F3 w */
! X; s# v* L" l7 D, e9 f int bbt_erase_shift;" s9 n# P" ~2 P2 A3 f3 G
/* 用位表示的NAND芯片的容量 */+ Y* d* ?, y1 D% Q& n
int chip_shift;5 W0 X) k6 E* m, k
/* NADN FLASH芯片的数量 */7 C% n% W3 K( r7 k y
int numchips;
$ k; v7 [) p- s. ` /* NAND芯片的大小 */
6 C! Z$ } \; x0 }0 |: s# A uint64_t chipsize;
: n5 N3 g$ C6 L int pagemask;: \6 E/ X0 J+ K0 X* L. Q
int pagebuf;# U, v0 s6 n3 ^+ Q' x1 X$ g
int subpagesize;
* U1 w" H& x9 L3 V3 ]" T0 O- f! z/ q- Z uint8_t cellinfo;
* e: Z3 u z' y! o int badblockpos;
6 B# o- R) \) K nand_state_t state;
2 v& `9 |7 K* y0 {: a" k& g: ^- v uint8_t *oob_poi;5 Y5 A5 p7 K! m/ X! x$ }
struct nand_hw_control *controller;
: g* d1 H1 T% H( o struct nand_ecclayout *ecclayout; /* ECC布局 */ J, S( `* V. V A
$ a4 Y* _% v: a8 m& x& G: i struct nand_ecc_ctrl ecc; /* ECC校验结构体,里面有大量的函数进行ECC校验 */
: F' ]: b w7 {$ v) J8 q struct nand_buffers *buffers;
. A; y9 z' s, |8 p6 b1 T struct nand_hw_control hwcontrol;
& [$ m3 ^) w% D: x9 d0 A: }. S struct mtd_oob_ops ops;
& a L/ G3 q8 r/ J Q" o uint8_t *bbt;
$ N1 L8 H! U0 [ struct nand_bbt_descr *bbt_td;( m8 t: X+ k; U& f
struct nand_bbt_descr *bbt_md;" [# v# P: \- [. a% D- p) n' P
struct nand_bbt_descr *badblock_pattern;' f' T E) K% J4 K, b9 f4 T; G
void *priv;
2 Z% p E9 u( v};
1 D1 f3 w/ W ^, r5 u0 Q' g7 O+ e, O0 v& c3 w
最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。( E) Z5 C( W' L$ y
% M" m5 x- |6 W r+ U
3 } Q9 o: B! p7 s {
, s5 }( _# Y. b0 O2 j) c$ W
# d. d$ R% ~4 h8 p* y Q" w3 _ |
|