TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。% r2 p. l+ l1 r- A6 I0 \& O
" i% F3 L* V6 B. W+ V$ P% q' H
6 q* G- G; I) `
6 B9 Q0 p* A0 L- l2 X6 \
如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。 l2 V! \$ ?2 l# v- g6 x' p
G( x3 W8 @1 \2 X
Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。1 c9 c# b1 f& j6 _$ \4 N
6 _0 e5 u2 j, d8 t0 _
MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c: MTD原始设备接口相关实现,mtdpart.c : MTD分区接口相关实现。
?, f% z3 T, `2 I* }
9 J( R, G- T1 oMTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。1 S: u) u( W1 k5 l! M
$ i5 O- ^# d2 l9 t
设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备 * |3 |9 }( [1 A
4 Z4 z9 F1 i2 T, e$ c P5 x0 s
MTD数据结构:' N3 W# V6 o) V
- P. z( X" R: { _. m
1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:2 q4 q& D/ F" J7 J# z* ?% ], I
9 q; Q* G, Q* n" r6 A' F
" S4 [2 N) @5 y7 Cstruct mtd_info *mtd_table[MAX_MTD_DEVICES];
9 a7 O9 I3 C6 Y: J( g# X$ m9 o2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。
s7 W7 I$ M# B; k* m4 X* g# j1 r# L2 _7 d# ?+ I4 b6 m1 l7 S
struct mtd_part {% I; K) e y d5 B5 b
struct mtd_info mtd; /* 分区信息, 大部分由master决定 */
% V, g' R9 n" N' t, r struct mtd_info *master; /* 分区的主分区 */: r, O. }7 }, \
uint64_t offset; /* 分区的偏移地址 */& E" U' X) g3 j. D: _; I z: y
int index; /* 分区号 (Linux3.0后不存在该字段) */
) G% m. t6 R* T) H, y0 z struct list_head list; /* 将mtd_part链成一个链表mtd_partitons */
7 s& a+ s) _% s9 ?* _9 D int registered;
/ q5 e* ?5 T' T};
& W4 d" t. h3 m% b1 @9 omtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。0 \ m% t' O3 P: x* h2 Z" W1 n) Q8 a
* X0 @7 Y; v, h7 A7 H# I$ zstruct mtd_info {
: B7 o: y: s/ [2 o5 ] u_char type; /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */
3 I/ j( G; P) u7 K+ E8 l% a& @6 ]" g2 ^ uint32_t flags; /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */. s! m8 Q8 q- U, e
uint64_t size; /* mtd设备的大小 */
- V* K. F+ ^# l uint32_t erasesize; /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */
, m5 r# Y2 L& {$ j& \" p! ]+ _ uint32_t writesize; /* 写大小, 对于norFlash是字节,对nandFlash为一页 */
3 b7 q+ L M: L5 ~% K, \ uint32_t oobsize; /* OOB字节数 */& d% o2 T$ D: L2 E
uint32_t oobavail; /* 可用的OOB字节数 */
' r4 ~+ y# [6 U% m( m unsigned int erasesize_shift; /* 默认为0,不重要 */- V2 M4 Q7 p: R T
unsigned int writesize_shift; /* 默认为0,不重要 */
R& X" A. D- u% |# ?4 _! O unsigned int erasesize_mask; /* 默认为1,不重要 */
/ j, ?% \( _& c6 f unsigned int writesize_mask; /* 默认为1,不重要 */) ]+ \: @+ Z$ B' ~
const char *name; /* 名字, 不重要*/; P: V$ J C! w/ z. v3 H6 O# }
int index; /* 索引号,不重要 */3 c+ W, r6 ^* [5 _
int numeraseregions; /* 通常为1 */
7 h, B/ V! R* Y, u struct mtd_erase_region_info *eraseregions; /* 可变擦除区域 */
$ ?0 \3 l7 A+ l! y) Q- [
4 [8 P: Q0 b" V" f, J void *priv; /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */
6 k1 w9 a8 I) N/ P" G struct module *owner; /* 一般设置为THIS_MODULE */
5 s% U3 |3 Q! |5 i* V3 R
2 P9 C. n4 l" r8 z1 o /* 擦除函数 */% B6 I( A, s! E6 E, [% N2 V# I0 v) J
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
A- H/ K$ }$ K4 H7 ]! h- ~6 s+ B+ v3 e* N# }- g. ^6 \, r! V
/* 读写flash函数 */$ ~* v1 I2 g; p, y2 |5 m" e' I
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);. |; T1 @# M z
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
2 S7 w, Y1 ?9 R" e' X) a/ L' |/ l9 I. D6 p
/* 带oob读写Flash函数 */' T$ I5 G2 j. @3 j# o
int (*read_oob) (struct mtd_info *mtd, loff_t from,
- R" ~8 Z; |: p7 e4 y struct mtd_oob_ops *ops);
3 X- A( E7 F) I% V* v! l int (*write_oob) (struct mtd_info *mtd, loff_t to,! t T7 R5 u1 p3 ]1 F; ]" e
struct mtd_oob_ops *ops);
7 S) D' X/ l& {$ V0 `) X0 e- K a, d* t8 ^
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);8 q& M. K3 C" f! L3 h1 Q
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
* F6 s) e; p( _ v int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);; z$ g, j. |9 d# j! X
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);8 }1 u" g6 D) x8 D
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);8 D6 g+ z/ D7 v W& U0 B) \
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);' s9 O+ ^$ j5 \8 `. z/ q
. \" h. o+ e2 q int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
+ H: Q: D& Z' I& _ int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
+ Y* L' w( }: s$ p4 K \( W; A2 p% z /* Sync */ Q5 ?: T0 Y7 ]3 x
void (*sync) (struct mtd_info *mtd);# [. F z: Q( T, v$ x; H+ [
" b$ w6 V6 z. @& i- D0 M6 m! m /* Chip-supported device locking */
% G# u# o! L" t" Z int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
. b: ?. p, L! W+ }1 N% ~9 ^ int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
( h! b; g- O. Y' Y' N K0 A. S7 r0 ~7 |+ B) s; x
/* 电源管理函数 */
1 X: W8 c$ r/ M4 v! s5 M' f. u" n! N int (*suspend) (struct mtd_info *mtd);7 o/ Z; g4 Z& Q9 G
void (*resume) (struct mtd_info *mtd);
& [' L+ x7 L3 o, q) v7 @; w6 y# E+ w: m# ]9 M+ s9 l
/* 坏块管理函数 */
! h# K! R6 j, S0 H! L* l int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
' q- F+ O0 m9 }* w3 ~& c int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);( }0 N1 [/ a$ z' T8 T0 s
7 M9 A1 m# m- K7 ] void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);, @* e- H+ [( z* X0 L
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,0 O8 O7 D$ U8 X3 ^' e5 C& y
unsigned long len,, p# S1 H; G* T
unsigned long offset,
2 M6 O2 D- P- o# } [% E7 J7 ` Y unsigned long flags);9 x( _1 C9 E. Z) m* A' ]8 F
struct backing_dev_info *backing_dev_info;7 \$ E' G1 k$ b2 h4 G- Q
struct notifier_block reboot_notifier; /* default mode before reboot */
3 ?7 q% | o6 U3 U! v' M' J6 A, ~3 @. |6 _% r: H* i
/* ECC status information */
! u* d: f) D* M, @& f) Q struct mtd_ecc_stats ecc_stats;
* ^2 g8 W# j# h" v int subpage_sft;
; o [( Q' d. F$ p' u0 m2 M struct device dev;
- C! P7 ]5 H5 z v, k int usecount;
% p4 F- M0 b8 s1 n int (*get_device) (struct mtd_info *mtd);8 o2 [& w* r2 @' f& |, }
void (*put_device) (struct mtd_info *mtd);
" A5 h/ d6 t: I- k7 O' X0 g};1 L- D: I, Q" z& Y; C! F, G! b
mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。
! d7 m( j4 n2 _/ l* `3 O: Z" c# y5 Y! V如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。
7 o. l# G2 p! e1 B# Z, T5 p* ]9 i- j0 B+ ?$ T' v
5 E/ P( |8 V- T( Y+ |
int add_mtd_device(struct mtd_info *mtd)6 X/ C" b% ]/ B& ]( t4 `' w
int del_mtd_device (struct mtd_info *mtd)
4 f" [4 q+ } v8 {7 L' |: z如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。2 J" Y* k9 y% g2 q! c
int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)4 T& n) B" a- S/ H) R
int del_mtd_partitions(struct mtd_info *master)8 i3 k" `# h2 R8 c9 Y& ^- I
其中mtd_partition结构体表示分区的信息1 w/ ]: P1 r5 {/ s2 F6 r
struct mtd_partition {" k# T! c, i4 E- e
char *name; /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */
/ Z, @6 `1 ^, U' l% \ uint64_t size; /* 分区大小 */
% E+ O! d; l M( _ uint64_t offset; /* 分区偏移值 */
8 ^ c" Q+ J ` uint32_t mask_flags; /* 掩码标识,不重要 */+ a1 n8 [% z, k6 Z$ `1 ~
struct nand_ecclayout *ecclayout; /* OOB布局 */% Q* r) p# R) J' y2 Z( m4 \7 g' Z
struct mtd_info **mtdp; /* pointer to store the MTD object */
3 W; B7 Q9 b: j& B: C2 ?: y};) b* e( x" \( N3 K/ @6 o2 m
其中nand_ecclayout结构体:5 K' j- L) K9 x, h
struct nand_ecclayout {
6 |$ F$ E' u3 p% N) ^ __u32 eccbytes; /* ECC字节数 */
( b" C4 Z# Y9 B+ A$ T R5 |% y2 e __u32 eccpos[64]; /* ECC校验码在OOB区域存放位置 */
" J% T& @, X" ] v1 a* r' k __u32 oobavail; $ I$ s( s G- v. B8 n
/* 除了ECC校验码之外可用的OOB字节数 */
0 n# G, r5 i9 q, L struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
1 E2 x, a: [; D4 _' u: n};
0 P" ^" O* k: M4 g/ r关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。! P$ y3 i6 z5 ~9 S3 J0 I2 o
MTD设备层:0 f' F/ K u! d( m" @% R0 `, G
mtd字符设备接口:
# e% o* J. g! \% r$ ?8 C8 ?) a2 p d# ^
/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)
7 S7 s9 v& ^. U
. ~$ {( f: S4 Omtd块设备接口:2 _/ M' W1 e( C/ U; ?' U& {9 z" R4 L
s0 W8 O8 X! X2 C/ |' n- o u/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。
: |# K- `# t# U5 z& `: NMTD硬件驱动层:6 M6 Q$ k% g4 e# ~1 ?
* n/ j& r* z) E& ]0 ^6 ]- ]& c% `
Linux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。6 y8 n- P% Z* \$ n, H7 Q$ L
% X# ` y* M$ t7 x* C2 n" g# o8 xMTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。( B3 Y- U& J9 e! \
+ ]6 H7 J! q9 z9 m' {+ g7 r/ E! p7 u( |- W: `
struct nand_chip {4 j% o4 X8 M* f" n g$ r
void __iomem *IO_ADDR_R; /* 读8位I/O线地址 */
5 x1 i" F( w7 R# q2 Y$ H: a" F) k7 h void __iomem *IO_ADDR_W; /* 写8位I/O线地址 */7 J2 J" Z6 Z' k6 g/ i* C
) O1 B! V3 p2 X i
/* 从芯片中读一个字节 */
+ @ Z# b% M) B3 l: P, j: Z uint8_t (*read_byte)(struct mtd_info *mtd); % m) v/ A2 V' V6 D8 B$ \! l
/* 从芯片中读一个字 */" Z) O1 o# f' V
u16 (*read_word)(struct mtd_info *mtd); , D. ?+ E! W+ B# @8 ]1 f( U
/* 将缓冲区内容写入芯片 */! V' b5 G3 ]) O. r* {( X+ m
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len); {# K/ Q- P' B* T
/* 读芯片读取内容至缓冲区/ */
& m. `$ l4 a. z1 s; Q void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
# d* K' o0 K1 W; \ /* 验证芯片和写入缓冲区中的数据 */7 r4 _: ^7 G6 e7 h h
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
I0 u7 N/ g( Y+ O4 Q; ] /* 选中芯片 */6 t d% R% S9 H/ h: K5 q( |
void (*select_chip)(struct mtd_info *mtd, int chip);
2 Y1 W! Q6 Y) X$ v' x6 o /* 检测是否有坏块 */
- j# k1 s! y, w) j: X4 V int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);% f& Q9 g+ n4 t* o" L8 j0 W
/* 标记坏块 */. [0 U2 n) |/ `$ v+ R
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);0 D3 ^' y) R: C8 m3 J6 ]
/* 命令、地址、数据控制函数 */7 h$ D$ L y, \ x% u
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);
7 k7 s* T+ A! P /* 设备是否就绪 */2 `! |8 d- { A4 Z) a
int (*dev_ready)(struct mtd_info *mtd);
9 T* ]) d* [. I /* 实现命令发送 */
- b7 ~8 c+ T* g4 V' y: K- X void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);
4 T# a: Y' P: b+ Z% g4 x3 H int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
* E% F4 A* c6 O, q* J* j /* 擦除命令的处理 */
; c+ h0 |* F( l- d7 h7 T" l void (*erase_cmd)(struct mtd_info *mtd, int page);
4 u. G& v' Y8 L& E# |$ S /* 扫描坏块 */; J. z" i9 {/ I2 A, i7 }
int (*scan_bbt)(struct mtd_info *mtd);
4 n: m& D) D7 U* S: ] int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);
! ^* Z* Q. e1 l& {5 Y9 U1 O /* 写一页 */0 G: \# b3 y" {% y7 r) M
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
- h! Q' E, T" D. I8 q. S const uint8_t *buf, int page, int cached, int raw);& u2 c2 |2 h- C; E, n
) p6 L* p" s+ C5 \* W4 X
int chip_delay; /* 由板决定的延迟时间 */
8 B u' N# ]* V$ ] A/ t* n- L /* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */
1 W( q4 S7 v; ?$ v8 p: A" D( ?, O unsigned int options;
9 b# R% K e: O/ ^
. j, c1 i' B+ ?' n, Z% T0 } /* 用位表示的NAND芯片的page大小,如某片NAND芯片
" L5 r+ z4 ~5 C( o; S, u * 的一个page有512个字节,那么page_shift就是9 8 [8 y* U# h* N
*/
! c5 B2 V4 O! A int page_shift;
# w u- J( m- \: m3 d; d6 p6 `7 z /* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可 q, Z! ?. R/ G2 U
* 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14( ]9 g! v3 Y6 d0 I" z
*/- d# q* @# f) ?$ Z
int phys_erase_shift;
" I) [1 p% a8 s0 ?) h. }8 X1 y9 { /* 用位表示的bad block table的大小,通常一个bbt占用一个block,
6 q* d7 c8 B0 j9 t3 e7 E3 ^7 Z * 所以bbt_erase_shift通常与phys_erase_shift相等
% H5 `5 D$ d1 c9 J1 _# K$ C */- X9 L3 N/ Y# L% X* W
int bbt_erase_shift;- Z. M- d2 {2 U
/* 用位表示的NAND芯片的容量 */8 K& P+ c: i' k
int chip_shift;! v9 e9 g2 K3 c& \) \
/* NADN FLASH芯片的数量 */
; ^! q- s u! v9 T int numchips;
* u+ o2 Z! ^% f, t /* NAND芯片的大小 */3 c& t" ^, Z. `
uint64_t chipsize;" S6 q6 H+ Z+ A7 Z( f; O& B) e
int pagemask;
|4 X- `9 _5 X e) x M int pagebuf;. J9 t% P+ z9 i% B# O( F
int subpagesize;
. s3 J ?( L" p% q+ H; @* P- i8 B uint8_t cellinfo;
" D& Z* m- O& q4 s1 s int badblockpos;+ ]: t# G! \9 n0 y& a
nand_state_t state;
" `4 b" r- {6 V; m uint8_t *oob_poi;8 H- a1 `8 E( i. G; ~/ O3 f1 }
struct nand_hw_control *controller;
5 e0 I7 V+ K; C- U struct nand_ecclayout *ecclayout; /* ECC布局 */
7 H* j. N1 [8 E% v" r$ h ( f5 {, s5 n8 \( h1 f
struct nand_ecc_ctrl ecc; /* ECC校验结构体,里面有大量的函数进行ECC校验 */
5 E. Z, b4 x* ^# L9 G struct nand_buffers *buffers;
% }8 _4 g: v# j/ `5 O ^0 c struct nand_hw_control hwcontrol;
- v$ O4 W- D2 q/ s6 n; I( A! g5 B2 P struct mtd_oob_ops ops;! Q# `' o. n0 c. b
uint8_t *bbt;9 T) ^' o- E- {! w% t
struct nand_bbt_descr *bbt_td;7 }' k9 U/ i+ u6 U0 g3 Q) @* o. p& D
struct nand_bbt_descr *bbt_md;1 h7 g; A4 r: L' n4 P
struct nand_bbt_descr *badblock_pattern;
0 m6 I' D e4 [ void *priv;
/ }3 D1 {. G0 B& T7 \};( v! k+ q' E; Q1 ?
% g6 m- b8 V0 V- C9 c最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。
: ?# I4 m, P7 _& U4 P
4 ?' p$ g% b0 c% S
w R0 o: y* p* o+ \0 X% g6 @% K% r) T
7 c6 z" C; u3 m+ ~ |
|