[toc]
MTD概述
MTD(memory technology device):内存技术设备,是linux用于描述ROM,NAND,NOR等设备的子系统的抽象,MTD设备可以按块读写也可以按字节读写,也就是说MTD设备既可以是块设备也可以是字符设备,块设备(mtdblackx)操作针对文件系统,字符设备(mtdx)操作主要针对格式化等操作的测试用。
如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。
- Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。
- MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c:MTD原始设备接口相关实现,mtdpart.c:MTD分区接口相关实现。
- MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中mtdchar.c:MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。
- 设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备。
MTD 数据结构
mtd_info
Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数,
描述一个设备或一个多分区设备中的一个分区。定义位于:includelinuxmtdmtd.h
本身是没有list_head来供内核管理,对mtd_info对象的管理是通过mtd_part来实现的。mtd_info对象属于原始设备层,里面的很多函数接口内核已经实现了。mtd_info中的read()/write()等操作是MTD设备驱动要实现的主要函数,在NORFlash或NANDFlash中的驱动代码中几乎看不到mtd_info的成员函数,即这些函数对于Flash芯片是透明的,因为Linux在MTD的下层实现了针对NORFlash和NANDFlash的通用的mtd_info函数。
struct mtd_info {
u_char type; //mtd类型,如:MTD_NORFLASH(See:mtd-abi.h)
uint32_t flags;// 标志位, MTD_WRITEABLE、MTD_NO_ERASE等(See mtd-abi.h)
uint64_t size; // Total size of the MTD(mtd设备的大小)
/* "Major" erase size for the device. Na茂ve users may take this
* to be the only erase size available, or may use the more detailed
* information below if they desire
*/
uint32_t erasesize;//擦除大小,即flash的块大小(mtd设备可能有多个erasesize)
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
* it is of ECC block size, etc. It is illegal to have writesize = 0.
* Any driver registering a struct mtd_info must ensure a writesize of
* 1 or larger.
*/
uint32_t writesize;// 写大小, 对于norFlash是字节,对nandFlash为一页
/*
* Size of the write buffer used by the MTD. MTD devices having a write
* buffer can write multiple writesize chunks at a time. E.g. while
* writing 4 * writesize bytes to a device with 2 * writesize bytes
* buffer the MTD driver can (but doesn't have to) do 2 writesize
* operations, but not 4. Currently, all NANDs have writebufsize
* equivalent to writesize (NAND page size). Some NOR flashes do have
* writebufsize greater than writesize.
*/
uint32_t writebufsize;
uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
uint32_t oobavail; // Available OOB bytes per block
/*
* If erasesize is a power of 2 then the shift is stored in
* erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
*/
unsigned int erasesize_shift; //默认为0,不重要
unsigned int writesize_shift; //默认为0,不重要
/* Masks based on erasesize_shift and writesize_shift */
unsigned int erasesize_mask; //默认为1,不重要
unsigned int writesize_mask; //默认为1,不重要
/*
* read ops return -EUCLEAN if max number of bitflips corrected on any
* one region comprising an ecc step equals or exceeds this value.
* Settable by driver, else defaults to ecc_strength. User can override
* in sysfs. N.B. The meaning of the -EUCLEAN return code has changed;
* see Documentation/ABI/testing/sysfs-class-mtd for more detail.
*/
unsigned int bitflip_threshold;
// Kernel-only stuff starts here.
const char *name; //mtd设备名
int index;
/* OOB layout description */
//nand_ecclayout结构体指针, 表示的是ecc布局,可参考硬件手册的OOB中ecc布局
const struct mtd_ooblayout_ops *ooblayout;
/* NAND pairing scheme, only provided for MLC/TLC NANDs */
const struct mtd_pairing_scheme *pairing;
/* the ecc step size. */
unsigned int ecc_step_size;
/* max number of correctible bit errors per ecc step */
unsigned int ecc_strength;
/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;//通常为1
struct mtd_erase_region_info *eraseregions;//可变擦除区域
/*
* Do not call via these pointers, use corresponding mtd_*()
* wrappers instead.
*/
/* flash擦除函数 */
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags);
/* flash读写函数 */
int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
/* 带oob读写flash函数 */
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
//下面是保护区域的操作函数
int (*_get_fact_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_get_user_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
size_t len, size_t *retlen, u_char *buf);
int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len);
int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
/* 同步函数 */
void (*_sync) (struct mtd_info *mtd);
int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
/* 坏块管理函数 */
int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs); //检查坏块
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);//标记坏块
int (*_max_bad_blocks) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* 电源管理函数 */
int (*_suspend) (struct mtd_info *mtd); //挂起函数
void (*_resume) (struct mtd_info *mtd);//恢复函数
void (*_reboot) (struct mtd_info *mtd);//重启函数
/*
* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
*/
int (*_get_device) (struct mtd_info *mtd);
void (*_put_device) (struct mtd_info *mtd);
struct notifier_block reboot_notifier; /* default mode before reboot */
/* ECC status information */
struct mtd_ecc_stats ecc_stats;//ECC状态
/* Subpage shift (NAND) */
int subpage_sft;
//私有数据, cfi接口flash指向map_info结构, 或指向自定义flash相关结构体
void *priv;
struct module *owner;
struct device dev;
int usecount;
struct mtd_debug_info dbg;
};
mtd_part
Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。
struct mtd_part {
struct mtd_info mtd; //分区信息,大部分来自master
struct mtd_info *master;//分区的主分区
uint64_t offset; //分区的偏移地址
struct list_head list; //链接到mtd_partitions链表中
};
mtd info master与slaver的区别
- 一个master mtd_info对应一个闪存设备;
- 一个slaver mtd_info对应一个闪存设备的逻辑分区(若没有逻辑分区,则不存在该变量)
- 若一个闪存设备没有进行逻辑分区,则会将该master mtd_info注册至mtd子系统中,并创建一个master mtd_info对应的字符设备、块设备;
- 若一个闪存设备进行逻辑分区,则每一个逻辑分区对应的slaver mtd_info均注册至mtd子系统中,并创建该slaver
mtd_info对应的字符设备与块设备,而master mtd_info不注册至mtd子系统中;此时slaver
mtd_info并没有实现对芯片驱动的访问接口,而对下层闪存芯片驱动的访问接口还是由master
mtd_info中的接口实现访问操作;而slaver mtd_info中的接口主要即是对master
mtd_info的简单封装而已。还需要将每一个逻辑分区对应的mtd_part变量注册至mtd_partitions链表中。
mtd_partition
该结构体表示某一分区的分区信息,其将被添加到mtd_partitions链表中。
struct mtd_partition {
const char *name; /* identifier string */
const char *const *types; /* names of parsers to use if any */
uint64_t size; /* partition size */
uint64_t offset; /* offset within the master MTD space */
uint32_t mask_flags; /* master MTD flags to mask out for this partition */
struct device_node *of_node;
};
map_info
struct map_info {
const char *name; //名称
unsigned long size; //大小
resource_size_t phys; //物理地址
#define NO_XIP (-1UL)
void __iomem *virt; //虚拟地址,通常通过ioremap将物理地址映射得到的
void *cached;
int swap; /* this mapping's byte-swapping requirement */
int bankwidth; /* in octets. This isn't necessarily the width
of actual bus cycles -- it's the repeat interval
in bytes, before you are talking to the first chip again.
*/
//读写函数
#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
map_word (*read)(struct map_info *, unsigned long);
void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
void (*write)(struct map_info *, const map_word, unsigned long);
void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
/* We can perhaps put in 'point' and 'unpoint' methods, if we really
want to enable XIP for non-linear mappings. Not yet though. */
#endif
/* It's possible for the map driver to use cached memory in its
copy_from implementation (and _only_ with copy_from). However,
when the chip driver knows some flash area has changed contents,
it will signal it to the map driver through this routine to let
the map driver invalidate the corresponding cache as needed.
If there is no cache to care about this can be set to NULL. */
void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
/* This will be called with 1 as parameter when the first map user
* needs VPP, and called with 0 when the last user exits. The map
* core maintains a reference counter, and assumes that VPP is a
* global resource applying to all mapped flash chips on the system.
*/
void (*set_vpp)(struct map_info *, int);
unsigned long pfow_base;
unsigned long map_priv_1; //驱动可用的私有数据
unsigned long map_priv_2;
struct device_node *device_node;
void *fldrv_priv;
struct mtd_chip_driver *fldrv;
};
nand_chip
struct nand_chip {
void __iomem *IO_ADDR_R; //读写8根io线的地址
void __iomem *IO_ADDR_W;
uint8_t (*read_byte)(struct mtd_info *mtd); //读一个字节
u16 (*read_word)(struct mtd_info *mtd); //读一个字
//将缓冲区内容写入芯片
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
//将芯片内容读取至缓冲区
void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
//验证芯片写入缓冲区的数据
int (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
//片选芯片
void (*select_chip)(struct mtd_info *mtd, int chip);
//检测是否有坏块
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
//坏块标记
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
//命令、地址、数据控制函数
void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl);
int (*init_size)(struct mtd_info *mtd, struct nand_chip *this,
u8 *id_data);
//检查设备是否就绪
int (*dev_ready)(struct mtd_info *mtd);
//实现命令发送
void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column,
int page_addr);
int(*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
//擦除命令处理
void (*erase_cmd)(struct mtd_info *mtd, int page);
//扫描坏块
int (*scan_bbt)(struct mtd_info *mtd);
int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state,
int status, int page);
//写一页
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int page, int cached, int raw);
int chip_delay; //有板决定延迟时间
unsigned int options;
unsigned int bbt_options;
int page_shift; //表示page的大小,如Nand的页大小为512byte,则page_shift为9
int phys_erase_shift; //擦除块的大小
/* 用位表示的bad block table的大小,通常一个bbt占用一个block,
所 以bbt_erase_shift通常与phys_erase_shift相等 */
int bbt_erase_shift;
int chip_shift; //芯片容量
int numchips; //芯片数量
uint64_t chipsize; //芯片大小
int pagemask;
int pagebuf;
int subpagesize;
uint8_t cellinfo;
int badblockpos;
int badblockbits;
int onfi_version;
struct nand_onfi_params onfi_params;
flstate_t state;
uint8_t *oob_poi;
struct nand_hw_control *controller;
struct nand_ecclayout *ecclayout; //ECC布局
struct nand_ecc_ctrl ecc; //ECC校验结构体,含有大量ECC校验的函数
struct nand_buffers *buffers;
struct nand_hw_control hwcontrol;
uint8_t *bbt;
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;
struct nand_bbt_descr *badblock_pattern;
void *priv;
};
数据结构关联
针对mtd设备驱动层,主要涉及struct mtd_partition、struct mtd_part、struct mtd_info这几个主要的数据结构。
mtd_partition用于进行闪存芯片的分区定义,针对不支持设备树的内核,则一般在开发板对应的板级文件中定义该结构体类型变量的定义,用于说明本芯片的分区情况;针对支持设备树的内核,一般在设备树文件中定义分区信息,然后在芯片对应的驱动文件中解析该分区定义;
mtd_part,主要由mtd设备驱动模型内部使用的分区信息,该结构体中包括本分区对应的mtd_info类型的变量以及指向主mtd_info的指针。系统中所有已注册的struct mtd_part变量,均链接至链表mtd_partitions上。
mtd_info,该结构体是mtd设备驱动模型最主要的数据结构,通过该数据结构,对上完成与mtd接口层的关联;对下完成与具体类型闪存芯片驱动的关联(如针对nand flash ),则通过mtd_info->priv=nand_chip,完成与nandflash的关联;针对nor flash,则同样通过mtd_info->priv=map_info完成关联;而针对其他类型的芯片,则同样是通过mtd_info->priv的关联),通过该结构体中的_erase、_read、_write等函数指针,完成针对下层设备驱动操作接口的抽象,完成对下层设备驱动接口的抽象模型的建立。
MTD分区添加
在mtd设备的底层驱动匹配函数中会调用
mtd_device_parse_register
注册分区。
这个函数会在parse_mtd_partitions中判断通过哪一种方式进行分区,获取real_parts。
int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
struct mtd_part_parser_data *parser_data,
const struct mtd_partition *parts,
int nr_parts)
{
int err;
struct mtd_partition *real_parts;
err = parse_mtd_partitions(mtd, types, &real_parts, parser_data);
if (err <= 0 && nr_parts && parts) {
real_parts = kmemdup(parts, sizeof(*parts) * nr_parts,
GFP_KERNEL);
if (!real_parts)
err = -ENOMEM;
else
err = nr_parts;
}
if (err > 0) {
err = add_mtd_partitions(mtd, real_parts, err);
kfree(real_parts);
} else if (err == 0) {
err = add_mtd_device(mtd);
if (err == 1)
err = -ENODEV;
}
/*
* FIXME: some drivers unfortunately call this function more than once.
* So we have to check if we've already assigned the reboot notifier.
*
* Generally, we can make multiple calls work for most cases, but it
* does cause problems with parse_mtd_partitions() above (e.g.,
* cmdlineparts will register partitions more than once).
*/
if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
register_reboot_notifier(&mtd->reboot_notifier);
}
return err;
}
static const char * const default_mtd_part_types[] = {
"cmdlinepart",
"ofpart",
NULL
};
int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct mtd_part_parser *parser;
int ret = 0;
if (!types)
types = default_mtd_part_types;
for ( ; ret <= 0 && *types; types++) {
parser = get_partition_parser(*types);
if (!parser && !request_module("%s", *types))
parser = get_partition_parser(*types);
if (!parser)
continue;
ret = (*parser->parse_fn)(master, pparts, data);
put_partition_parser(parser);
if (ret > 0) {
printk(KERN_NOTICE "%d %s partitions found on MTD device %sn",
ret, parser->name, master->name);
break;
}
}
return ret;
}
内核直接添加
在内核中添加分区表是就内核常用的方法,主要是在平台设备中添加mtd_partion,如下:
struct mtd_partition m25p80_part[] = {
{
.name = "Bootloader",
.offset = 0,
.size = (1 * SZ_1M),
.mask_flags = MTD_CAP_NANDFLASH,
},
{
.name = "Kernel",
.offset = (1 * SZ_1M),
.size = (31 * SZ_1M) ,
.mask_flags = MTD_CAP_NANDFLASH,
},
{
.name = "User",
.offset = (32 * SZ_1M),
.size = (16 * SZ_1M) ,
},
{
.name = "File System",
.offset = (48 * SZ_1M),
.size = (96 * SZ_1M),
}
};
static struct flash_platform_data m25p80_platform_data[] = {
[0] = {
.name = "m25p80",
.nr_parts = ARRAY_SIZE(m25p80_part),
.parts = m25p80_part,
},
};
static struct spi_board_info xxx_spi_nor_device[] = {
{
.modalias = "m25p80", //spi设备名字,设备驱动探测时会用到该项
.max_speed_hz = 25000000,
.bus_num = 1,
.chip_select = 1,//该spi设备的片选编号
.mode = SPI_MODE_0, //此spi设备支持spi总线的工作模式
.platform_data = &m25p80_platform_data, //存放flash分区表
},
{},
};
传参添加
在u-boot或device_tree可以通过添加mtdparts信息到bootargs中,u-boot启动后会将bootargs中的信息传送给kernel,,kernel在启动的时候会解析bootargs中mtdparts的部分,这边举个例子:
mtdparts=spi0.0:......
为了使kernel能够解析mtdparts信息,我们需要将内核中的Device Drivers -> Memory Technology Device (MTD) support ->Command line partition table parsing选项开启。
dts传参的原理其实和u-boot一样,区别在于:u-boot的时候是通过cmdlinepart.c文件实现分区信息写入LIST_HEAD(mtd_partitions)链表,dts则是用过ofpart.c文件实现分区信息写入LIST_HEAD
mtd_partitions)链表,所以同样要把ofpart.c文件的宏打开,在调用mtd_device_parse_register(mtd,
probe_types,&ppdata, NULL, 0);函数的时候types要设置成ofpart。
&spi0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0_0>;
num-cs = <1>;
ranges = <0 0x08000000>;
nor_flash@0 {
partition@1 {
label = "Bootloader";
reg = <0x00000000 0x00010000>;
};
partition@2 {
label = "Kernel";
reg = <0x00100000 0x002000000>;
};
partition@3 {
label = "User";
reg = <0x002000000 0x03000000>;
};
partition@4 {
label = "File System";
reg = <0x03000000 0x08000000>;
};
};
};
MTD写保护
部分mtd设备是支持硬件分区上锁的可以调用mtd_info中的lock接口上锁,但是不是所有设备都支持,同时mtd可以通过mtd_info中的flags中的1个bit进行写保护。
mtd写保护实现方式
mtd->flags &=~MTD_WRITEABLE
mtd_write写保护
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf)
{
*retlen = 0;
if (to < 0 || to >= mtd->size || len > mtd->size - to)
return -EINVAL;
if (!mtd->_write || !(mtd->flags & MTD_WRITEABLE))
return -EROFS;
if (!len)
return 0;
return mtd->_write(mtd, to, len, retlen, buf);
}
字符节点写保护
static int mtdchar_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
int devnum = minor >> 1;
int ret = 0;
struct mtd_info *mtd;
struct mtd_file_info *mfi;
pr_debug("MTD_openn");
/* You can't open the RO devices RW */
if ((file->f_mode & FMODE_WRITE) && (minor & 1))
return -EACCES;
mutex_lock(&mtd_mutex);
mtd = get_mtd_device(NULL, devnum);
if (IS_ERR(mtd)) {
ret = PTR_ERR(mtd);
goto out;
}
if (mtd->type == MTD_ABSENT) {
ret = -ENODEV;
goto out1;
}
/* You can't open it RW if it's not a writeable device */
if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
ret = -EACCES;
goto out1;
}
mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
if (!mfi) {
ret = -ENOMEM;
goto out1;
}
mfi->mtd = mtd;
file->private_data = mfi;
mutex_unlock(&mtd_mutex);
return 0;
out1:
put_mtd_device(mtd);
out:
mutex_unlock(&mtd_mutex);
return ret;
} /* mtdchar_open */
块设备写保护
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
struct mtdblk_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return;
dev->mbd.mtd = mtd;
dev->mbd.devnum = mtd->index;
dev->mbd.size = mtd->size >> 9;
dev->mbd.tr = tr;
if (!(mtd->flags & MTD_WRITEABLE))
dev->mbd.readonly = 1;
if (add_mtd_blktrans_dev(&dev->mbd))
kfree(dev);
}
留言