MTD情景分析
所有的NOR型Flash的驱动(探测probe)程序都放在drivers/mtd/chips下,一个MTD原始设备可以由一块或者数块相同的Flash芯片组成。假设由4块devicetype为x8的Flash,每块大小为8M,interleave为2,起始地址为0x01000000,地址相连,则构成一个MTD原始设备(0x01000000-0x03000000),其中两块interleave成一个chip,其地址从0x01000000到0x02000000,另两块interleave成一个chip,其地址从0x02000000到0x03000000。
请注意,所有组成一个MTD原始设备的Flash芯片必须是同类型的(无论是
interleave还是地址相连),在描述MTD原始设备的数据结构中也只是采用了同一个结构来描述组成它的Flash芯片。
每个MTD原始设备都有一个mtd_info结构,其中的priv指针指向一个map_info结构,map_info结构中的fldrv_priv指向一个cfi_private结构,cfi_private结构的cfiq指针指向一个cfi_ident结构,chips指针指向一个flchip结构的数组。其中mtd_info、map_info和cfi_private结构用于描述MTD原始设备;因为组成MTD原始设备的NOR型Flash相同,cfi_ident结构用于描述Flash芯片的信息;而flchip结构用于描述每个Flash芯片的专有信息(比如说起始地址)
总的来说,嵌入式系统中一般来说会有一块或多块连续的NOR flash或NAND flash空间(每一个可能是多块相同的芯片来构成)每一个这样的空间被看成一个MTD原始设备(我不知道这个名字谁起的,我也这么用吧)根据一些文章和代码中使用的变量名,我后面称呼它为主分区。你可以按照自己的需要把主分区分成几个区,我的开发板用的分区信息如下:
来自alchemy_flash.c:
static struct mtd_partition alchemy_partitions[] = {
{
.name = "User FS", //这里给根文件系统
.size = BOARD_FLASH_SIZE - 0x00400000,
.offset = 0x0000000
},{
.name = "YAMON",//这块给bootloader
.size = 0x0100000,
.offset = MTDPART_OFS_APPEND, //表示接着上一个分区 .mask_flags = MTD_WRITEABLE
},{
.name = "raw kernel",
.size = (0x300000 - 0x40000), /* last 256KB is yamon env */ //这块给自解
压 //的压缩内核,最后留了点给booterloader的环境变量,它没有被设备驱动使用,而是由booterloader以自己的方式访问。
.offset = MTDPART_OFS_APPEND,
}
};
如果你增加或者是减少了你的flash空间(通过增加或减少flash芯片)或则你想调整几个分区的大小,你只需要修改这个表就可以了。
如果你还有一块NAND区,那么你可能有如下的分区表(au1550nd.c):
const static struct mtd_partition partition_info[] = {
{
.name = "NAND FS 0",
.offset = 0,
.size = 8*1024*1024
},
{
.name = "NAND FS 1",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL
}
};
整个alchemy_flash.c就两个函数: alchemy_mtd_init(void)和 alchemy_mtd_cleanup
()。
int __init alchemy_mtd_init(void)
{
struct mtd_partition *parts;
int nb_parts = 0;
unsigned long window_addr;
unsigned long window_size;
/* Default flash buswidth */
alchemy_map.bankwidth = BOARD_FLASH_WIDTH;
window_addr = 0x20000000 - BOARD_FLASH_SIZE;
window_size = BOARD_FLASH_SIZE;
#ifdef CONFIG_MIPS_MIRAGE_WHY
/* Boot ROM flash bank only; no user bank */
window_addr = 0x1C000000;
window_size = 0x04000000;
/* USERFS from 0x1C00 0000 to 0x1FC00000 */
alchemy_partitions[0].size = 0x03C00000;
#endif
/*
* Static partition definition selection
*/
parts = alchemy_partitions;
nb_parts = NB_OF(alchemy_partitions);
alchemy_map.size = window_size;
/*
* Now let's probe for the actual flash. Do it here since
* specific machine settings might have been set above.
*/
printk(KERN_NOTICE BOARD_MAP_NAME ": probing %d-bit flash bus\n",
alchemy_map.bankwidth*8);
alchemy_map.virt = ioremap(window_addr, window_size);
mymtd = do_map_probe("cfi_probe", &alchemy_map);
if (!mymtd) {
iounmap(alchemy_map.virt);
return -ENXIO;
}
mymtd->owner = THIS_MODULE;
add_mtd_partitions(mymtd, parts, nb_parts);
return 0;
}
看看红色的区域,do_map_probe返回了一个mtd_info结构指针。那么表明这个函数在正确找到你的驱动(cfi驱动)后会填好这个表把其中的读写函数等设置到正确的值,具体的实现放到以后分析吧,其中的一个map_info参数,我暂时没有完全读懂,因为我以前不曾研究flash的底层驱动,你需要设置好bankwidth和基地址,和驱动名字,我只能通过一些信息猜测他用来管理这个主分区并给底层驱动使用的,如坏块信息就保存在这里。
最后是调用add_mtd_partitions根据你设置的分区表和主分区的mtd_info来分区。其实如果你不打算给这个主分区分成几个区,你可以直接把这个mtd_info加到mtd_table而不需要mtdpart.c中的处理。这是后话,先沿着这条路分析一下:这个函数跳到了文件mtdpart.c。当你的把主分区分成几个区以后这几个分区的mtd_info和你的主分区是大部分是一样的,只是在size,name等方面不一样。add_mtd_partitions函数太大了点,全部贴出来不合适,我挑几行吧。
slave->mtd.type = master->type; //复制了主分区的信息
slave->mtd.size = parts[i].size; //改了size
slave->mtd.oobblock = master->oobblock; //复制了主分区的信息 slave->mtd.name = parts[i].name; //改了名字
slave->mtd.bank_size = master->bank_size; //复制了主分区的信息 slave->mtd.read = part_read; //这个是自己的,下面分析看看 if(parts[i].mtdp)
{ /* store the object pointer (caller may or may not register it */
*parts[i].mtdp = &slave->mtd;
slave->registered = 0;//你也可以不加进mtd_table中
}
else
{
/* register our partition */
add_mtd_device(&slave->mtd););//最后是这个,加进mtd_table中的是而不是那个包含它的结构mtd_part
slave->registered = 1;
}
static int part_read (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct mtd_part *part = PART(mtd);
if (from >= mtd->size)
len = 0;
else if (from + len > mtd->size)
len = mtd->size - from;
if (part->master->read_ecc == NULL)
return part->master->read (part->master, from + part->offset, len, retlen, buf);
else
return part->master->read_ecc (part->master, from + part-
>offset,
len, retlen, buf, NULL, &mtd->oobinfo); }
这下清楚了:还是调用主分区的读函数,就是加上了偏移量而已。
add_mtd_partitions的最后调用add_mtd_device(&slave->mtd);把每个分区加进了mtd_table。这个函数在mtdcore.c中,前面提到的如果不分区,你可以直接把主分区做为参数加到mtd_table中。
Mtdcore.c如其名,它叫core是有原因的:它不调用mtd驱动各层次的任何外部函数(除了注册的回调函数)而只是输出函数,它管理着mtd_table。底层通过add_mtd_device和del_mtd_device来添加和删除原始设备(分区)。上层字符设备和块设备部分通过get_mtd_device和put_mtd_device来申请和释放分区等等。
int add_mtd_device(struct mtd_info *mtd)
{
int i;
down(&mtd_table_mutex);
for (i=0; i
if (!mtd_table[i]) {
struct list_head *this;
mtd_table[i] = mtd;
mtd->index = i;
mtd->usecount = 0;
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);//我的观点是如果你不打算动态增加和删除设备的话这一
//部分是没有必要的。系统初始化mtd设备时,这个链表也是空的。
}
up(&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;
}
up(&mtd_table_mutex);
return 1;
}
整个函数比较简单,在mtd_table中选择一个空位置放置你的分区的mtd_info。红色部分是比较难懂一点的:这个mtd_notifiers链表起什么作用?看看谁在这个链表中加了东西。register_mtd_user是唯一向这个链表添加了成员的,谁调了?看一下下面的程序
void register_mtd_user (struct mtd_notifier *new)
{
int i;
down(&mtd_table_mutex);
list_add(&new->list, &mtd_notifiers);
__module_get(THIS_MODULE);
for (i=0; i
if (mtd_table[i])
new->add(mtd_table[i]);
up(&mtd_table_mutex);
}
static inline void mtdchar_devfs_init(void)
{
devfs_mk_dir("mtd");
register_mtd_user(¬ifier);
}
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
int ret, i;
/* Register the notifier if/when the first device type is
registered, to prevent the link/init ordering from fucking
us over. */
if (!blktrans_notifier.list.next)
register_mtd_user(&blktrans_notifier);