一、块设备驱动引入 1.1 磁盘读写 磁盘的读写慢在“磁头”的机械结构的定位上面。从一个“磁头”的某“柱面”某“扇区”读到数据后(步骤 R0),跳到另一个“磁头”的某“柱面”的某“扇区”去写(步骤 W0),接着再跳回原“磁头”相同柱面的下一个“扇区”去读(步骤R1)。
若按“字符设备”中的“opne”,”read”,”write”方式,则总体效率在硬盘上会非常低。 字符设备读写:“R0”–>”W0”–>”R1” —> “磁头”跳转 2 次。 调整顺序优化:“R0”–>”R1”–>”W0” —> “磁头”跳转 1 次。
总结:先不执行读/写操作,而是放入队列,优化后再执行。若按“字符设备驱动”那样的读写步骤会在硬盘上反复横跳,整体效率会非常低。所以有必要引入“优化过程”。即:读/写操作先不执行,先放到某个“队列”中(调整顺序优化),最后再执行磁盘真正的读/写操作
1.2 Flash读写 Flash分为很多“块”,“块”里有很多的扇区。Flash 要先擦除再写, 擦除是整“块”进行的。若现在要写同一“块”的“扇区 0”和“扇区 1”。用“字符设备驱动”的方式来写: ① 先把Flash的这个“块”读到一个 buf 中。 ② 然后修改 buf 中“扇区 0”对应位置的数据。 ③ 擦除Flash的这个“块”。 ④ 把修改过“扇区 0”数据的buf烧写到Flash的这个“块”。 ⑤ 以相同的操作写“扇区1” 故:当要修改多个扇区时会多次擦除烧写动作。总体效率低下。
“块设备驱动”的方式来写: ① 先不执行。 ② 优化 - 合并后执行。 a. 读出Flash的这个“块”到 buf 中。 b. 在buf中修改“扇区 0”和“扇区 1”对应位置的数据。 c. 擦除Flash的这个“块”。 d. 把修改过“扇区 0”和“扇区 1”数据的buf烧写到Flash的这个“块”。 故:“块驱动”不提供“字符设备驱动”那样直接读写IO的函数,其步骤可概括为: ① 先把读写请求放入队列,先不执行。 ② 优化后再执行。
二、块设备驱动框架 2.1 层次架构 1 2 3 4 5 6 7 8 9 10 用户app: open,read,write "1.txt" ----------------------------- 文件的读写 文件系统: vfat, ext4, yaffs2... (把文件的读写转换为扇区的读写) ---------ll_rw_block--------- 扇区的读写 1. 把"读写"放入队列 2. 调用队列的处理函数(优化/调顺序/合并) ----------------------------- 块设备驱动程序 ----------------------------- 真实硬件: 硬盘, flash
对普通文件 1.txt 的读写会转成对块设备的读写,即要读写哪个扇区。从文件的读写转成对扇区的读写,中间会涉及到“文件系统”。普通的文件转换成对扇区的读写,是由“文件系统”转换。比如:要写一个很小的数据到txt文件某个位置时,由于块设备写的数据是按扇区为单位,但又不能破坏txt文件里其它位置,那么就引入了一个“缓存区”,将所有数据先读到缓存区里,然后修改缓存数据,最后将整个数据放入txt文件对应的某个扇区中。
当txt文件多次写入很小的数据的话,就会重复不断地对扇区读出、写入,这样会浪费很多时间在读/写硬盘上,所以内核提供了一个队列机制:在没有关闭txt文件之前,会将读/写请求进行优化、排序、合并等操作,从而提高访问硬盘的效率。ll_rw_block 是通用的入口,会把“读/写”放入队列,调用队列的处理函数去优化(调顺序、合并),最后执行真正的读/写操作。
2.2 ll_rw_block分析
① 分析ll_rw_block()函数(fs/buffer.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void ll_rw_block (int rw, int nr, struct buffer_head *bhs[]) { int i; for (i = 0 ; i < nr; i++) { struct buffer_head *bh = bhs[i]; ... ... if (rw == WRITE || rw == SWRITE) { ... ... submit_bh(WRITE, bh); continue ; } } }
其中buffer_head结构体为缓冲区描述符,存放缓存区的各种信息,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct buffer_head { unsigned long b_state; struct buffer_head *b_this_page ; struct page *b_page ; sector_t b_blocknr; size_t b_size; char *b_data; struct block_device *b_bdev ; bh_end_io_t *b_end_io; void *b_private; struct list_head b_assoc_buffers ; struct address_space *b_assoc_map ; atomic_t b_count; };
② 进入submit_bh()中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int submit_bh (int rw, struct buffer_head * bh) { struct bio *bio = bio_alloc(GFP_NOIO, 1 ); bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9 ); bio->bi_bdev = bh->b_bdev; bio->bi_io_vec[0 ].bv_page = bh->b_page; bio->bi_io_vec[0 ].bv_len = bh->b_size; bio->bi_io_vec[0 ].bv_offset = bh_offset(bh); ... ... submit_bio(rw, bio); ... ... }
submit_bh()函数通过buffer_head构造bio,然后调用submit_bio()提交。如下:
1 2 3 4 5 6 7 void submit_bio (int rw, struct bio *bio) { ... ... generic_make_request(bio); __generic_make_request(bio); }
③ __generic_make_request()函数如下:
1 2 3 4 5 6 7 8 9 10 11 static inline void __generic_make_request(struct bio *bio){ request_queue_t *q; int ret; ... ... do { q = bdev_get_queue(bio->bi_bdev); ... ... ret = q->make_request_fn(q, bio); } while (ret); }
这个q->make_request_fn()又是什么函数?到底做了什么,搜索make_request_fn,它在blk_queue_make_request()函数中被初始化为mfn这个参数:
继续搜索blk_queue_make_request,找到它被谁调用,赋入的mfn参数是什么,如下图,找到它在blk_init_queue_node()函数中被调用:
最终q->make_request_fn()执行的是__make_request()函数
④ __make_request()函数,对提交的申请队列q和bio做了什么?
1 2 3 4 5 6 7 8 9 10 static int __make_request(request_queue_t *q, struct bio *bio){ struct request *req ; ... ... el_ret = elv_merge(q, &req, bio); ... ... __generic_unplug_device(q); }
a. 上面的elv_merge()函数,就是内核中的电梯算法(elevator merge)。比如申请队列中有以下6个申请:4(in),2(out),5(in),3(out),6(in),1(out)。其中in:写出;out:读入。最后执行下来就会排序合并:先写出4,5,6,队列,再读入1,2,3队列。
b. __generic_unplug_device()函数如下:
1 2 3 4 5 6 7 8 9 void __generic_unplug_device(request_queue_t *q){ if (unlikely(blk_queue_stopped(q))) return ; if (!blk_remove_plug(q)) return ; q->request_fn(q); }
最终执行申请队列的处理函数:q的成员request_fn()函数,它是操作硬件的函数,如果是RAM模拟磁盘会执行memcopy;如果是真实块设备会执行擦除烧写动作。
2.3 块设备驱动编写步骤
以面向对象的思想分配gendisk结构体。用alloc_disk函数。
设置 gendisk 结构体。 ① 分配/设置一个队列:request_queue_t(提供读写能力),即设置request_fn()处理函数。用blk_init_queue函数。 ② 设置gendisk其他信息(提供磁盘属性:磁盘容量,扇区大小等),这样块设备才能分区。
注册gendisk结构体。用add_disk函数。
三、RAM模拟块设备程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/dma.h> static struct gendisk *ramblock_disk ;static request_queue_t *ramblock_queue;static int major;static DEFINE_SPINLOCK (ramblock_lock) ;#define RAMBLOCK_SIZE (1024*1024) static unsigned char *ramblock_buf;static int ramblock_getgeo (struct block_device *bdev, struct hd_geometry *geo) { geo->heads = 2 ; geo->cylinders = 32 ; geo->sectors = RAMBLOCK_SIZE/2 /32 /512 ; return 0 ; } static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo, }; static void do_ramblock_request (request_queue_t * q) { static int r_cnt = 0 ; static int w_cnt = 0 ; struct request *req ; while ((req = elv_next_request(q)) != NULL ) { unsigned long offset = req->sector * 512 ; unsigned long len = req->current_nr_sectors * 512 ; if (rq_data_dir(req) == READ) memcpy (req->buffer, ramblock_buf+offset, len); else memcpy (ramblock_buf+offset, req->buffer, len); end_request(req, 1 ); } } static int ramblock_init (void ) { ramblock_disk = alloc_disk(16 ); ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); ramblock_disk->queue = ramblock_queue; major = register_blkdev(0 , "ramblock" ); ramblock_disk->major = major; ramblock_disk->first_minor = 0 ; sprintf (ramblock_disk->disk_name, "ramblock" ); ramblock_disk->fops = &ramblock_fops; set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512 ); ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); add_disk(ramblock_disk); return 0 ; } static void ramblock_exit (void ) { unregister_blkdev(major, "ramblock" ); del_gendisk(ramblock_disk); put_disk(ramblock_disk); blk_cleanup_queue(ramblock_queue); kfree(ramblock_buf); } module_init(ramblock_init); module_exit(ramblock_exit); MODULE_LICENSE("GPL" );
四、Nand Flash驱动分析 4.1 Nand Flash引入 1. 问题与回答:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 问1:原理图上NAND FLASH和S3C2440之间只有数据线,怎么传输地址? 答1:在DATA0~DATA7上既传输数据,又传输地址。当ALE为高电平时传输的是地址。 问2:从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令。怎么传入命令? 答2:在DATA0~DATA7上既传输数据,又传输地址,也传输命令。 当ALE为高电平时传输的是地址,当CLE为高电平时传输的是命令。 当ALE和CLE都为低电平时传输的是数据。 问3:数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等。怎么避免干扰? 答3:这些设备要访问之必须"选中"。没有选中的芯片不会工作,相当于没接一样。 问4:假设烧写NAND FLASH,把命令、地址、数据发给它之后,怎么判断传输完成? 答4:通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙。 问5:怎么操作NAND FLASH呢? 答5:根据NAND FLASH的芯片手册,一般的过程是:发出命令,发出地址,发出数据/读数据。
2. NAND FLASH操作:
3. 用UBOOT来体验NAND FLASH的读ID:
4. 用UBOOT来体验NAND FLASH的读内容(读0地址的数据):
先用UBOOT命令读出NAND FLASH 0地址的内容:
1 2 3 nand dump 0 Page 00000000 dump: 17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5
再用UBOOT操作NAND FLASH控制器读出0地址内容:
5. nand flash驱动框图:
6. 驱动注册流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 s3c2410_nand_inithw s3c2410_nand_init_chip nand_scan nand_scan_ident nand_set_defaults if (!chip->select_chip) chip->select_chip = nand_select_chip; if (chip->cmdfunc == NULL ) chip->cmdfunc = nand_command; if (!chip->read_byte) chip->read_byte = nand_read_byte; nand_get_flash_type chip->select_chip(mtd, 0 ); chip->cmdfunc(mtd, NAND_CMD_READID, 0x00 , -1 ); *maf_id = chip->read_byte(mtd); dev_id = chip->read_byte(mtd); nand_scan_tail mtd->erase = nand_erase; mtd->read = nand_read; mtd->write = nand_write; s3c2410_nand_add_partition add_mtd_partitions add_mtd_device list_for_each (this, &mtd_notifiers) { struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list ); not->add(mtd); 先看字符设备的mtd_notify_add class_device_create class_device_create 再看块设备的blktrans_notify_add list_for_each (this, &blktrans_majors) { struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list ); tr->add_mtd(tr, mtd); mtdblock_add_mtd (drivers\mtd\mdblock.c) add_mtd_blktrans_dev alloc_disk gd->queue = tr->blkcore_priv->rq; add_disk
4.2 驱动入口 参考s3c2440的nand flash驱动,drivers/mtd/nand/s3c2410.c。
为什么nand在mtd目录下?
因为mtd(memory technology device 存储 技术设备 ) 是用于访问 memory 设备( ROM 、 flash )的Linux 的子系统。 MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。
进入probe函数中,主要调用nand_scan()函数,add_mtd_partitions()函数,来完成注册nand flash:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int s3c24xx_nand_probe (struct platform_device *pdev, enum s3c_cpu_type cpu_type) { s3c2410_nand_inithw(info, pdev); s3c2410_nand_init_chip(info, nmtd, sets); nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1 ); s3c2410_nand_add_partition(info, nmtd, sets); }
4.3 配置Nand Flash控制器 s3c2410_nand_inithw:初始化nand flash控制器,设置TACLS 、TWRPH0、TWRPH1时序等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static int s3c2410_nand_inithw (struct s3c2410_nand_info *info, struct platform_device *pdev) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev); unsigned long clkrate = clk_get_rate(info->clk); int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4 ; int tacls, twrph0, twrph1; unsigned long cfg = 0 ; clkrate /= 1000 ; if (plat != NULL ) { tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max); twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8 ); twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8 ); } else { tacls = tacls_max; twrph0 = 8 ; twrph1 = 8 ; } cfg = S3C2440_NFCONF_TACLS(tacls - 1 ); cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1 ); cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1 ); writel(cfg, info->regs + S3C2410_NFCONF); writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); }
4.4 设置nand chip结构体 s3c2410_nand_init_chip:设置nand_chip结构体,为其分配底层接口函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void s3c2410_nand_init_chip (struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *nmtd, struct s3c2410_nand_set *set ) { struct nand_chip *chip = &nmtd->chip; void __iomem *regs = info->regs; chip->write_buf = s3c2410_nand_write_buf; chip->read_buf = s3c2410_nand_read_buf; chip->select_chip = s3c2410_nand_select_chip; chip->chip_delay = 50 ; chip->priv = nmtd; chip->options = 0 ; chip->controller = &info->controller; switch (info->cpu_type) { case TYPE_S3C2440: chip->IO_ADDR_W = regs + S3C2440_NFDATA; info->sel_reg = regs + S3C2440_NFCONF; info->sel_bit = S3C2440_NFCONF_nFCE; chip->cmd_ctrl = s3c2440_nand_hwcontrol; chip->dev_ready = s3c2440_nand_devready; break ; } chip->IO_ADDR_R = chip->IO_ADDR_W; }
4.5 nand_scan()流程分析 nand_scan: 最终目的是使用nand_chip结构体,构造出mtd_info结构体提供给MTD层,并读取nand flash型号信息。
nand_set_defaults()函数: 为nand_chip中暂未设置的接口,分配系统默认的nand flash接口函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static void nand_set_defaults (struct nand_chip *chip, int busw) { if (!chip->chip_delay) chip->chip_delay = 20 ; if (chip->cmdfunc == NULL ) chip->cmdfunc = nand_command; if (chip->waitfunc == NULL ) chip->waitfunc = nand_wait; if (!chip->select_chip) chip->select_chip = nand_select_chip; if (!chip->read_byte) chip->read_byte = busw ? nand_read_byte16 : nand_read_byte; if (!chip->read_word) chip->read_word = nand_read_word; if (!chip->write_buf) chip->write_buf = busw ? nand_write_buf16 : nand_write_buf; if (!chip->read_buf) chip->read_buf = busw ? nand_read_buf16 : nand_read_buf; ...... }
nand_get_flash_type()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static struct nand_flash_dev *nand_get_flash_type (struct mtd_info *mtd,struct nand_chip *chip,int busw, int *maf_id) { chip->select_chip(mtd, 0 ); chip->cmdfunc(mtd, NAND_CMD_READID, 0x00 , -1 ); dev_id = chip->read_byte(mtd); for (i = 0 ; nand_flash_ids[i].name != NULL ; i++) { if (dev_id == nand_flash_ids[i].id) { type = &nand_flash_ids[i]; break ; } } printk(KERN_INFO "NAND device: Manufacturer ID:" " 0x%02x, Chip ID: 0x%02x (%s %s)\n" , *maf_id, dev_id, nand_manuf_ids[maf_idx].name, mtd->name); }
nand_flash_ids[]数组:
启动内核就可以看到打印出nand flash参数:
nand_scan_tail()函数: 构造mtd_info结构体,为硬件和上层之间提供了一个抽象的接口。mtd_info中包含nand_chip成员,故mtd层的接口最终都会调用到nand_chip所设置的底层硬件接口。
1 2 3 4 5 6 7 8 9 int nand_scan_tail (struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; mtd->type = MTD_NANDFLASH; mtd->erase = nand_erase; -> nand_erase_nand -> nand_chip->erase_cmd mtd->read = nand_read; -> nand_do_read_ops -> nand_chip->ecc.read_page mtd->write = nand_write; -> nand_do_write_ops -> nand_chip->write_page ...... }
4.6 添加MTD分区 系统启动后可以从log看到其MTD分区信息:
probe()里的s3c2410_nand_add_partition()函数注册nand flash的mtd设备,并添加分区
最终它调用了s3c2410_nand_add_partition()->add_mtd_partitions() -> add_mtd_device()
其中add_mtd_partitions()函数主要实现多个分区创建,也就是多次调用add_mtd_device()
当只设置nand flash为一个分区时,即主分区,直接调用add_mtd_device()即可。
1 2 3 4 5 6 7 8 s3c2410_nand_add_partition -> add_mtd_device(&mtd->mtd) -> list_for_each(this, &mtd_notifiers) { struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list ); not->add(mtd); }
mtd_notifiers链表在哪设置?mtdchar.c (字符设备)、mtd_blkdevs.c(块设备)
①nand flash 字符设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void mtd_notify_add (struct mtd_info* mtd) { class_device_create(mtd_class, NULL , MKDEV(MTD_CHAR_MAJOR, mtd->index*2 ), NULL , "mtd%d" , mtd->index); class_device_create(mtd_class, NULL , MKDEV(MTD_CHAR_MAJOR, mtd->index*2 +1 ), NULL , "mtd%dro" , mtd->index); } static struct mtd_notifier notifier = { .add = mtd_notify_add, .remove = mtd_notify_remove, }; init_mtdchar -> register_mtd_user(¬ifier) -> list_add(&new->list , &mtd_notifiers);
②nand flash 块设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static void blktrans_notify_add (struct mtd_info *mtd) { struct list_head *this ; list_for_each(this, &blktrans_majors) { struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list ); tr->add_mtd(tr, mtd); } } static struct mtd_notifier blktrans_notifier = { .add = blktrans_notify_add, .remove = blktrans_notify_remove, }; register_mtd_blktrans-> register_mtd_user(&blktrans_notifier) -> list_add(&new->list , &mtd_notifiers)
blktrans_majors链表又在哪设置? mtdblock_ro.c、mtdblock.c(以他为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 mtdblock_add_mtd -> add_mtd_blktrans_dev(dev)-> alloc_disk(1 << tr->part_bits); set_capacity(gd, (new->size * tr->blksize) >> 9 ); gd->major = tr->major; gd->first_minor = (new->devnum) << tr->part_bits; gd->fops = &mtd_blktrans_ops; gd->queue = tr->blkcore_priv->rq; add_disk(gd); static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock" , .major = 31 , .part_bits = 0 , .blksize = 512 , .open = mtdblock_open, .flush = mtdblock_flush, .release = mtdblock_release, .readsect = mtdblock_readsect, .writesect = mtdblock_writesect, .add_mtd = mtdblock_add_mtd, .remove_dev = mtdblock_remove_dev, .owner = THIS_MODULE, }; init_mtdblock -> register_mtd_blktrans(&mtdblock_tr); -> list_add(&tr->list , &blktrans_majors);
③MTD设备
所以mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c),块设备接口最终会分配设置注册一个gendisk。在控制台输入ls -l /dev/mtd*,能找到块MTD设备节点和字符MTD设备节点,可以看到一共创建了4个分区的设备,每个分区都包含了两个字符设备(mtd%d,mtd%dro)、一个块设备(mtdblock0)。MTD块设备的主设备号为31,MTD字符设备的主设备号为90。
④MTD块设备请求队列
块设备的操作接口(mtd_blkdevs.c)会调用mtdblock_add_mtd,最终会分配、设置、注册一个gendisk。
那么这个gendisk的请求队列在哪被设置,这个队列最终会怎么实现flash的读写呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 init_mtdblock -> register_mtd_blktrans(&mtdblock_tr); tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock); tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr, "%sd" , tr->name); static void mtd_blktrans_request (struct request_queue *rq) { struct mtd_blktrans_ops *tr = rq->queuedata; wake_up_process(tr->blkcore_priv->thread); } static int mtd_blktrans_thread (void *arg) { struct mtd_blktrans_ops *tr = arg; req = elv_next_request(rq); dev = req->rq_disk->private_data; tr = dev->tr; res = do_blktrans_request(tr, dev, req); end_request(req, res); }
4.7 Nand Flash读取流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 mtd_blktrans_request -> wake_up_process(tr->blkcore_priv->thread); mtd_blktrans_thread do_blktrans_request -> tr->readsect(dev, block, buf) -> do_cached_read(mtdblk, block<<9 , 512 , buf) -> mtd->read(mtd, pos, size, &retlen, buf) -> nand_do_read_ops(mtd, from, &chip->ops) -> chip->ecc.read_page_raw(mtd, chip, bufpoi) -> nand_read_page_raw -> chip->read_buf(mtd, buf, mtd->writesize); chip->read_buf(mtd, chip->oob_poi, mtd->oobsize) -> readsb(this->IO_ADDR_R, buf, len);
4.8 Nand Flash驱动编写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 #include <linux/module.h> #include <linux/types.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/clk.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/nand_ecc.h> #include <linux/mtd/partitions.h> #include <asm/io.h> #include <asm/arch/regs-nand.h> #include <asm/arch/nand.h> struct s3c_nand_regs { unsigned long nfconf ; unsigned long nfcont ; unsigned long nfcmd ; unsigned long nfaddr ; unsigned long nfdata ; unsigned long nfeccd0 ; unsigned long nfeccd1 ; unsigned long nfeccd ; unsigned long nfstat ; unsigned long nfestat0; unsigned long nfestat1; unsigned long nfmecc0 ; unsigned long nfmecc1 ; unsigned long nfsecc ; unsigned long nfsblk ; unsigned long nfeblk ; }; static struct nand_chip *s3c_nand_chip ;static struct mtd_info *s3c_mtd ;static struct s3c_nand_regs * s3c_nand_regs ;static struct mtd_partition s3c_nand_parts [] = { [0 ] = { .name = "bootloader" , .size = 0x00040000 , .offset = 0 , }, [1 ] = { .name = "params" , .offset = MTDPART_OFS_APPEND, .size = 0x00020000 , }, [2 ] = { .name = "kernel" , .offset = MTDPART_OFS_APPEND, .size = 0x00200000 , }, [3 ] = { .name = "root" , .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } }; static void s3c2440_select_chip (struct mtd_info *mtd, int chipnr) { if (chipnr == -1 ) { s3c_nand_regs->nfcont |= (1 <<1 ); } else { s3c_nand_regs->nfcont &= ~(1 <<1 ); } } static void s3c2440_cmd_ctrl (struct mtd_info *mtd, int dat, unsigned int ctrl) { if (ctrl & NAND_CLE) s3c_nand_regs->nfcmd = dat; else s3c_nand_regs->nfaddr = dat; } static int s3c2440_dev_ready (struct mtd_info *mtd) { return s3c_nand_regs->nfstat & (1 <<0 ); } static int nand_init (void ) { struct clk *clk ; s3c_nand_chip = kzalloc(sizeof (struct nand_chip), GFP_KERNEL); s3c_nand_regs = ioremap(0x4e000000 ,sizeof (struct s3c_nand_regs)); s3c_nand_chip->select_chip = s3c2440_select_chip; s3c_nand_chip->cmd_ctrl = s3c2440_cmd_ctrl; s3c_nand_chip->IO_ADDR_R = &s3c_nand_regs->nfdata; s3c_nand_chip->IO_ADDR_W = &s3c_nand_regs->nfdata; s3c_nand_chip->dev_ready = s3c2440_dev_ready; s3c_nand_chip->ecc.mode = NAND_ECC_SOFT; clk = clk_get(NULL , "nand" ); clk_enable(clk); #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 s3c_nand_regs->nfconf = (TACLS<<12 ) | (TWRPH0<<8 ) | (TWRPH1<<4 ); s3c_nand_regs->nfcont = (1 <<1 ) | (1 <<0 ); s3c_mtd = kzalloc(sizeof (struct mtd_info), GFP_KERNEL); s3c_mtd->owner = THIS_MODULE; s3c_mtd->priv = s3c_nand_chip; nand_scan(s3c_mtd,1 ); add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4 ); return 0 ; } static void nand_exit (void ) { del_mtd_partitions(s3c_mtd); kfree(s3c_mtd); iounmap(s3c_nand_regs); kfree(s3c_nand_chip); } module_init(nand_init); module_exit(nand_exit); MODULE_LICENSE("GPL" );
驱动测试:
make menuconfig去掉内核自带的NAND FLASH驱动1 2 3 4 -> Device Drivers -> Memory Technology Device (MTD) support -> NAND Device Support < > NAND Flash support for S3C2410/S3C2440 SoC
make uImage 使用新内核启动, 并且使用NFS作为根文件系统
insmod s3c_nand.ko
格式化,默认格式化成 yaffs。(工具制作过程参见下方) flash_eraseall /dev/mtd3
挂接 mount -t yaffs /dev/mtdblock3 /mnt
在/mnt目录下创建文件等操作
工具制作:
tar xjf mtd-utils-05.07.23.tar.bz2
cd mtd-utils-05.07.23/util 修改Makefile:#CROSS=arm-linux- 改为 CROSS=arm-linux-
make
cp flash_eraseall /work/nfs_root/first_fs/bin/
五、nor flash驱动分析 5.1 nor flash框架 nor flash与nand flash类似,对于nand flash,硬件相关层构造一个nand_chip结构体,然后向上注册一个mtd_info。对于nor flash而言,硬件相关层构造一个map_info结构体,然后向上注册一个mtd_info。所以对于MTD层而言,nand和nor是没有区别的,在MTD看来他们都是mtd_info。
5.2 nor flash规范 1. JEDEC规范
老式的Nor Flash一般是jedec规范,其一般只包含识别 ID、擦除芯片、烧写数据的命令。要想知道其容量大小等信息,就需要先读出其芯片id,然后到内核中的jedec_table数组中比较得到对应的芯片信息,比较麻烦。另外如果内核jedec_table数组中事先没有对应芯片id的信息,还需要先在该数组中添加。jedec_table数组:
2. CFI规范
目前的Nor Flash一般都支持CFI规范,其除了提供识别 ID、擦除芯片、烧写数据的命令之后,还提供了进入CFI模式的命令(往地址0x55处写入0x98),进入CFI模式后就可以通过读取相应地址的数据获取芯片属性信息,如容量、电压等信息。
读取芯片容量(从地址0x27处读取容量大小 ):
5.3 nor flash驱动使能 make menuconfig:
1 2 3 4 5 6 7 -> Device Drivers -> Memory Technology Device (MTD) support (MTD [=y]) -> Mapping drivers for chip access <M> CFI Flash device in physical memory map // 将驱动编译位内核模块,方便调试,设位y为编译进内核 (0x0) Physical start address of flash mapping // 配置基地址 (0x200000) Physical length of flash mapping // 配置物理大小 (2) Bank width in octets // 配置位宽 2*8=16
可以在linux-2.6.22.6/.config中看下如下信息:
1 2 3 4 CONFIG_MTD_PHYSMAP=m CONFIG_MTD_PHYSMAP_START=0x0 CONFIG_MTD_PHYSMAP_LEN=0x200000 CONFIG_MTD_PHYSMAP_BANKWIDTH=2
2440内核没有使用设备树,是代码注册driver与device:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #ifdef CONFIG_MTD_PHYSMAP_LEN #if CONFIG_MTD_PHYSMAP_LEN != 0 #warning using PHYSMAP compat code #define PHYSMAP_COMPAT #endif #endif static struct platform_driver physmap_flash_driver = { .probe = physmap_flash_probe, .remove = physmap_flash_remove, #ifdef CONFIG_PM .suspend= physmap_flash_suspend, .resume = physmap_flash_resume, .shutdown = physmap_flash_shutdown, #endif .driver = { .name = "physmap-flash" , }, }; #ifdef PHYSMAP_COMPAT static struct physmap_flash_data physmap_flash_data = { .width = CONFIG_MTD_PHYSMAP_BANKWIDTH, }; static struct resource physmap_flash_resource = { .start = CONFIG_MTD_PHYSMAP_START, .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1 , .flags = IORESOURCE_MEM, }; static struct platform_device physmap_flash = { .name = "physmap-flash" , .id = 0 , .dev = { .platform_data = &physmap_flash_data, }, .num_resources = 1 , .resource = &physmap_flash_resource, }; static int __init physmap_init (void ) { int err; err = platform_driver_register(&physmap_flash_driver); #ifdef PHYSMAP_COMPAT if (err == 0 ) platform_device_register(&physmap_flash); #endif return err; }
5.4 驱动入口 进入probe函数中,主要调用do_map_probe()函数,add_mtd_partitions()函数,来完成注册nor flash:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static int physmap_flash_probe (struct platform_device *dev) { struct physmap_flash_info *info ; info = kzalloc(sizeof (struct physmap_flash_info), GFP_KERNEL); info->map .name = dev->dev.bus_id; info->map .phys = dev->resource->start; info->map .size = dev->resource->end - dev->resource->start + 1 ; info->map .bankwidth = physmap_data->width; info->map .set_vpp = physmap_data->set_vpp; info->map .virt = ioremap(info->map .phys, info->map .size); simple_map_init(&info->map ); map ->read = simple_map_read; map ->write = simple_map_write; map ->copy_from = simple_map_copy_from; map ->copy_to = simple_map_copy_to; const char **probe_type = { "cfi_probe" , "jedec_probe" , "map_rom" , NULL }; info->mtd = do_map_probe(*probe_type, &info->map ); #ifdef CONFIG_MTD_PARTITIONS const char *part_probe_types[] = { "cmdlinepart" , "RedBoot" , NULL }; parse_mtd_partitions(info->mtd, part_probe_types, &info->parts, 0 ); add_mtd_partitions(info->mtd, info->parts, err); return 0 ; if (physmap_data->nr_parts) { add_mtd_partitions(info->mtd, physmap_data->parts, physmap_data->nr_parts); return 0 ; } #endif add_mtd_device(info->mtd); return 0 ; }
5.5 do_map_probe流程分析 1. CFI规范Flash识别分析
1.0 CFI协议层注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void register_mtd_chip_driver (struct mtd_chip_driver *drv) { spin_lock(&chip_drvs_lock); list_add(&drv->list , &chip_drvs_list); spin_unlock(&chip_drvs_lock); } static struct chip_probe cfi_chip_probe = { .name = "CFI" , .probe_chip = cfi_probe_chip }; struct mtd_info *cfi_probe (struct map_info *map ) { return mtd_do_chip_probe(map , &cfi_chip_probe); } static struct mtd_chip_driver cfi_chipdrv = { .probe = cfi_probe, .name = "cfi_probe" , .module = THIS_MODULE }; static int __init cfi_probe_init (void ) { register_mtd_chip_driver(&cfi_chipdrv); return 0 ; }
1.1 CFI规范识别过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 info->mtd = do_map_probe(*probe_type, &info->map ) -> drv = get_mtd_chip_driver(name) -> ...... list_for_each(pos, &chip_drvs_list) this = list_entry(pos, typeof(*this), list ); if (!strcmp (this->name, name)) { ret = this; break ; } ...... return ret; drv->probe(map )-> mtd_do_chip_probe(map , &cfi_chip_probe)-> genprobe_ident_chips(map , cp) -> genprobe_new_chip(map , cp, &cfi) -> cp->probe_chip(map , 0 , NULL , cfi) -> cfi_send_gen_cmd(0xF0 , 0 , base, map , cfi, cfi->device_type, NULL ); cfi_send_gen_cmd(0xFF , 0 , base, map , cfi, cfi->device_type, NULL ); cfi_send_gen_cmd(0x98 , 0x55 , base, map , cfi, cfi->device_type, NULL ); qry_present(map ,base,cfi) cfi_chip_setup(map , cfi) -> ...... for (i=0 ; i<(sizeof (struct cfi_ident) + num_erase_regions * 4 ); i++) ((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map ,base + (0x10 + i)*ofs_factor); ..... mtd = check_cmd_set(map , 1 ) -> ...... case 0x0002 : return cfi_cmdset_0002(map , primary) -> ....... mtd = kzalloc(sizeof (*mtd), GFP_KERNEL); mtd->priv = map ; mtd->type = MTD_NORFLASH; mtd->erase = cfi_amdstd_erase_varsize; mtd->write = cfi_amdstd_write_words; mtd->read = cfi_amdstd_read; mtd->sync = cfi_amdstd_sync; mtd->flags = MTD_CAP_NORFLASH; ....... return mtd;
2. JEDEC规范Flash识别分析
2.0 JEDEC协议层注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void register_mtd_chip_driver (struct mtd_chip_driver *drv) { spin_lock(&chip_drvs_lock); list_add(&drv->list , &chip_drvs_list); spin_unlock(&chip_drvs_lock); } static struct chip_probe jedec_chip_probe = { .name = "JEDEC" , .probe_chip = jedec_probe_chip }; static struct mtd_info *jedec_probe (struct map_info *map ) { return mtd_do_chip_probe(map , &jedec_chip_probe); } static struct mtd_chip_driver jedec_chipdrv = { .probe = jedec_probe, .name = "jedec_probe" , .module = THIS_MODULE }; static int __init jedec_probe_init (void ) { register_mtd_chip_driver(&jedec_chipdrv); return 0 ; }
2.1 JEDEC规范识别过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 info->mtd = do_map_probe(*probe_type, &info->map ) -> drv = get_mtd_chip_driver(name) -> ...... list_for_each(pos, &chip_drvs_list) this = list_entry(pos, typeof(*this), list ); if (!strcmp (this->name, name)) { ret = this; break ; } ...... return ret; drv->probe(map )-> mtd_do_chip_probe(map , &jedec_chip_probe) -> genprobe_ident_chips(map , cp) -> genprobe_new_chip(map , cp, &cfi) -> cp->probe_chip(map , 0 , NULL , cfi) -> jedec_match( base, map , cfi, &jedec_table[i] ) cfi_jedec_setup(cfi, i) -> p_cfi->cfiq->P_ID = jedec_table[index].CmdSet; mtd = check_cmd_set(map , 1 ) ......
do_map_probe执行完后便添加分区,最终都是调用add_mtd_device,与nand flash一模一样,此处不再赘述。
一个nor flash的读写操作,首先会执行块设备的优化,放入队列, 找到对应的mtd_info结构体,再找到对应的私有数据map_info,执行硬件相关的读写操作。
5.6 nor flash驱动编写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> #include <asm/io.h> static struct map_info *s3c_nor_map ;static struct mtd_info *s3c_nor_mtd ;static struct mtd_partition s3c_nor_parts [] = { [0 ] = { .name = "bootloader_nor" , .size = 0x00040000 , .offset = 0 , }, [1 ] = { .name = "root_nor" , .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } }; static int s3c_nor_init (void ) { s3c_nor_map = kzalloc(sizeof (struct map_info), GFP_KERNEL);; s3c_nor_map->name = "s3c_nor" ; s3c_nor_map->phys = 0 ; s3c_nor_map->size = 0x1000000 ; s3c_nor_map->bankwidth = 2 ; s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size); simple_map_init(s3c_nor_map); printk("use cfi_probe\n" ); s3c_nor_mtd = do_map_probe("cfi_probe" , s3c_nor_map); if (!s3c_nor_mtd) { printk("use jedec_probe\n" ); s3c_nor_mtd = do_map_probe("jedec_probe" , s3c_nor_map); } if (!s3c_nor_mtd) { iounmap(s3c_nor_map->virt); kfree(s3c_nor_map); return -EIO; } add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2 ); return 0 ; } static void s3c_nor_exit (void ) { del_mtd_partitions(s3c_nor_mtd); iounmap(s3c_nor_map->virt); kfree(s3c_nor_map); } module_init(s3c_nor_init); module_exit(s3c_nor_exit); MODULE_LICENSE("GPL" );
5.7 RAM模拟MTD设备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 #include <linux/module.h> #include <linux/slab.h> #include <linux/ioport.h> #include <linux/vmalloc.h> #include <linux/init.h> #include <linux/mtd/mtd.h> #include <linux/mtd/mtdram.h> #include <linux/mtd/partitions.h> static unsigned long total_size = 10 *1024 ; static unsigned long erase_size = 1 *1024 ; #define MTDRAM_TOTAL_SIZE (total_size * 1024) #define MTDRAM_ERASE_SIZE (erase_size * 1024) static struct mtd_partition ram_parts [] = { [0 ] = { .name = "bootloader_nor" , .size = 0x200000 , .offset = 0 , }, [1 ] = { .name = "root_nor" , .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } }; static struct mtd_info *mtd_info ;static int check_offs_len (struct mtd_info *mtd, loff_t ofs, uint64_t len) { int ret = 0 ; if (mtd_mod_by_eb(ofs, mtd)) { pr_debug("%s: unaligned address\n" , __func__); ret = -EINVAL; } if (mtd_mod_by_eb(len, mtd)) { pr_debug("%s: length not block aligned\n" , __func__); ret = -EINVAL; } return ret; } static int ram_erase (struct mtd_info *mtd, struct erase_info *instr) { if (check_offs_len(mtd, instr->addr, instr->len)) return -EINVAL; memset ((char *)mtd->priv + instr->addr, 0xff , instr->len); instr->state = MTD_ERASE_DONE; mtd_erase_callback(instr); return 0 ; } static int ram_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, void **virt, resource_size_t *phys) { *virt = mtd->priv + from; *retlen = len; return 0 ; } static int ram_unpoint (struct mtd_info *mtd, loff_t from, size_t len) { return 0 ; } static unsigned long ram_get_unmapped_area (struct mtd_info *mtd, unsigned long len, unsigned long offset, unsigned long flags) { return (unsigned long ) mtd->priv + offset; } static int ram_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { memcpy (buf, mtd->priv + from, len); *retlen = len; return 0 ; } static int ram_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { memcpy ((char *)mtd->priv + to, buf, len); *retlen = len; return 0 ; } static void __exit cleanup_mtdram (void ) { if (mtd_info) { mtd_device_unregister(mtd_info); vfree(mtd_info->priv); kfree(mtd_info); } } int mtdram_init_device (struct mtd_info *mtd, void *mapped_address, unsigned long size, const char *name) { memset (mtd, 0 , sizeof (*mtd)); mtd->name = name; mtd->type = MTD_NORFLASH; mtd->flags = MTD_CAP_NORFLASH; mtd->size = size; mtd->writesize = 1 ; mtd->writebufsize = 64 ; mtd->erasesize = MTDRAM_ERASE_SIZE; mtd->priv = mapped_address; mtd->owner = THIS_MODULE; mtd->_erase = ram_erase; mtd->_point = ram_point; mtd->_unpoint = ram_unpoint; mtd->_get_unmapped_area = ram_get_unmapped_area; mtd->_read = ram_read; mtd->_write = ram_write; if (mtd_device_register(mtd, ram_parts, 2 )) return -EIO; return 0 ; } static int __init init_mtdram (void ) { void *addr; int err; if (!total_size) return -EINVAL; mtd_info = kmalloc(sizeof (struct mtd_info), GFP_KERNEL); if (!mtd_info) return -ENOMEM; addr = vmalloc(MTDRAM_TOTAL_SIZE); if (!addr) { kfree(mtd_info); mtd_info = NULL ; return -ENOMEM; } err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device" ); if (err) { vfree(addr); kfree(mtd_info); mtd_info = NULL ; return err; } memset (mtd_info->priv, 0xff , MTDRAM_TOTAL_SIZE); return err; } module_init(init_mtdram); module_exit(cleanup_mtdram); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>" ); MODULE_DESCRIPTION("Simulated MTD driver for testing" );