Flash 在嵌入式系统中必不可少,Flash是 BootLoader、Linux 内核和文件系统的最佳载体。在 Linux 内核中,引入 MTD(内存技术设备) 层为 NOR Flash 和NAND Flash 设备提供统一的接口,使得 Flash 驱动的设计工作大为简化。
1、 Linux Flash 驱动的结构,主要讲解MTD 系统的层次结构和接口。
2、讲解NOR Flash 和 NAND Flash 驱动的设计方法,给出设计模板。
3、以 S3C6410 外围 NOR Flash 和 NAND Flash 为实例讲解NOR Flash 和 NAND Flash 驱动的设计。
4、如何在 Flash 上建立 cramfs、jffs/jffs2、yaffs/yaffs2 及ubifs 文件系统。
19.1 Linux Flash驱动结构
19.1.1 Linux MTD 系统层次
在 Linux 系统中,提供 MTD(MemoryTechnology Device,内存技术设备)系统来建立Flash 针对 Linux 的统一、抽象的接口。MTD 将文件系统与底层的 Flash 存储器进行隔离,使Flash 驱动工程师无须关心 Flash 作为字符设备和块设备与 Linux 内核的接口。
如图 19.1 所示,在引入 MTD 后,Linux系统中的 Flash 设备驱动及接口可分为 4 层,从上到下依次是:设备节点、MTD 设备层、MTD 原始设备层和硬件驱动层,这 4 层的作用如下。
图19.1 Linux MTD 系统
1、硬件驱动层:Flash 硬件驱动层负责 Flash 硬件设备的读、写、擦除,Linux MTD 设备的NOR Flash 芯片驱动位于 drivers/mtd/chips 子目录下,NAND 型 Flash 的驱动程序则位于drivers/mtd/nand 子目录下。
2、MTD 原始设备层:MTD 原始设备层由两部分组成,一部分是 MTD 原始设备的通用代码,另一部分是各个特定的 Flash 的数据,例如分区。
3、MTD 设备层:基于 MTD 原始设备,Linux 系统可以定义出 MTD 的块设备(主设备号31)和字符设备(设备号 90),构成 MTD 设备层。MTD 字符设备的定义在 mtdchar.c中实现,通过注册一系列 file_operation 函数(lseek、open、close、read、write、ioctl)可实现对 MTD 设备的读写和控制。MTD 块设备定义了一个描述 MTD 块设备的结构 mtdblk_dev,并声明一个名为 mtdblks 的指针数组,这数组中的每一个 mtdblk_dev
和 mtd_table 中的每一个 mtd_info 一一对应。
4、设备节点:通过 mknod 在/dev 子目录下建立 MTD 字符设备节点(主设备号为 90)和 MTD块设备节点(主设备号为31),用户通过访问此设备节点即可访问MTD 字符设备和块设备。
19.1.2 Linux MTD 系统接口
如图 19.2 所示,在引入 MTD 后,底层 Flash 驱动直接与 MTD 原始设备层交互,利用其提供的接口注册设备和分区。
图19.2 底层 Flash 驱动
描述 MTD 原始设备的数据结构是 mtd_info,定义了大量关于 MTD 的数据和操作函数,如代码清单 19.1 所示。mtd_info 是表示 MTD 原始设备的结构体,每个分区也被认为是一个 mtd_info,例如,如果有两个MTD 原始设备,每个上有 3 个分区,在系统中就有 6 个 mtd_info 结构体,这些 mtd_info 的指针被存放在名为 mtd_table 的数组里。
代码清单 19.1 mtd_info 结构体
include/linux/mtd/mtd.h
struct mtd_info {
u_char type;/* 内存技术的类型 */
u_int32_t flags;/*标志位*/
u_int32_t size; /*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
*/
u_int32_t 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.
*/
u_int32_t writesize;/*最小的可写单元的字节数*/
u_int32_t oobsize; /* OOB 字节数*/
u_int32_t oobavail; /*可用的 OOB 字节数*/
// Kernel-only stuff starts here.
char *name;
int index;
/* ecc layout structure pointer - read only ! */
struct nand_ecclayout *ecclayout;/*ECC 布局结构体指针*/
/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
/* This stuff for eXecute-In-Place */
int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);
/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);
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 (*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);
/*
* Methods to access the protection register area, present in some
* flash devices. The user data is one time programmable but the
* factory data is read only.
*/
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
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, struct otp_info *buf, size_t len);
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 from, size_t len, size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
/* kvec-based read/write methods.
NB: The 'count' parameter is the number of _vectors_, each of
which contains an (ofs, len) tuple.
*/
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
/* Sync */
void (*sync) (struct mtd_info *mtd);
/* Chip-supported device locking */
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* Power Management functions */
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
/* Bad block management functions */
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
struct notifier_block reboot_notifier; /* default mode before reboot */
/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;
void *priv;
struct module *owner;
int usecount;
/* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
* The driver may register its callbacks. These callbacks are not
* supposed to be called by MTD users */
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};
mtd_info 的 type 字段给出底层物理设备的类型,包括 MTD_RAM、MTD_ROM、MTD_NORFLASH、MTD_NANDFLASH 等。
flags 字段标志可以是 MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE、
MTD_POWERUP_LOCK 等的组合。针对 ROM 而言,不具有上述任何属性,因此 MTD_CAP_ROM定义为 0;MTD_CAP_RAM 是 MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE的组合;MTD_CAP_NORFLASH 是 MTD_WRITEABLE、MTD_BIT_WRITEABLE 的组合,对
MTD_CAP_NANDFLASH 则仅意味着 MTD_WRITEABLE。
mtd_info 中的 read()、write()、read_oob()、write_oob()、erase()是 MTD 设备驱动要实现的主要函数,在 NOR 和 NAND 的驱动代码中几乎看不到 mtd_info 的成员函数,这是因为 Linux 在 MTD 的下层实现了针对 NORFlash 和 NAND Flash 的通用的 mtd_info 成员函数。
某些内存技术支持带外数据(OOB),例如,NAND Flash 每 512 字节就会有 16 个字节的“额外数据”,用于存放纠错码或文件系统元数据。这是因为,所有 Flash 器件都受“位翻转”现象的困扰,而 NAND 发生的概率比 NOR 大,因此 NAND 厂商推荐在使用 NAND 的时候最好要使用ECC(Error Checking and Correcting)。mtd_info 的 ecclayout 类型即是描述 OOB 区域中 ECC 字节的布局情况。
Flash 驱动中使用如下两个函数注册和注销 MTD 设备:
include/linux/mtd/mtd.h
int add_mtd_device(struct mtd_info *mtd);
int del_mtd_device (struct mtd_info *mtd);
drivers/mtd/mtdcore.c
/**
* add_mtd_device - register an MTD device
* @mtd: pointer to new MTD device info structure
*
* Add a device to the list of MTD devices present in the system, and
* notify each currently active MTD 'user' of its arrival. Returns
* zero on success or 1 on failure, which currently will only happen
* if the number of present devices exceeds MAX_MTD_DEVICES (i.e. 16)
*/
int add_mtd_device(struct mtd_info *mtd)
{
int i;
BUG_ON(mtd->writesize == 0);
mutex_lock(&mtd_table_mutex);
for (i=0; i < MAX_MTD_DEVICES; i++)
if (!mtd_table[i]) {
struct list_head *this;
mtd_table[i] = mtd;
mtd->index = i;
mtd->usecount = 0;
/* Some chips always power up locked. Unlock them now */
if ((mtd->flags & MTD_WRITEABLE)
&& (mtd->flags & MTD_STUPID_LOCK) && mtd->unlock) {
if (mtd->unlock(mtd, 0, mtd->size))
printk(KERN_WARNING "%s: unlock failed, "
"writes may not work\n",
mtd->name);
}
DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
list_for_each(this, &mtd_notifiers) {
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);// 获取指向该结构体的指针
not->add(mtd);
}
mutex_unlock(&mtd_table_mutex);
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
either. :) */
__module_get(THIS_MODULE);
return 0;
}
mutex_unlock(&mtd_table_mutex);
return 1;
}
/**
* del_mtd_device - unregister an MTD device
* @mtd: pointer to MTD device info structure
*
* Remove a device from the list of MTD devices present in the system,
* and notify each currently active MTD 'user' of its departure.
* Returns zero on success or 1 on failure, which currently will happen
* if the requested device does not appear to be present in the list.
*/
int del_mtd_device (struct mtd_info *mtd)
{
int ret;
mutex_lock(&mtd_table_mutex);
if (mtd_table[mtd->index] != mtd) {
ret = -ENODEV;
} else if (mtd->usecount) {
printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
mtd->index, mtd->name, mtd->usecount);
ret = -EBUSY;
} else {
struct list_head *this;
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
list_for_each(this, &mtd_notifiers) {
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);//获取指向该结构体的指针
not->remove(mtd);
}
mtd_table[mtd->index] = NULL;
module_put(THIS_MODULE);
ret = 0;
}
mutex_unlock(&mtd_table_mutex);
return ret;
}
代码清单 19.2 所示的 mtd_part 结构体用于表示分区,其 mtd_info 结构体成员用于描述该分区,它会被加入到 mtd_table中,其大部分成员由其主分区 mtd_part→master 决定,各种函数也指向主分区的相应函数。
代码清单 19.2 mtd_part 结构体
drivers/mtd/mtdpart.c
struct mtd_part {
struct mtd_info mtd;/*分区的信息(大部分由其 master 决定)*/
struct mtd_info *master;/*该分区的主分区*/
u_int32_t offset;/*该分区的偏移地址*/
int index;/*分区号*/
struct list_head list;
int registered;
};
mtd_partition 会在 MTD 原始设备层调用 add_mtd_partions()时传递分区信息用,这个结构体的定义如代码清单 19.3 所示。
代码清单 19.3 mtd_partition 结构体
include/linux/mtd/partitions.h
/*
* Partition definition structure:
*
* An array of struct partition is passed along with a MTD object to
* add_mtd_partitions() to create them.
*
* For each partition, these fields are available:
* name: string that will be used to label the partition's MTD device.
* size: the partition size; if defined as MTDPART_SIZ_FULL, the partition
* will extend to the end of the master MTD device.
* offset: absolute starting position within the master MTD device; if
* defined as MTDPART_OFS_APPEND, the partition will start where the
* previous one ended; if MTDPART_OFS_NXTBLK, at the next erase block.
* mask_flags: contains flags that have to be masked (removed) from the
* master MTD flag set for the corresponding MTD partition.
* For example, to force a read-only partition, simply adding
* MTD_WRITEABLE to the mask_flags will do the trick.
*
* Note: writeable partitions require their size and offset be
* erasesize aligned (e.g. use MTDPART_OFS_NEXTBLK).
*/
struct mtd_partition {
char *name; /* 标识字符串 */
u_int32_t size; /* 分区大小 */
u_int32_t offset; /* 主 MTD 空间内的偏移*/
u_int32_t mask_flags; /* 掩码标志 */
struct nand_ecclayout *ecclayout; /* out of band layout for this partition (NAND only)*/
struct mtd_info **mtdp; /* pointer to store the MTD object */
};
Flash 驱动中使用如下两个函数注册和注销分区:
include/linux/mtd/partitions.h
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
int del_mtd_partitions(struct mtd _ info *master);
drivers/mtd/mtdpart.c
int add_mtd_partitions(struct mtd_info *master,
const struct mtd_partition *parts,
int nbparts)
{
struct mtd_part *slave;
u_int32_t cur_offset = 0;
int i;
printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
for (i = 0; i < nbparts; i++) {
/* allocate the partition structure */
slave = kzalloc (sizeof(*slave), GFP_KERNEL);
if (!slave) {
printk ("memory allocation error while creating partitions for \"%s\"\n",
master->name);
del_mtd_partitions(master);
return -ENOMEM;
}
list_add(&slave->list, &mtd_partitions);
/* set up the MTD object for this partition */
slave->mtd.type = master->type;
slave->mtd.flags = master->flags & ~parts[i].mask_flags;
slave->mtd.size = parts[i].size;
slave->mtd.writesize = master->writesize;
slave->mtd.oobsize = master->oobsize;
slave->mtd.oobavail = master->oobavail;
slave->mtd.subpage_sft = master->subpage_sft;
slave->mtd.name = parts[i].name;
slave->mtd.owner = master->owner;
slave->mtd.read = part_read;
slave->mtd.write = part_write;
if(master->point && master->unpoint){
slave->mtd.point = part_point;
slave->mtd.unpoint = part_unpoint;
}
if (master->read_oob)
slave->mtd.read_oob = part_read_oob;
if (master->write_oob)
slave->mtd.write_oob = part_write_oob;
if(master->read_user_prot_reg)
slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
if(master->read_fact_prot_reg)
slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
if(master->write_user_prot_reg)
slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
if(master->lock_user_prot_reg)
slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg;
if(master->get_user_prot_info)
slave->mtd.get_user_prot_info = part_get_user_prot_info;
if(master->get_fact_prot_info)
slave->mtd.get_fact_prot_info = part_get_fact_prot_info;
if (master->sync)
slave->mtd.sync = part_sync;
if (!i && master->suspend && master->resume) {
slave->mtd.suspend = part_suspend;
slave->mtd.resume = part_resume;
}
if (master->writev)
slave->mtd.writev = part_writev;
if (master->lock)
slave->mtd.lock = part_lock;
if (master->unlock)
slave->mtd.unlock = part_unlock;
if (master->block_isbad)
slave->mtd.block_isbad = part_block_isbad;
if (master->block_markbad)
slave->mtd.block_markbad = part_block_markbad;
slave->mtd.erase = part_erase;
slave->master = master;
slave->offset = parts[i].offset;
slave->index = i;
if (slave->offset == MTDPART_OFS_APPEND)
slave->offset = cur_offset;
if (slave->offset == MTDPART_OFS_NXTBLK) {
slave->offset = cur_offset;
if ((cur_offset % master->erasesize) != 0) {
/* Round up to next erasesize */
slave->offset = ((cur_offset / master->erasesize) + 1) * master->erasesize;
printk(KERN_NOTICE "Moving partition %d: "
"0x%08x -> 0x%08x\n", i,
cur_offset, slave->offset);
}
}
if (slave->mtd.size == MTDPART_SIZ_FULL)
slave->mtd.size = master->size - slave->offset;
cur_offset = slave->offset + slave->mtd.size;
printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
slave->offset + slave->mtd.size, slave->mtd.name);
/* let's do some sanity checks */
if (slave->offset >= master->size) {
/* let's register it anyway to preserve ordering */
slave->offset = 0;
slave->mtd.size = 0;
printk ("mtd: partition \"%s\" is out of reach -- disabled\n",
parts[i].name);
}
if (slave->offset + slave->mtd.size > master->size) {
slave->mtd.size = master->size - slave->offset;
printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n",
parts[i].name, master->name, slave->mtd.size);
}
if (master->numeraseregions>1) {
/* Deal with variable erase size stuff */
int i;
struct mtd_erase_region_info *regions = master->eraseregions;
/* Find the first erase regions which is part of this partition. */
for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++)
;
for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) {
if (slave->mtd.erasesize < regions[i].erasesize) {
slave->mtd.erasesize = regions[i].erasesize;
}
}
} else {
/* Single erase size */
slave->mtd.erasesize = master->erasesize;
}
if ((slave->mtd.flags & MTD_WRITEABLE) &&
(slave->offset % slave->mtd.erasesize)) {
/* Doesn't start on a boundary of major erase size */
/* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */
slave->mtd.flags &= ~MTD_WRITEABLE;
printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
parts[i].name);
}
if ((slave->mtd.flags & MTD_WRITEABLE) &&
(slave->mtd.size % slave->mtd.erasesize)) {
slave->mtd.flags &= ~MTD_WRITEABLE;
printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
parts[i].name);
}
slave->mtd.ecclayout = master->ecclayout;
if (master->block_isbad) {
uint32_t offs = 0;
while(offs < slave->mtd.size) {
if (master->block_isbad(master,
offs + slave->offset))
slave->mtd.ecc_stats.badblocks++;
offs += slave->mtd.erasesize;
}
}
if(parts[i].mtdp)
{ /* store the object pointer (caller may or may not register it */
*parts[i].mtdp = &slave->mtd;
slave->registered = 0;
}
else
{
/* register our partition */
add_mtd_device(&slave->mtd);
slave->registered = 1;
}
}
return 0;
}
int del_mtd_partitions(struct mtd_info *master)
{
struct list_head *node;
struct mtd_part *slave;
for (node = mtd_partitions.next;
node != &mtd_partitions;
node = node->next) {
slave = list_entry(node, struct mtd_part, list);
if (slave->master == master) {
struct list_head *prev = node->prev;
__list_del(prev, node->next);
if(slave->registered)
del_mtd_device(&slave->mtd);
kfree(slave);
node = prev;
}
}
return 0;
}
分析:
为了使系统能支持 MTD 字符设备与块设备及 MTD 分区,在编译内核时应该包括相应的配置选项,如下所示。