TA的每日心情 | 怒 2019-11-20 15:22 |
|---|
签到天数: 2 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。9 ?2 ]* A8 a- W* c2 m3 y
( x m* H( U! H9 F: V
. R# \! r# [# v0 W/ C
7 N( Q' N2 r' R* K2 w3 {) k如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。: T6 \' q/ Q, A8 o! Q7 x, b( k) P5 p4 B1 A
& L7 j$ S9 s. y' w0 D& W
Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。( P- V6 L3 `8 D- E% I
; c) W! O) J) M9 e3 i7 S* L5 ^) FMTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c: MTD原始设备接口相关实现,mtdpart.c : MTD分区接口相关实现。
6 j3 K5 _" b9 \7 c [# C0 P( A' h$ v' q! _
MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c : MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。 a4 W/ ~0 S; l0 a% E9 l, P
( C( Z- B: G+ {+ V
设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备
0 g, S9 K; n! ?# O. o( ?
+ o r. h% |" l. Q0 WMTD数据结构:
* s. y; p, |- f0 K- Q. P, X6 E) P, \" r9 F' _/ l$ p" E
1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:
2 w3 H+ {* p1 s; Z4 ^7 N. G3 j) y9 A4 j9 d5 o/ Q, W$ h" A3 a
; a+ U" q5 t$ e% I! W( pstruct mtd_info *mtd_table[MAX_MTD_DEVICES];" h' L0 @! }* _! Q- z+ z, I: s+ c
2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。2 Z$ r& f" [! y. `# ^
! ?6 Y# X& }3 ?9 j
struct mtd_part {
% Y, _ z- Y; e* R( N3 P struct mtd_info mtd; /* 分区信息, 大部分由master决定 */) G0 l- X8 h; C Q) S/ [1 b
struct mtd_info *master; /* 分区的主分区 */
/ r' ^& u. F1 h4 z uint64_t offset; /* 分区的偏移地址 */0 `1 g/ J, p1 o/ W
int index; /* 分区号 (Linux3.0后不存在该字段) */6 m4 X1 b" @. W/ ?! W
struct list_head list; /* 将mtd_part链成一个链表mtd_partitons */
0 J- ~/ W$ i7 G9 C3 C int registered;
1 a# k0 [ o* Q H0 F" V, @};
* x% |" W$ W; o; ~ lmtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。
1 }1 p1 a+ L0 E+ e
4 v! z/ N) t. Y) i, }struct mtd_info {
, F+ Q* ~* ?/ y9 i. G/ A u_char type; /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */
/ |( ^9 c" i5 U0 W# A+ e uint32_t flags; /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */9 Q/ T! }( \/ t, q" w/ ?% V
uint64_t size; /* mtd设备的大小 */8 g8 {5 w1 x$ X9 {: Z& N
uint32_t erasesize; /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */
; p1 K+ f: K3 ]4 E, @ uint32_t writesize; /* 写大小, 对于norFlash是字节,对nandFlash为一页 */
( M# y' e) @" W4 N uint32_t oobsize; /* OOB字节数 */
& \; Z' h. m% U5 [& e# J- n3 G) q( ~ uint32_t oobavail; /* 可用的OOB字节数 */% x$ Z6 \3 {# b6 G2 f( f
unsigned int erasesize_shift; /* 默认为0,不重要 */
% O* z" j" ~3 L/ i" i: p) Z unsigned int writesize_shift; /* 默认为0,不重要 */: q+ K I2 \& ^
unsigned int erasesize_mask; /* 默认为1,不重要 */8 b( r7 Y" ^& C6 v
unsigned int writesize_mask; /* 默认为1,不重要 */
7 W( N+ E; b. p4 Q: a3 { const char *name; /* 名字, 不重要*/4 x1 Z, O: D4 m; d- y/ u
int index; /* 索引号,不重要 */
$ m- E$ o' y2 h; u% j& ^# `( y% w int numeraseregions; /* 通常为1 */: D( b7 V$ h0 {) o
struct mtd_erase_region_info *eraseregions; /* 可变擦除区域 */: w1 I" n* ^! x5 }' i0 A F6 r
8 Y0 F+ d: J) ]: P; V void *priv; /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */
+ F& F$ Q s( ~" g4 n+ F3 c* V% f struct module *owner; /* 一般设置为THIS_MODULE */
" A+ \5 @* h; w* H9 d8 n& P 0 K% b8 N& j, s7 E
/* 擦除函数 */7 X0 O5 t: J* S# W. U
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);" `$ C" q0 U; Q. f B7 X; I2 g _4 Q
7 h. R( O* _% }, P/ K& n* j
/* 读写flash函数 */& ?, P7 P% G5 ?
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);4 O5 K1 i! T; B- x
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);) f b, R S2 F8 c" s- I
* ]3 }% r& e; ]" e8 v /* 带oob读写Flash函数 */
$ @3 y" L, O+ a& Z+ z int (*read_oob) (struct mtd_info *mtd, loff_t from,/ N! \6 U, ]2 _7 q4 [0 f3 ^
struct mtd_oob_ops *ops);
. ^: V/ K L' H, J1 e% W int (*write_oob) (struct mtd_info *mtd, loff_t to,$ [) }2 U6 R6 S& ` G* d* u3 W- J
struct mtd_oob_ops *ops);" ~2 ]6 q, \: R0 d8 O2 R
: m$ t2 S s. k
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
& A' a9 u. P4 E: Z5 t( F int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
4 u! h$ N5 k2 T9 h int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
/ ?+ Q& m3 E: J) C* ~* w5 m int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);) s w6 o; |! o5 r9 U* r. V$ e
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
9 `: U' G* [1 D int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
3 v2 {2 F$ U: R; v) k
7 S5 t- H m3 Z/ }" ^) `2 U* I B int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);) D. s, ?/ |6 ^) B& G/ }* e
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
' f" q! t6 r5 z: Q. I: { /* Sync */( `9 z4 W% {3 Y. X, K8 N
void (*sync) (struct mtd_info *mtd);4 ]9 O1 o k! m6 ]( E, m
7 w/ Q8 Y5 v8 K s l5 m6 X) W* [ /* Chip-supported device locking */5 `# J, c/ X# B5 D- ?
int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);" F9 g V4 J6 J7 x8 \
int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);0 r3 D; e) P* d( e1 ~" h
@8 d* `/ r" M9 {& Q( G6 I- t
/* 电源管理函数 */
5 d/ i/ _6 Z# m7 R int (*suspend) (struct mtd_info *mtd);
9 R) [7 ]* {" G void (*resume) (struct mtd_info *mtd);
D( B) f3 O+ I) {& i7 {4 Q6 r0 G6 k: B# M8 \
/* 坏块管理函数 */
$ `: r- T6 ?3 ]5 t+ k int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);& |8 ^) W6 U0 x, b
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);; j, H, z/ t( s/ P+ O
: h1 N/ u7 \4 x, H. p
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);. ^: k8 i9 k, ]: a3 e% t4 g
unsigned long (*get_unmapped_area) (struct mtd_info *mtd,5 e$ V. x" ]& R G: d% ^& @
unsigned long len,7 i0 v7 r# S! J0 D2 @) ?: i
unsigned long offset,- g; i3 `6 O1 U7 G8 G9 R! k# t6 }* z+ {
unsigned long flags);- l6 j# a' v% a" x" s V* t: U P
struct backing_dev_info *backing_dev_info;6 P, P" A9 f% p# I! [
struct notifier_block reboot_notifier; /* default mode before reboot */
0 z) i6 b1 @3 ?: E
9 x f$ ~' p" _, F3 C# L /* ECC status information */
! @, i2 l3 h( C struct mtd_ecc_stats ecc_stats;
( T, K! S! ]& d4 t int subpage_sft; [2 U: F# A- m' J6 p1 _
struct device dev;* M4 x( N( P! ~# j. ~- e
int usecount;4 w6 z, y, `6 s# s6 M' r1 _
int (*get_device) (struct mtd_info *mtd);
2 D. g% m( V2 T void (*put_device) (struct mtd_info *mtd);
2 G* O ~) z# u( q: }' d9 G};
. a# c" ~! W: Z8 u6 R; omtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。4 d8 H0 D5 D+ b. H
如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。
f0 k( ~; i& S( g& I# z+ X; K0 t6 G; B% B8 {8 G
. Y1 u6 u' g" L4 d% l, xint add_mtd_device(struct mtd_info *mtd)5 `6 [ N P: J# P3 l. C% i
int del_mtd_device (struct mtd_info *mtd)
# K" p) ^4 Q) S. B! r如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。
6 i, J9 l& D5 P0 g0 ?int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)
3 {0 l' S1 _/ b) u7 m4 Oint del_mtd_partitions(struct mtd_info *master)
" B, ?4 x; e3 z其中mtd_partition结构体表示分区的信息 H B6 {$ g2 {# e8 x3 I' W$ {
struct mtd_partition {
4 j ?% D' k* I1 ~' R4 s- V6 } W char *name; /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */' ~; |7 Y' L3 j& n3 z3 {2 A2 {6 |, }
uint64_t size; /* 分区大小 */, Q: c3 ~! o) l+ h- ]# G# f- i
uint64_t offset; /* 分区偏移值 */4 v9 Z( O* t c' @' k
uint32_t mask_flags; /* 掩码标识,不重要 */4 F& _' L! n. m! E5 z: l, N
struct nand_ecclayout *ecclayout; /* OOB布局 */3 ]) {) t3 n. F: J3 A5 \
struct mtd_info **mtdp; /* pointer to store the MTD object */
- v! z* E S! @4 L- D};
1 A; w9 c$ U( e0 M2 ]) R& [, j其中nand_ecclayout结构体:& p7 r1 L' @. U7 ~
struct nand_ecclayout {# u! U) k, P0 H7 n9 \: F
__u32 eccbytes; /* ECC字节数 */3 W& D$ y% }$ V2 h
__u32 eccpos[64]; /* ECC校验码在OOB区域存放位置 */, A& W" i- x7 L* k
__u32 oobavail; ! p& o1 U2 M- J4 d
/* 除了ECC校验码之外可用的OOB字节数 */
. E1 d+ C# M7 Y4 b4 f- L struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
! L* r3 Y$ w( {) q& c};; F' J5 z/ d K, U, H) y% D
关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。
9 u+ H! U) G* z* u z2 y `, tMTD设备层:
/ f, x$ }; Z" ^4 ]! C$ n4 cmtd字符设备接口:6 F2 x. _: O# y8 K9 y9 B& i+ e' u
& c+ j% P b* s$ w* ^8 H0 W% X/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) ) L! j" J6 \" Q# s& ?* ~- w$ I2 `3 c
- ~% z0 K1 v! U; L! q
mtd块设备接口:
" p; d& v2 |+ j( e2 Z
5 D4 v: z" f7 T, ~4 E8 N% c' U/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。: }/ a( Y8 N+ _2 J( N. e
MTD硬件驱动层:
3 [$ v( S7 _& ^+ e8 Z
$ r- m( ^5 r& Y& ]3 F6 `. E9 q* d- cLinux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。
8 A y2 q9 A( V8 ~/ J
7 {6 G; h! e9 P" u) I! d2 y* EMTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。
9 r: }0 n3 f1 P/ x& R
) o5 p- g* Y% {, Z4 }' @
+ I& Y. G. j6 [struct nand_chip {
& ?& @# ^2 v' Z; P- s) L void __iomem *IO_ADDR_R; /* 读8位I/O线地址 */: k5 H4 u4 ^5 v0 l M b
void __iomem *IO_ADDR_W; /* 写8位I/O线地址 */
0 U+ g7 M5 t* |7 w; M8 n- T `, p( T! w9 c
/* 从芯片中读一个字节 */
1 O2 i- T' ^9 P/ q6 q; N& K& `# @* b$ d8 R uint8_t (*read_byte)(struct mtd_info *mtd); 1 o- z5 j# N3 z6 ]1 d d& F
/* 从芯片中读一个字 */9 Z' Z( ]. T ?& U4 c! T4 y; i
u16 (*read_word)(struct mtd_info *mtd);
2 q4 c# {& z0 A( i /* 将缓冲区内容写入芯片 */3 f+ a: l! L# K P2 C
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
! {% x( _9 Q2 E$ l /* 读芯片读取内容至缓冲区/ */: f' O: w$ y9 E3 W* H& f; I
void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);! [* v. y! f1 [ f% W) ?
/* 验证芯片和写入缓冲区中的数据 */# ?0 }: ?. t& \" x0 m. ?
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
: J8 e( R( y; c9 F# ~9 ~6 H /* 选中芯片 */
6 C; U. u# \' Z3 U7 Y' g: m void (*select_chip)(struct mtd_info *mtd, int chip);
( v, ^* P o0 o; K2 B /* 检测是否有坏块 */( y, X3 p; L0 {* j& [' z& t
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);- }0 v R* {" k
/* 标记坏块 */4 C! o7 p k: j
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
. y1 F" M8 {% C: @ /* 命令、地址、数据控制函数 */& Z7 }& y" d6 M
void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);) ^/ g E7 h( \) h% J# k" S
/* 设备是否就绪 */
/ g3 [, e+ I" k8 f1 e% { int (*dev_ready)(struct mtd_info *mtd);# P7 x( e% r+ X; D* z
/* 实现命令发送 */
& @/ f r. e0 E" p void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);) r* n$ j% M9 z3 \0 I
int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);. S- \) v$ K$ X/ ]. Y& Y
/* 擦除命令的处理 */
^% R' p, z5 Y: g void (*erase_cmd)(struct mtd_info *mtd, int page);
& S6 [' X$ U& Q; ]$ Y8 n8 M5 j4 I /* 扫描坏块 */
: h# l- _( R/ t8 I8 F int (*scan_bbt)(struct mtd_info *mtd);
7 ]6 m P8 V) Y8 a8 A" o int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);4 | u/ I/ t/ M' b. {7 ^
/* 写一页 */8 u" g& Z. O9 x+ p) ?8 O" k
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
5 q$ L0 z7 Z0 K! u4 i8 r6 _7 A8 B const uint8_t *buf, int page, int cached, int raw);
% |, v& Q: O5 H) Y( g; l9 X' |$ Q3 u
int chip_delay; /* 由板决定的延迟时间 */. {, V, f5 C$ K; H4 i, w( W v( ?% u
/* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */% L3 N+ a; X9 T( R w X5 w
unsigned int options;
9 ], W; n7 c' ?* f: x& {; ~- S$ _: p, j% [8 Q
/* 用位表示的NAND芯片的page大小,如某片NAND芯片8 q. l8 r. k% y, Q
* 的一个page有512个字节,那么page_shift就是9
" R/ G7 S& B! l. B */
0 M$ W* x% S% _4 { int page_shift;6 \+ E) N3 Q1 A* s, R7 g1 d: [; k& ?
/* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可
& s3 V1 P9 C/ | * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是145 G* D i' x- B5 A# x7 S
*/
$ e. [; v1 q } int phys_erase_shift;
' I- j; w! } E7 Q* N$ q /* 用位表示的bad block table的大小,通常一个bbt占用一个block,
. o7 Q( P' r! B' @/ d! W * 所以bbt_erase_shift通常与phys_erase_shift相等
6 Q9 Z( g' Y' b0 \+ @0 x# L */
7 W: x, [8 ]* ~0 r2 e; l* G int bbt_erase_shift;
3 J6 U4 w* M+ [ /* 用位表示的NAND芯片的容量 */8 a( C* L( I. a0 f4 i( ^
int chip_shift;
- S* l0 T1 M7 w' p+ m& F /* NADN FLASH芯片的数量 */% v4 f5 S3 ^$ B) z4 N+ Z2 `& {
int numchips;+ L- A: b7 r! A# w
/* NAND芯片的大小 */5 S! q- Y0 A& V ]
uint64_t chipsize;5 _0 ]- J1 R5 D5 B3 J
int pagemask;
( S6 g: i# S9 N int pagebuf;/ f5 L5 X, _2 h& l! C
int subpagesize;: y( P/ l+ g, m* \& k
uint8_t cellinfo;+ ^! U1 a% w4 G4 Y( i
int badblockpos;
- A4 t' M. { Y+ Y7 d* D9 L nand_state_t state;: ^' G ~& m6 [! D3 `
uint8_t *oob_poi;, e# i" N( t. f8 a: }8 r
struct nand_hw_control *controller;
1 [! s; |6 w6 F+ C. [+ Q struct nand_ecclayout *ecclayout; /* ECC布局 */: W+ j) Y) j1 n1 J& U
6 O3 ~$ H. g, r- b struct nand_ecc_ctrl ecc; /* ECC校验结构体,里面有大量的函数进行ECC校验 */
: U* J7 Y1 A4 p4 [5 @. p struct nand_buffers *buffers;
: v1 A, \) e1 O( `, |6 g struct nand_hw_control hwcontrol;' y$ {! x ]7 `5 q* s8 s
struct mtd_oob_ops ops; l& b( P$ b. y V' N. A( i# x1 X
uint8_t *bbt;
* \3 \# x% T2 k3 o r/ F6 j struct nand_bbt_descr *bbt_td; D5 L5 [9 b' c0 t7 G8 C
struct nand_bbt_descr *bbt_md;. h v- x6 X* K3 g7 a0 y* U
struct nand_bbt_descr *badblock_pattern;
9 B* E2 |$ m H% n void *priv;
4 W5 {6 E( E0 I8 P4 R};0 [6 T' H) Z3 i7 M" ]
) G7 S! R% p+ b0 G最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。0 ]7 v8 r8 C4 d$ ~ N* J
( V) k5 Q% i' G) P
5 a9 _, A. i7 z- ?$ K( }
; O- c" C5 I0 W3 }$ T
; ?' \9 D+ p' _ |
|