TA的每日心情 | 怒 2019-11-20 15:22 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。1 ~" {: Z8 G- S2 S; m0 S! E0 p7 m
+ u# I; P; }3 T8 _% v* ^+ z
% y1 n8 n- B1 u+ _6 x! u
8 s2 F* x$ H) ]$ z- _
如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。9 H: L6 C2 n/ Q* b8 i
. Z- v8 |7 L" q; J- k$ X+ H: c* CFlash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。& M2 b+ b1 o! \' V! S. w5 ^5 h% y
3 P7 A5 J- g' h4 Q3 N$ W5 TMTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c: MTD原始设备接口相关实现,mtdpart.c : MTD分区接口相关实现。7 k$ S7 z* X2 \4 }! I4 D' J
$ _# i# _( Z* [8 g) ^3 r/ CMTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。
0 J, d8 a4 A% t
; I2 D6 x9 v+ L0 f" n; F' [设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备
' n; @' A) m$ W0 z# U1 v0 F6 R0 b; F j$ c) A% ~/ G
MTD数据结构:6 V( ^+ ^+ A' Z! j# ]: m, z2 J
# F# o6 n# k% u8 N2 j1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:
+ I2 u* Z8 i1 n% f4 T8 H ^7 P
+ G' t p0 h, k' P2 T( y4 o$ N) j" K- s/ I, E/ E+ C1 I6 G
struct mtd_info *mtd_table[MAX_MTD_DEVICES];* j8 |( q5 l: J$ C! \ W
2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。) }% Y4 ?$ V1 C! R
+ A& V' G) y. _ v9 a% i; t1 T3 gstruct mtd_part {
) D5 ~3 i% R; s- u4 z struct mtd_info mtd; /* 分区信息, 大部分由master决定 */" l% ^; J4 [, J' B+ ^: G% O* i U$ Q) V1 e
struct mtd_info *master; /* 分区的主分区 */6 ~% X6 s. m2 A
uint64_t offset; /* 分区的偏移地址 */! O+ i/ Q: a* x p2 L
int index; /* 分区号 (Linux3.0后不存在该字段) */
; {8 D3 Y9 S# L9 G struct list_head list; /* 将mtd_part链成一个链表mtd_partitons */
) ?$ g( m) x" p5 f1 _, | C* R int registered;, Z$ A; { ? S/ a! @( ?+ Z7 d
};
" B; U2 H% b8 Z# r( Z8 l0 Umtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。
/ j8 V( |# _" S3 B8 B* @, G( y4 y) V: F) F; P* b3 A/ m
struct mtd_info {
0 b$ P A3 B$ m3 | u_char type; /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */
1 O5 V2 \0 Q# M) R/ U/ q% ] uint32_t flags; /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */7 f) l+ E8 Q |' {) Z" f. V, \! D: c
uint64_t size; /* mtd设备的大小 */
) `+ G5 ?) @& U# |- p/ I7 P6 O1 N uint32_t erasesize; /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */
( k" w+ e# V+ ~- E6 ] uint32_t writesize; /* 写大小, 对于norFlash是字节,对nandFlash为一页 */8 u u. H" _6 A' p
uint32_t oobsize; /* OOB字节数 */
+ c' z2 {# @/ J, O ]: P uint32_t oobavail; /* 可用的OOB字节数 */3 [+ O% X6 A- k7 ^$ F
unsigned int erasesize_shift; /* 默认为0,不重要 */
6 T( C" w7 k; s/ H5 K5 [# n9 \* q3 _ unsigned int writesize_shift; /* 默认为0,不重要 */* |( o, B9 o( u* w _# j% j* H
unsigned int erasesize_mask; /* 默认为1,不重要 */, e) ~2 a. @ u
unsigned int writesize_mask; /* 默认为1,不重要 */; F/ {* V. }- a) G6 ^# l& m
const char *name; /* 名字, 不重要*/
& a9 }/ Q) V d* \. l: h int index; /* 索引号,不重要 */
& B# R9 Y, L; s4 U int numeraseregions; /* 通常为1 */
; k% s- l7 u3 k- D1 Z6 x/ S struct mtd_erase_region_info *eraseregions; /* 可变擦除区域 */
' {$ u; [2 A- h9 ]9 x
& q& J' W+ L1 t0 X0 J/ J void *priv; /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 *// k. a! Q9 C, D8 U6 G8 \
struct module *owner; /* 一般设置为THIS_MODULE */4 ?$ Q) u! {7 }2 H. m9 o
/ ~& {8 W; |2 }9 Z+ r1 J: \9 ? E n
/* 擦除函数 */7 p4 `' j( g. r+ F% b7 l1 {
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);' j0 x4 `6 C, ?0 f
+ s7 M a; G: Z8 I! ^ /* 读写flash函数 */0 I- a0 V' O, t1 B3 n
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);$ @' c7 K6 k/ M' l; T
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
) t0 I5 W. e- ]- J
3 H2 m1 e3 e# f4 t. E0 V& u /* 带oob读写Flash函数 */
' a" l$ h, @8 V int (*read_oob) (struct mtd_info *mtd, loff_t from,
0 K) L, u0 u3 T% r% h struct mtd_oob_ops *ops); u( ?8 W$ F4 ] M9 V
int (*write_oob) (struct mtd_info *mtd, loff_t to,+ m* o7 t* G2 @' p9 T
struct mtd_oob_ops *ops);
6 g# g9 ?6 R# X- `# C
$ V7 V2 \/ n% d/ ]3 L2 \# A int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);: C4 ]# I4 ~7 h0 c: G, k! Z. x
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);4 g' K! o5 p" o! r, j
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);/ v1 C3 Z2 d- [. Y B0 d1 M
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);& r# B3 }! j Y( |
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
: C; Q. ~+ I3 d7 s4 A int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
C, p0 Q7 u4 s' ]
( c/ q9 N4 p. [5 c int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);! Z: a4 o: v' J4 n; W& {" q
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);0 W9 I$ C: a7 D
/* Sync */# R# D, D! ^6 x
void (*sync) (struct mtd_info *mtd);
2 i& \5 U; B# ~. p( Q( t" t2 b/ D
( O' D: ^0 B+ E2 h /* Chip-supported device locking *// l5 m2 e; D! r# P% H; n& G5 B
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);6 A& n! Q" b# _6 ?0 r6 V, d
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);8 { h5 J* V8 R* x k
# w$ F0 y( w) y! U1 p /* 电源管理函数 */& B( }5 g) P& i) X# L' L8 K b
int (*suspend) (struct mtd_info *mtd);
4 H6 e7 ?# L, O4 ~, d6 w& v void (*resume) (struct mtd_info *mtd);% y `# l3 ~7 |/ x; ^4 E/ d+ w
. B4 l9 c, x1 O9 s' s; K( w0 ? /* 坏块管理函数 */# F$ ~+ K6 n; N# T0 |) q5 p
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
1 r* ?& H/ W+ t# [# ^% _) v int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
) X* ]: n2 u3 l+ H6 G7 a
# e U2 D2 n% e8 q+ }8 m void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);* i8 z3 D+ A; g: d
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
# p6 J0 @6 a* n& D1 \: V% V unsigned long len,
9 g. g# H# J& C$ Z* _* H; H unsigned long offset,
( ^5 R0 ~: \! e unsigned long flags);0 z! z( f1 C* o
struct backing_dev_info *backing_dev_info;& q: T; r3 O" h
struct notifier_block reboot_notifier; /* default mode before reboot */
0 b! ]$ V. {: T; Z, Z9 U/ ]* n- s3 c" C# C7 v) N7 l
/* ECC status information */
& F1 q2 t+ {& {9 I/ B2 P struct mtd_ecc_stats ecc_stats;2 T6 f/ `: e! o% p# Z' H: H$ [
int subpage_sft;
$ e! u8 J7 ?2 |- K) e. c struct device dev;
) P( x" y1 F8 O% Q8 Y5 y) O9 Y' Z5 K int usecount;1 [0 {' p) q0 |3 s7 U$ F
int (*get_device) (struct mtd_info *mtd);; K( X5 B: W( A; @, ^
void (*put_device) (struct mtd_info *mtd);) d- G' O8 E: i( g2 x
};9 _+ s: J$ y( Y+ |9 M) \
mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。& M4 {- b+ R: d6 F9 D& g3 q, y8 @
如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。
, G- z! K$ M' N9 c
2 @- U' K1 ~% } l+ u
* v* H' f% {6 R' sint add_mtd_device(struct mtd_info *mtd)3 q, p# r) k: _7 B- u; @. D
int del_mtd_device (struct mtd_info *mtd)
# [7 F* I* X4 s! I: n" s如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。8 v2 A9 o. O0 g/ q" Z' @+ P
int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)
/ w1 Z5 Z1 X% B# K1 a: Lint del_mtd_partitions(struct mtd_info *master)
5 }1 m: {& V+ X其中mtd_partition结构体表示分区的信息
n' Q, P; b7 T) e' ~$ y; u" estruct mtd_partition {0 n R5 G- a0 N- A9 t) [9 v2 |
char *name; /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */
- |$ t3 `! R. j9 m- T$ m( ~7 ~. s% ?* Q uint64_t size; /* 分区大小 */- H8 |: C6 t |
uint64_t offset; /* 分区偏移值 */# |" l, c0 A/ ^
uint32_t mask_flags; /* 掩码标识,不重要 */
5 Y8 a$ f( q6 F$ J struct nand_ecclayout *ecclayout; /* OOB布局 */
% z1 @' J9 o6 D2 \* n% \ struct mtd_info **mtdp; /* pointer to store the MTD object */
& I8 S. d }$ j! c' o* O! M; {};4 _: v* V5 \# x6 J( v+ D6 m* C
其中nand_ecclayout结构体:6 X1 T( ]; |0 ?' Z, a2 w) ~' w2 e
struct nand_ecclayout {
; r; e4 |( O8 Y% r& r __u32 eccbytes; /* ECC字节数 */' I# X3 u. p& C C( `
__u32 eccpos[64]; /* ECC校验码在OOB区域存放位置 */
8 z& l8 d0 a4 N" X9 ?& S __u32 oobavail; # W& `4 X5 J i7 H& B
/* 除了ECC校验码之外可用的OOB字节数 */3 i# I) v1 u! O
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];- n3 S% ~7 ]- m0 }8 v" R
};
5 O! q$ ]! X7 W; ^关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。
; [+ L+ O" s1 {4 S! _) @% B. XMTD设备层:
' U( |% O4 `$ i) K: }+ ?mtd字符设备接口:7 j; g5 t: e# Z) q" t4 X4 l
+ P+ _) Y3 X" J. {7 L/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)
6 n5 e2 L% c4 L( D5 @: W7 }- F) P" H2 ^1 ~8 r( j. F3 }7 k/ J, {
mtd块设备接口:( r+ P2 }" P' n$ f2 O
) ^" I7 @0 Y( r/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。* N9 Q0 E$ q1 k* K* J
MTD硬件驱动层:1 I9 n* W. C3 ~, }( Q- Q
& X- ~- w. {+ c- NLinux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。
% x8 r' o3 N/ |- B: P* l- b1 K# X7 h
! k( z8 {4 K: C% sMTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。
% ~1 J" g- m; G. u4 Q% D+ p! N; t* ^1 @2 _3 L
" _, i, {$ a# l q3 Z
struct nand_chip {
_" {% ]) l3 i" |, p0 K. i# r void __iomem *IO_ADDR_R; /* 读8位I/O线地址 */
6 y4 L- \0 l4 E" \ void __iomem *IO_ADDR_W; /* 写8位I/O线地址 */5 s5 q, h7 |2 L5 |; k3 @, X
+ B" P+ d$ e) i. \) F$ z0 g$ X /* 从芯片中读一个字节 */
6 ?. Z8 f j k# ? uint8_t (*read_byte)(struct mtd_info *mtd); ! ^+ t- L2 b: t
/* 从芯片中读一个字 */
3 T; |2 O3 E j* q u16 (*read_word)(struct mtd_info *mtd);
! F- A2 Z/ a& X2 c5 S! | /* 将缓冲区内容写入芯片 */
7 J5 t4 \/ z" m! H( _ void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
% {4 V' w; H: w! ^5 M% v- u) p. A /* 读芯片读取内容至缓冲区/ */
8 r: s& R/ |. F, G void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
. R& }, m) k0 e" k6 w1 t) P: ` /* 验证芯片和写入缓冲区中的数据 */. v! y4 k" a. z5 k* F; j$ O
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);* d# F, q' x# x7 q/ \
/* 选中芯片 */
& o" R: j6 s9 o `( S, y void (*select_chip)(struct mtd_info *mtd, int chip);
% ]2 }9 k6 y4 B6 L /* 检测是否有坏块 */1 z: n7 m7 n7 X) B
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);$ P* Z$ N& Z, e' X+ x7 H) K
/* 标记坏块 */
: R6 d9 t+ J3 A3 @. e; v. s+ r int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);& f% x* V& Y) N& p$ G
/* 命令、地址、数据控制函数 */' X! i, t0 j' s1 O- F. `7 \) ^
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
* M0 _$ n- B- ^& A3 n, \ /* 设备是否就绪 */* r) p; l. ^9 {4 D% I
int (*dev_ready)(struct mtd_info *mtd);
( `) j* E, S% m/ H( t& O /* 实现命令发送 */( [: m% `3 D0 j' j! Y
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);' k. b6 `8 _; j
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);0 Q z5 @* }# D
/* 擦除命令的处理 */
: ^/ O! M+ p* K& ?, @% @ void (*erase_cmd)(struct mtd_info *mtd, int page);
. M' |6 D/ Y* F5 B6 [% H! x /* 扫描坏块 */# Q, i+ c3 a0 `0 y, w- w9 N
int (*scan_bbt)(struct mtd_info *mtd);- I4 i$ Q! p4 _% \8 L1 y
int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);5 O4 B# M1 {7 G- T! s- ^1 ]
/* 写一页 */8 X6 y8 N- B9 X( \6 c
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,5 m; C# f! M: U* Z. u" e0 S
const uint8_t *buf, int page, int cached, int raw); a/ q/ j1 O2 U% O2 x5 U* K7 {
5 T. N1 t& Z5 }& ^
int chip_delay; /* 由板决定的延迟时间 */5 X. n9 q$ w* D
/* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */
: `0 S7 k! u2 _, ^4 {0 ?4 D! E unsigned int options;
& a5 q) |: j. r# l; [+ Q4 c3 x+ c; h8 H" o
/* 用位表示的NAND芯片的page大小,如某片NAND芯片
3 }7 i/ e" k0 U% ^% I. f * 的一个page有512个字节,那么page_shift就是9 8 T) ^ k. t" ~& S4 p( x) Z. R
*/# ^$ ? R2 u6 J* C4 s5 ^* M
int page_shift;
3 V$ ^2 o" l5 ?) V /* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
/ @: b! P7 x6 O: _( K" N * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14. l: z, I0 Y0 w1 P
*/# i" |& e3 \- d' |) P1 H9 f
int phys_erase_shift;% k( Y0 J% ]1 G
/* 用位表示的bad block table的大小,通常一个bbt占用一个block,
0 `1 {, R- Q) H, e! S( u5 p * 所以bbt_erase_shift通常与phys_erase_shift相等
/ n3 c! K" e" T8 |) K+ y* { */
$ ^7 Z2 k3 x7 t3 _ int bbt_erase_shift;
0 H; G' i5 c( |; m8 U; T# p! @ /* 用位表示的NAND芯片的容量 */7 U5 E' l, ]' h' _2 B, c. m
int chip_shift;; ]" ^% o! j& }7 E; ~9 ~6 h \3 ^
/* NADN FLASH芯片的数量 */# p5 N7 k) K2 ^: y9 R/ a. Y
int numchips;
6 z* o* m7 `3 A3 y /* NAND芯片的大小 */
; ?: |" J2 {. N& X uint64_t chipsize;
, c, Q7 @5 {1 \% n int pagemask;" G' w" L/ x8 Q9 M2 j
int pagebuf;
/ z, j1 O( Q" V8 m! B( X2 ]: {4 B int subpagesize;
! {+ | c, |( U0 E& q. b uint8_t cellinfo;
7 l5 T. a o4 t int badblockpos;
* @' `/ |6 Z" d% s1 P nand_state_t state;
0 A; P. |' @+ q* c0 D8 o! V uint8_t *oob_poi;9 k& n( ]6 u. c
struct nand_hw_control *controller;6 ]1 w6 }! x/ Q. N1 S; P8 D
struct nand_ecclayout *ecclayout; /* ECC布局 */; q9 w( f) p0 _& a. |" x/ G- i, N
$ G5 q5 z, B. c/ O; ^) c9 v struct nand_ecc_ctrl ecc; /* ECC校验结构体,里面有大量的函数进行ECC校验 */
2 [( L. F, }# t. C1 T; Y6 U struct nand_buffers *buffers;
8 l3 g$ Z8 m" E- p struct nand_hw_control hwcontrol;7 j! E8 J X$ @
struct mtd_oob_ops ops; b0 p' D$ l, j, ]. k& b1 \! ^7 M5 n7 g
uint8_t *bbt;
0 U4 V4 N( Z3 h6 J1 s- w) i struct nand_bbt_descr *bbt_td;6 }5 j8 c8 S) ]5 f
struct nand_bbt_descr *bbt_md;0 H6 X% n; l) ]% o
struct nand_bbt_descr *badblock_pattern;7 |. B0 u1 k [
void *priv;
6 m; q7 ? I) q. a% s};
; r2 S0 P; L4 F7 Y, P1 O: e. a4 \
( t# N2 b" O8 Q2 D最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。
) Q5 m7 e2 K1 d$ {' S" P% u3 s: y- ^, ]* I% D& q
1 g9 @& X1 R% f. Y K. O0 S0 Z9 h
6 t( }# ]" V" X6 f* c0 N6 I
5 N$ { O8 |3 O |
|