一、块设备驱动引入

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分析

1.ll_rw_block调用层次

① 分析ll_rw_block()函数(fs/buffer.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//rw: 读写标志位,  nr: bhs[]长度,  bhs[]: 要读写的数据
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]; //获取nr个buffer_head
... ...
if (rw == WRITE || rw == SWRITE) {
... ...
submit_bh(WRITE, bh); //提交WRITE写标志的buffer_head
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;         //I/O完成方法
void *b_private;             //完成方法数据
struct list_head b_assoc_buffers;   //相关映射链表
/* mapping this buffer is associated with */
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)
{
/* 分配一个bio(block input output),块设备i/o */
struct bio *bio = bio_alloc(GFP_NOIO, 1);
/* 根据buffer_head(bh)构造bio */
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);
... ...
/* 提交bio */
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(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); //通过bio->bi_bdev获取申请队列q
... ...
ret = q->make_request_fn(q, bio); //提交申请队列q和bio
} while (ret);
}

这个q->make_request_fn()又是什么函数?到底做了什么,搜索make_request_fn,它在blk_queue_make_request()函数中被初始化为mfn这个参数:

2.blk_queue_make_request

继续搜索blk_queue_make_request,找到它被谁调用,赋入的mfn参数是什么,如下图,找到它在blk_init_queue_node()函数中被调用:

3.mfn参数

最终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; //块设备本身的队列
... ...
/* (1)将之前的申请队列q和传入的bio,通过排序,合并在本身的req队列中 */
el_ret = elv_merge(q, &req, bio);
... ...
/* (2) 执行申请队列的处理函数 */
__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 块设备驱动编写步骤

  1. 以面向对象的思想分配gendisk结构体。用alloc_disk函数。
  2. 设置 gendisk 结构体。
    ① 分配/设置一个队列:request_queue_t(提供读写能力),即设置request_fn()处理函数。用blk_init_queue函数。
    ② 设置gendisk其他信息(提供磁盘属性:磁盘容量,扇区大小等),这样块设备才能分区。
  3. 注册gendisk结构体。用add_disk函数。

4.注册gendisk

4.request_fn

三、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

/* 参考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/

#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)
{
/* 容量 = heads * cylinders * sectors * 512 */
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;

/* 目的/源: */
// req->buffer

/* 长度: */
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)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */

/* 2.0 设置 */
/* 2.1 分配/设置队列: 提供读写能力 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;

/* 2.2 设置其他属性: 比如容量 */
major = register_blkdev(0, "ramblock"); /* cat /proc/devices */
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);

/* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);

/* 4. 注册 */
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操作:

5.nand_flash操作

3. 用UBOOT来体验NAND FLASH的读ID:

5.uboot读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.uboot读内容

5. nand flash驱动框图:

5.nand驱动框架

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 // drivers/mtd/nand/nand_base.c 根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info
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) {
// 问. mtd_notifiers在哪设置?
// 答. drivers/mtd/mtdchar.c,mtd_blkdev.c调用register_mtd_user
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
not->add(mtd);
// mtd_notify_add 和 blktrans_notify_add
先看字符设备的mtd_notify_add
class_device_create
class_device_create
再看块设备的blktrans_notify_add
list_for_each(this, &blktrans_majors) {
// 问. blktrans_majors在哪设置?
// 答. drivers\mtd\mdblock.c 或 mtdblock_ro.c register_mtd_blktrans
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
// tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
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)
{
/* 初始化nand flash控制器,设置TACLS 、TWRPH0、TWRPH1时序等 */
s3c2410_nand_inithw(info, pdev);

/* 设置nand chip结构体,分配接口函数 */
s3c2410_nand_init_chip(info, nmtd, sets);

/* 构造mtd info结构体,并读取nand flash型号信息 */
nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1);

/* 添加多个mtd分区,里面会调用add_mtd_partitions */
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; /* turn clock into kHz for ease of use */

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 {
/* default timings */
tacls = tacls_max;
twrph0 = 8;
twrph1 = 8;
}

// 设置TACLS 、TWRPH0、TWRPH1时序
cfg = S3C2440_NFCONF_TACLS(tacls - 1);
cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
writel(cfg, info->regs + S3C2410_NFCONF);

// 使能 nand flash控制器
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;

// 提供nand flash读写数据接口
chip->write_buf = s3c2410_nand_write_buf;
chip->read_buf = s3c2410_nand_read_buf;
// 提供nand flash 片选接口
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:
// 提供 nand flash控制器数据写入的寄存器地址
chip->IO_ADDR_W = regs + S3C2440_NFDATA;
info->sel_reg = regs + S3C2440_NFCONF;
info->sel_bit = S3C2440_NFCONF_nFCE;
// 提供nand flash 写命令或者地址接口
chip->cmd_ctrl = s3c2440_nand_hwcontrol;
// 提供nand flash 判断就绪或忙状态接口
chip->dev_ready = s3c2440_nand_devready;
break;
}

// 提供 nand flash控制器数据读取的寄存器地址
chip->IO_ADDR_R = chip->IO_ADDR_W;
}

4.5 nand_scan()流程分析

nand_scan:最终目的是使用nand_chip结构体,构造出mtd_info结构体提供给MTD层,并读取nand flash型号信息。

5.nand_scan

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)
{
/* check for proper chip_delay setup, set 20us if not */
if (!chip->chip_delay)
chip->chip_delay = 20;

/* check, if a user supplied command function given */
if (chip->cmdfunc == NULL)
chip->cmdfunc = nand_command;

/* check, if a user supplied wait function given */
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)
{
/* 调用nand_chip结构体的成员select_chip使能flash片选 */
chip->select_chip(mtd, 0);
/* 调用nand_chip结构体的成员cmdfunc发送读id命令 */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* 获取nand flash返回的设备ID数据 */
dev_id = chip->read_byte(mtd);
/* 循环匹配nand_flash_ids[]数组,找到对应的nandflash信息 */
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (dev_id == nand_flash_ids[i].id) {
type = &nand_flash_ids[i];
break;
}
}
/* 匹配成功,便打印nandflash参数 */
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[]数组:

5.nand_flash_type

启动内核就可以看到打印出nand flash参数:

5.nand_flash_log

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分区信息:

6.MTD分区log

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 ->
// 在s3c2410_nand_init_chip函数中已经将分区信息设置给了mtd
add_mtd_device(&mtd->mtd) ->
// 遍历mtd_notifiers,通过其add接口添加分区
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
// linux-2.6.22.6/drivers/mtd/mtdchar.c
static void mtd_notify_add(struct mtd_info* mtd)
{
// 创建字符设备 设备节点为/dev/mtd%d
class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
NULL, "mtd%d", mtd->index);
// 创建字符设备 设备节点为/dev/mtd%dro (只读)
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(&notifier) ->
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
// linux-2.6.22.6/drivers/mtd/mtd_blkdevs.c
static void blktrans_notify_add(struct mtd_info *mtd)
{
struct list_head *this;
// 遍历blktrans_majors链表 通过其add_mtd函数添加分区
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
// linux-2.6.22.6/drivers/mtd/mtdblock.c
mtdblock_add_mtd ->
add_mtd_blktrans_dev(dev)->
//分配gendisk结构体
alloc_disk(1 << tr->part_bits);
//设置gendisk结构体
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;
//设置gendisk请求队列
gd->queue = tr->blkcore_priv->rq;
//注册gendisk结构体
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。

7.mtd设备

④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
// linux-2.6.22.6/drivers/mtd/mtdblock.c
init_mtdblock ->
register_mtd_blktrans(&mtdblock_tr);
//mtd_blktrans_request便是gendisk的请求队列
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);

//mtd_blktrans_request这个请求队列唤醒mtd_blktrans_thread线程
static void mtd_blktrans_request(struct request_queue *rq)
{
struct mtd_blktrans_ops *tr = rq->queuedata;
wake_up_process(tr->blkcore_priv->thread);
}

//mtd_blktrans_thread线程便是gendisk具体的操作了
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;

//该函数会取出对应的mtd_info结构体,调用mtd_info的读写接口,
//最终会调用对应的nand_chip结构体的读写接口,实现flash物理上的读写操作。
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
// APP对块设备节点/dev/mtdblock进行访问操作

//一个读数据的bio,被合并或被生成一个请求,触发请求队列的请求处理函数
mtd_blktrans_request ->
// 唤醒一个休眠线程--mtd_blktrans_thread,该线程在register_mtd_blktrans中创建启动
wake_up_process(tr->blkcore_priv->thread);

// 该线程从请求队列中取出一个请求调用do_blktrans_request接口进行处理
mtd_blktrans_thread
do_blktrans_request ->
//tr->readsect 即:struct mtd_blktrans_ops中的mtdblock_readsect接口
tr->readsect(dev, block, buf) ->
do_cached_read(mtdblk, block<<9, 512, buf) ->
//mtd->read 即:struct mtd_info中的 nand_read接口
//在 nand_scan_tail接口中被设置 mtd->read = nand_read;
mtd->read(mtd, pos, size, &retlen, buf) ->
nand_do_read_ops(mtd, from, &chip->ops) ->
//调用到对应nand_chip的底层接口
chip->ecc.read_page_raw(mtd, chip, bufpoi) ->
nand_read_page_raw ->
// chip->read_buf 即:s3c2410_nand_read_buf
// 在驱动程序:s3c2410_nand_init_chip函数里设置
// chip->read_buf = s3c2410_nand_read_buf;
chip->read_buf(mtd, buf, mtd->writesize);
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize) ->
// 从nand flash控制器的NFDATA寄存器中读取数据
// chip->IO_ADDR_W = regs + S3C2440_NFDATA;
// chip->IO_ADDR_R = chip->IO_ADDR_W;
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)
{
//取消选中:NFCONT[1]设为1
s3c_nand_regs->nfcont |= (1<<1);
}
else
{
//选中:NFCONT[1]设为0
s3c_nand_regs->nfcont &= ~(1<<1);
}
}

static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
if (ctrl & NAND_CLE)
//发命令:NFCMMD = dat
s3c_nand_regs->nfcmd = dat;
else
//发地址:NFADDR = dat
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;

/*1 分配一个nand_chip结构体 */
s3c_nand_chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

s3c_nand_regs = ioremap(0x4e000000,sizeof (struct s3c_nand_regs));

/*2 设置 */
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;

/*3 硬件相关的设置,根据nand flsah手册设置时间参数 */
/* 使能NAND FLASH控制器的时钟 */
clk = clk_get(NULL, "nand");
clk_enable(clk); /* CLKCON'bit[4] */

/* HCLK=100MHz
* TACLS: 发出CLE/ALE之后多长时间才发出nWE信号, 从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0
* TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从NAND手册可知它要>=12ns, 所以TWRPH0>=1
* TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平, 从NAND手册可知它要>=5ns, 所以TWRPH1>=0
*/
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

/* NFCONT:
* BIT1-设为1, 先取消片选
* BIT0-设为1, 使能NAND FLASH控制器
*/
s3c_nand_regs->nfcont = (1<<1) | (1<<0);

/*4 使用:nand_scan */
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);

/*5 add_mtd_partitions */
add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);

/* 不分区的话,也即只有一个主分区,直接调用add_mtd_device */
//add_mtd_device(s3c_mtd);

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");

驱动测试:

  1. 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
  2. make uImage
    使用新内核启动, 并且使用NFS作为根文件系统
  3. insmod s3c_nand.ko
  4. 格式化,默认格式化成 yaffs。(工具制作过程参见下方)
    flash_eraseall /dev/mtd3
  5. 挂接
    mount -t yaffs /dev/mtdblock3 /mnt
  6. 在/mnt目录下创建文件等操作

工具制作:

  1. tar xjf mtd-utils-05.07.23.tar.bz2
  2. cd mtd-utils-05.07.23/util
    修改Makefile:#CROSS=arm-linux- 改为 CROSS=arm-linux-
  3. make
  4. 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。

8.nor_nand总结

5.2 nor flash规范

1. JEDEC规范

老式的Nor Flash一般是jedec规范,其一般只包含识别 ID、擦除芯片、烧写数据的命令。要想知道其容量大小等信息,就需要先读出其芯片id,然后到内核中的jedec_table数组中比较得到对应的芯片信息,比较麻烦。另外如果内核jedec_table数组中事先没有对应芯片id的信息,还需要先在该数组中添加。jedec_table数组:

9.jedec_table数组

2. CFI规范

目前的Nor Flash一般都支持CFI规范,其除了提供识别 ID、擦除芯片、烧写数据的命令之后,还提供了进入CFI模式的命令(往地址0x55处写入0x98),进入CFI模式后就可以通过读取相应地址的数据获取芯片属性信息,如容量、电压等信息。

10.CFI规范

读取芯片容量(从地址0x27处读取容量大小 ):

10.CFI规范1

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
// linux-2.6.22.6/drivers/mtd/maps/physmap.c

// make menuconfig时配置生成的.config中,定义了CONFIG_MTD_PHYSMAP_LEN宏
// 所以这里将会定义#define PHYSMAP_COMPAT,
// 进而会在驱动入口函数中执行platform_device_register接口调用。
#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)
{
/* 1. 分配一个physmap_flash_info结构体,里面包含map_info、nand_chip结构体 */
struct physmap_flash_info *info;
info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL);

/* 2.0 设置map_info结构体;从device的resource中解析nor flash的物理基地址、长度、位宽等信息 */
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);

/* 2.1 设置map_info结构体;提供nor flash的硬件相关读写接口 */
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;

/* 3. 以nor flash规范识别型号信息,构造出mtd_info结构体 */
const char **probe_type = { "cfi_probe", "jedec_probe", "map_rom", NULL };
info->mtd = do_map_probe(*probe_type, &info->map);

/* 4. 添加mtd分区,优先级为:"cmdlinepart", "RedBoot",physmap_data->nr_parts*/
#ifdef CONFIG_MTD_PARTITIONS
// { "cmdlinepart", "RedBoot", NULL },添加cmdlinepart分区和RedBoot分区
// 从uboot命令行或"RedBoot"获取分区信息,然后添加分区,并为其创建对应的字符设备节点和块设备节点
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;

// 在设备信息中配置了分区的话,就添加对应的分区。这里没有配置,即physmap_data->nr_parts=0,没有要添加额外的分区。
if (physmap_data->nr_parts) {
add_mtd_partitions(info->mtd, physmap_data->parts,
physmap_data->nr_parts);
return 0;
}
#endif

// 一个主分区,直接调用add_mtd_device创建对应的字符设备节点和块设备节点。
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
// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
spin_lock(&chip_drvs_lock);
// 将协议层接口放到chip_drvs_list链表中
list_add(&drv->list, &chip_drvs_list);
spin_unlock(&chip_drvs_lock);
}

//linux-2.6.22.6/drivers/mtd/chips/cfi_probe.c
static struct chip_probe cfi_chip_probe = {
.name = "CFI",
.probe_chip = cfi_probe_chip
};

struct mtd_info *cfi_probe(struct map_info *map)
{
/*
* Just use the generic probe stuff to call our CFI-specific
* chip_probe routine in all the possible permutations, etc.
*/
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
// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
// *probe_type--"cfi_probe"
info->mtd = do_map_probe(*probe_type, &info->map) ->
drv = get_mtd_chip_driver(name) ->
......
// 从chip_drvs_list链表中取出对应协议层接口
// chip_drvs_list链表在register_mtd_chip_driver接口中设置
// register_mtd_chip_driver接口在协议层调用
// cfi_probe_init/jedec_probe_init/map_ram_init
list_for_each(pos, &chip_drvs_list)
this = list_entry(pos, typeof(*this), list);
if (!strcmp(this->name, name)) {
ret = this;
break;
}
......
return ret;
// 执行协议层的probe函数 即上面分析的cfi_probe接口
drv->probe(map)-> //即cfi_probe(map)
// cfi_chip_probe 结构体中有一个.probe_chip接口(cfi_probe_chip)
mtd_do_chip_probe(map, &cfi_chip_probe)->
genprobe_ident_chips(map, cp) ->
genprobe_new_chip(map, cp, &cfi) ->
// 在该函数中,进入通过命令CFI模式,读取芯片信息
cp->probe_chip(map, 0, NULL, cfi) -> //即cfi_probe_chip
// 进入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"
qry_present(map,base,cfi)
// 读取芯片信息,比如cfi->cfiq->P_ID = 0x0000 0002
// 由芯片手册知P_ID: Primary vendor command set and control interface ID code
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);
.....
// 根据上面读取到的P_ID调用相应的接口申请并设置struct mtd_info结构体
mtd = check_cmd_set(map, 1) ->
......
case 0x0002:
return cfi_cmdset_0002(map, primary) ->
.......
mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
//mtd_info的私有数据为map_info,包含读写操作最终的硬件接口
mtd->priv = map;
mtd->type = MTD_NORFLASH;

/* Fill in the default mtd operations */
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
// linux-2.6.22.6/drivers/mtd/chips/chipreg.c
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
spin_lock(&chip_drvs_lock);
// 将协议层接口放到chip_drvs_list链表中
list_add(&drv->list, &chip_drvs_list);
spin_unlock(&chip_drvs_lock);
}

// linux-2.6.22.6/drivers/mtd/chips/jedec_probe.c
static struct chip_probe jedec_chip_probe = {
.name = "JEDEC",
.probe_chip = jedec_probe_chip
};

static struct mtd_info *jedec_probe(struct map_info *map)
{
/*
* Just use the generic probe stuff to call our CFI-specific
* chip_probe routine in all the possible permutations, etc.
*/
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) ->
......
// 从chip_drvs_list链表中取出对应协议层驱动
// chip_drvs_list链表在register_mtd_chip_driver接口中设置
// register_mtd_chip_driver接口在协议层调用
// cfi_probe_init/jedec_probe_init/map_ram_init
list_for_each(pos, &chip_drvs_list)
this = list_entry(pos, typeof(*this), list);
if (!strcmp(this->name, name)) {
ret = this;
break;
}
......
return ret;
// 执行协议层的probe函数 即上面分析的cfi_probe接口
drv->probe(map)-> //jedec_probe(map)
// jedec_chip_probe 结构体中有一个.probe_chip接口(jedec_probe_chip)
mtd_do_chip_probe(map, &jedec_chip_probe) ->
genprobe_ident_chips(map, cp) ->
genprobe_new_chip(map, cp, &cfi) ->
// 读取芯片的id,和jedec_table数组比较得到芯片信息
cp->probe_chip(map, 0, NULL, cfi) -> //即jedec_probe_chip
jedec_match( base, map, cfi, &jedec_table[i] )
cfi_jedec_setup(cfi, i) ->
p_cfi->cfiq->P_ID = jedec_table[index].CmdSet;
// 根据读取到的p_cfi->cfiq->P_ID来调用对应接口申请并设置struct mtd_info
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
/*
* 参考 drivers\mtd\maps\physmap.c
*/

#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)
{
/* 1. 分配map_info结构体 */
s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);;

/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
s3c_nor_map->name = "s3c_nor";
s3c_nor_map->phys = 0;
s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
s3c_nor_map->bankwidth = 2;
s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);

simple_map_init(s3c_nor_map);

/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
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;
}

/* 4. add_mtd_partitions */
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
/*
* 参考 drivers\mtd\devices\mtdram.c
*
*/

#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; //10M
static unsigned long erase_size = 1*1024; //1M

#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,
}
};

// We could store these in the mtd structure, but we only support 1 device..
static struct mtd_info *mtd_info;

static int check_offs_len(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
int ret = 0;

/* Start address must align on block boundary */
if (mtd_mod_by_eb(ofs, mtd)) {
pr_debug("%s: unaligned address\n", __func__);
ret = -EINVAL;
}

/* Length must align on block boundary */
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;
}

/*
* Allow NOMMU mmap() to directly map the device (if not NULL)
* - return the address to which the offset maps
* - return -ENOSYS to indicate refusal to do the mapping
*/
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));

/* Setup the MTD structure */
mtd->name = name;
mtd->type = MTD_NORFLASH;
mtd->flags = MTD_CAP_NORFLASH;
mtd->size = size;
mtd->writesize = 1;
mtd->writebufsize = 64; /* Mimic CFI NOR flashes */
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;

/* 新版本的内核做的实验,add_mtd_partitions改成了mtd_device_register */
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;

/* Allocate some memory */
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");