目录
1. 块设备框架
1.1 mmc块设备驱动层
1.2 总结
2. mmc_blk实现块设备核心框架
2.1 实现块设备的驱动框架
2.1.1 创建struct request_queue
2.1.2 创建struct gendisk
2.1.3 初始化struct gendisk
2.1.4 关联struct gendisk和struct request_queue
2.1.5 注册块设备
2.2 mmc_blk实现块设备的驱动框架补充
3. mmc_blk的设备驱动模型实现
3.1 mmc bus结构
3.2 mmc_blk驱动模型结构实现
4. mmc_blk_probe
4.1 块设备的dev节点
4.2 块设备的sys节点
4.3 mmc_blk_probe
4.3.1 mmc_blk_alloc
4.3.2 mmc_add_disk
5. 读写流程
6. 参考文献
参考代码:drivers\mmc\core\block.c
参考文章:
块设备描述1
块设备描述2
块设备驱动有很多个种类,对于sd card来说,使用的是mmc块设备驱动层。
这部分内容可以参考《[mmc subsystem] 概念与框架》
mmc块设备驱动层已经属于mmc subsystem结构的一部分。其框架图如下:
其主要分成三个层次:
(1)mmc card drivers层(mmc_blk层):
向上:为mmc card(如sd card)实现对应的request_queue以及gendisk并注册,生成的对应的块设备文件。
请求处理:对上层传下来的request进行提取处理
向下:向mmc core层提交mmc请求
(2)mmc core层(只提取部分内容):
可以参考《[mmc subsystem] mmc core(第一章)——概述》
向上:为mmc card drivers提供注册mmc driver的接口。为mmc card drivers提供发起mmc请求的接口。
请求处理:异步mmc请求的处理
向下:向mmc host层提交mmc请求
(3)mmc host层(只提取部分内容):
可以参考《[mmc subsystem] host(第一章)——概述》
对mmc core下发的mmc请求进行处理,然后通过mmc总线发起实际的通讯内容。
综上,对于sd card而言,整个数据流是:
文件系统——》通用块层——》IO调度层——》mmc_blk层——》mmc core层——》mmc host层——》mmc硬件总线——》sd card。
而这里我们要学习的就是mmc_blk层。
对应代码是drivers/mmc/card/block.c、driver/mmc/card/queue.c
同时,我们也知道了mmc_blk层的核心内容是实现块设备,为了实现块设备,实现块设备的核心就是要实现对应的gendisk以及request_queue。后续就是围绕这个内容对mmc_blk设备驱动进行展开。
请求队列,IO调度层会把IO请求(struct request)往对应块设备的这个结构体里面挂,然后调用request_queue里面的回调函数对request进行处理。
可以调用blk_init_queue来创建一个request_queue。
创建方法eg:blk_init_queue(mmc_request_fn, lock),其中,mmc_request_fn则是从request_queue提取request并进行处理的回调函数。
struct gendisk用来代表一个独立的磁盘设备或者分区。一个块设备对应一个gendisk。块设备驱动通过创建对应的gendisk并注册来产生对应的块设备节点。
可以调用alloc_disk来分配一个gendisk。
分配方法eg:alloc_disk(perdev_minors),perdev_minors表示分配给每个块设备的从设备号数量
struct gendisk->major,块设备的主设备号,对于mmcblk块设备来说,其主设备号是MMC_BLOCK_MAJOR,179
struct gendisk->first_minor,分配的第一个从设备号
struct gendisk->fops,块设备的操作函数,对于mmcblk来说,是mmc_bdops
struct gendisk->private_data,私有数据指针,一般关联到对应块设备驱动的数据结构体
struct gendisk->driverfs_dev,父sys节点
struct gendisk->flags
struct gendisk->disk_name,块设备名
因为IO调度层里面的request_queue一般有不止一个。当数据从通用块层下来的时候,IO调度层只知道要写到哪个gendisk,然后根据gendisk里面获取到对应的request_queue,然后再request 往request_queue里面放。
所以就要求gendisk必须和request_queue关联起来。
关联方法:struct gendisk->queuerequest_queue.
不管前面的流程怎么样,最终的目的都是注册一个块设备到系统中。
可以通过调用add_disk(gendisk)来注册块设备。
根据mmc_blk的实现方式,有如下注意点:
构造mmc_queue_req作为一个MMC IO请求,把从request_queue提取request封装到这里面来。
构造mmc_queue作为块设备的请求队列,把request_queue封装到mmc_queue中。同时存储 了当前正在处理以及上一次正在处理的MMC IO请求mmc_queue_req。
构造mmc_blk_data作为块设备的私有数据,同时把mmc块设备请求对应mmc_queue以及块设备gendisk存储到mmc_blk_data中。
同时mmc_blk_data也存放了mmc_blk驱动相关的部分内容。
因此mmc_blk实现块设备的整体流程如下:
创建struct mmc_blk_data
初始化mmc_blk_data
初始化struct mmc_blk_data->mmc_queue
创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
注册mmc_blk_data->gendisk到系统中 (注册块设备)
从硬件上来看,每一个mmc host对应一条实际的mmc总线。
但是mmc subsystem只存在一条虚拟的mmc bus。并且mmc host并不会作为这个设备驱动总线模型的一个部分。
相应的:
在mmc bus上挂载的device是由mmc core根据实际mmc设备抽象出来的card设备。
例如在sd.c中,mmc_attach_sd会构造mmc_card,并且调用mmc_add_card(struct mmc_card *card)将mmc_card挂在了mmc_bus上。
而mmc_card就是mmc_bus上的device。
在mmc bus上挂载的driver是在card目录下实现的card driver,用于驱动虚拟card设备、对接其他subsystem,实现其实际的功能。
例如在block.c中,mmc_blk_init会构造一个mmc_driver,并且调用mmc_register_driver(struct mmc_driver *drv)将mmc_driver挂在了mmc_bus上。
而mmc_driver就是mmc_bus上driver。
根据mmc_bus的匹配规则(mmc_bus_match),只要有mmc_card注册到mmc_bus上,就会匹配所有mmc_driver,也就是所有mmc_driver的probe都会执行。
然后mmc_driver就开始为mmc_card实现对应的功能了,比如mmc_blk这个mmc_driver就是为mmc_card实现块设备的功能。
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
.shutdown = mmc_blk_shutdown,
};
static int __init mmc_blk_init(void)
{
res = mmc_register_driver(&mmc_driver);
// mmc_driver注册到mmc_bus上,当有mmc_card注册到mmc_bus上时,其probe方法就会执行。
}
module_init(mmc_blk_init);
随后可以查询到mmkblk driver的节点为/sys/bus/mmc/drivers/mmcblk,内容如下
root@:/sys/bus/mmc/drivers/mmcblk # ls
ls
bind
mmc0:0001 // 通过该driver probe成功的mmc_card,这里是一个emmc设备
mmc1:e624 // 通过该driver probe成功的mmc_card,这里是一个sd设备
uevent
unbind
mmc_blk_probe就是为mmc_card(比如sd card、emmc)实现存储设备的功能的核心,也就是将mmc_card实现出对应块设备的实现所在,也就是这里要学习的入口函数。后续会从mmc_blk_probe展开对“mmc_blk层为sd card创建块设备流程”的代码说明。
root@:/dev/block # ls -l | grep mmcblk
brw------- root root 179, 0 1970-01-03 03:46 mmcblk0
brw------- root root 179, 1 1970-01-03 03:46 mmcblk0p1
brw------- root root 179, 10 1970-01-03 03:46 mmcblk0p10
brw------- root root 179, 11 1970-01-03 03:46 mmcblk0p11
brw------- root root 179, 12 1970-01-03 03:48 mmcblk0p12
brw------- root root 179, 13 1970-01-03 03:46 mmcblk0p13
...
brw------- root root 179, 32 1970-01-03 03:46 mmcblk0rpmb
brw------- root root 179, 64 1970-01-03 03:46 mmcblk1
brw------- root root 179, 65 1970-01-03 03:46 mmcblk1p1
可以观察到设备都是以mmcblk为前缀,并且主设备号为179,从设备号则是依次递增。
root@msm8916_64:/sys/bus/mmc/devices/mmc1:e624/block/mmcblk1 # ls
alignment_offset bdi bkops_check_threshold capability
dev device discard_alignment ext_range force_ro
holders inflight mmcblk1p1 no_pack_for_random num_wr_reqs_to_start_packing
power queue range removable ro
...
mmc_blk_probe
1. mmc_blk_alloc
mmc_blk_alloc_req
ida_simple_get
alloc_disk
mmc_init_queue
blk_alloc_queue
blk_queue_logical_block_size
2. mmc_add_disk
device_add_disk
static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
char cap_str[10];
/*
* Check that the card supports the command class(es) we need.
*/
/** 判断card是不是一个block设备 **/
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
return -ENODEV;
// 前面说过了,只要是mmc_card(包括SDIO card)被注册到mmc_bus上,
// 那么所有mmc_bus上的mmc_driver都会被匹配到.
// 而mmc_blk只使用于存储设备(emmc、sd card、mmc card),
// 并不能驱动于SDIO card,因此,这里根据是否支持块读写属性
// 判断card是不是一个存储设备,如果不是的话,说明并不能使
// 用mmc_blk这个mmc_driver来驱动mmc_card.
mmc_fixup_device(card, mmc_blk_fixups);
/** 为mmc_card分配和设置mmc_blk_data **/
/** 在mmc_blk_alloc中会去分配和设置mmc_queue、request_queue、gendisk **/
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
...............
/** 一个存储设备上(例如emmc)上可能有多个物理分区,这里用于为这些物理分区(例如rpmb分区)分配和设置mmc_blk_data **/
/** SD card上只有一个物理分区,所以我们这里不care,后面学习emmc的时候再说明 **/
if (mmc_blk_alloc_parts(card, md))
goto out;
/** 关联mmc_card和mmc_blk_data **/
dev_set_drvdata(&card->dev, md);
/** 将mmc_blk构造的gendisk注册到系统中,生成对应的块设备 **/
if (mmc_add_disk(md))
goto out;
/** 将其他物理分区的gendisk注册到系统中,生成对应的块设备。 **/
/** sd card 上只有一个分区,所以这里我们同样不关心 **/
list_for_each_entry(part_md, &md->part, part) {
if (mmc_add_disk(part_md))
goto out;
}
/* Add two debugfs entries */
mmc_blk_add_debugfs(card, md);
..............
}
通过上述代码,可以知道对于“mmc_blk层为sd card创建块设备流程”来说,有两个核心代码来实现。
后续主要分析这两个函数的代码。
static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
{
sector_t size;
struct mmc_blk_data *md;
/** 以下先获取card容量,以扇区为单位 **/
if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
// 对于emmc设备来说,其容量是从ext_csd寄存器的sectors域获取
size = card->ext_csd.sectors;
} else {
size = card->csd.capacity << (card->csd.read_blkbits - 9);
// 对于sd card来说,其容量是从csd的capacity域获取的
// 计算方法memory capacity = (C_SIZE+1) * 512K byte
// 可以参考SD 3.0协议中的5.3.3节
}
/** 调用mmc_blk_alloc_req来实现前面所说的工作 **/
md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL,
MMC_BLK_DATA_AREA_MAIN);
// 参数说明如下:
// card:对应的mmc设备,mmc_card
// card->dev:作为块设备的sys节点的父设备,/sys/bus/mmc/devices/mmc1:e624/block/mmcblk1,这里的mmc1:e624就是card->dev
// size:块设备的大小,也就是card的容量
// false:bool default_ro,默认并不作为只读设备
// NULL:subname,块设备的后缀名,例如mmcblk0rpmb,rpmb就是后缀名
// MMC_BLK_DATA_AREA_MAIN:分区类型,UDA分区
return md;
}
/-----------------------------mmc_blk_alloc_req实现------------------------------/
static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
struct device *parent,
sector_t size,
bool default_ro,
const char *subname,
int area_type)
{
struct mmc_blk_data *md;
int devidx, ret;
devidx = ida_simple_get(&mmc_blk_ida, 0, max_devices, GFP_KERNEL);
if (devidx < 0) {
/*
* We get -ENOSPC because there are no more any available
* devidx. The reason may be that, either userspace haven't yet
* unmounted the partitions, which postpones mmc_blk_release()
* from being called, or the device has more partitions than
* what we support.
*/
if (devidx == -ENOSPC)
dev_err(mmc_dev(card->host),
"no more device IDs available\n");
return ERR_PTR(devidx);
}
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
if (!md) {
ret = -ENOMEM;
goto out;
}
//---- 设置mmc_blk_data->area_type
md->area_type = area_type; // 物理分区类型,SD card只有一个物理分区,对应为MMC_BLK_DATA_AREA_MAIN
//---- 设置mmc_blk_data->read_only
md->read_only = mmc_blk_readonly(card); // 设置只读属性
//---- 分配mmc_blk_data->disk
md->disk = alloc_disk(perdev_minors); // 调用alloc_disk分配一个gendisk结构体
//---- 设置mmc_blk_data->lock、part、usage
spin_lock_init(&md->lock);
INIT_LIST_HEAD(&md->part);
INIT_LIST_HEAD(&md->rpmbs);
md->usage = 1; // 使用计数设置为1
//---- 核心:设置mmc_blk_data->queue,会调用mmc_init_queue来分配和设置mmc_blk_data->queue,在这里面会创建对应的request_queue.
/* 以下对应“3. 初始化struct mmc_blk_data->mmc_queue” */
/* 以下对应“4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)” */
ret = mmc_init_queue(&md->queue, card, &md->lock, subname); // 后面说明
md->queue.issue_fn = mmc_blk_issue_rq; // 将mmc_queue的IO请求(request)下发方法设置为mmc_blk_issue_rq
md->queue.data = md; // 关联mmc_queue和mmc_blk_data
//---- 核心:设置mmc_blk_data->disk
/* 以下对应“5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)” */
// 分配gendisk放在前面完成了
md->disk->major = MMC_BLOCK_MAJOR; // 设置块设备的主设备号为MMC_BLOCK_MAJOR,179
md->disk->first_minor = devidx * perdev_minors; // 设置块设备的从设备号
md->disk->fops = &mmc_bdops; // 设置操作集
md->disk->private_data = md; // 关联gendisk和mmc_blk_data
/* 以下对应“6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)” */
md->disk->queue = md->queue.queue; // 重要,关联gendisk和request_queue!!!
md->disk->driverfs_dev = parent;
set_disk_ro(md->disk, md->read_only || default_ro); // 设置gendisk的只读属性
md->disk->flags = GENHD_FL_EXT_DEVT; // 设置gendisk的一些标识
if (area_type & MMC_BLK_DATA_AREA_RPMB)
md->disk->flags |= GENHD_FL_NO_PART_SCAN;
snprintf(md->disk->disk_name, sizeof(md->disk->disk_name), // 设置块设备的设备名,例如mmcblk0、mmcblk1,而mmcblk0rpmb中的rpmb则是指subname
"mmcblk%d%s", md->name_idx, subname ? subname : "");
// 设置逻辑块大小,这里先不关心
if (mmc_card_mmc(card))
blk_queue_logical_block_size(md->queue.queue, card->ext_csd.data_sector_size);
else
blk_queue_logical_block_size(md->queue.queue, 512);
set_capacity(md->disk, size); // 设置gendisk的容量,size是以扇区为单位。
return md;
}
/-----------------------------mmc_init_queue实现------------------------------/
// 两个重要的功能
// 1、创建request_queue并封装到mmc_queue中
// 2、创建IO请求(request)的提取和处理进程
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname)
{
struct mmc_host *host = card->host;
u64 limit = BLK_BOUNCE_HIGH;
int ret;
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
limit = *mmc_dev(host)->dma_mask;
mq->card = card;
/** 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue) **/
mq->queue = blk_init_queue(mmc_request_fn, lock); // request的处理回调函数设置为mmc_request_fn
if (!mq->queue)
return -ENOMEM;
if ((host->caps2 & MMC_CAP2_STOP_REQUEST) &&
host->ops->stop_request &&
mq->card->ext_csd.hpi_en)
blk_urgent_request(mq->queue, mmc_urgent_request);
mq->mqrq_cur = mqrq_cur;
mq->mqrq_prev = mqrq_prev;
mq->queue->queuedata = mq;
mq->num_wr_reqs_to_start_packing =
min_t(int, (int)card->ext_csd.max_packed_writes,
DEFAULT_NUM_REQS_TO_START_PACK);
blk_queue_prep_rq(mq->queue, mmc_prep_request);
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
if (mmc_can_erase(card))
mmc_queue_setup_discard(mq->queue, card);
success:
sema_init(&mq->thread_sem, 1);
//---- 创建IO请求(request)的提取和处理进程
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", host->index, subname ? subname : "");
if (IS_ERR(mq->thread)) {
ret = PTR_ERR(mq->thread);
goto free_bounce_sg;
}
return 0;
}
static int mmc_add_disk(struct mmc_blk_data *md)
{
int ret;
struct mmc_card *card = md->queue.card;
/** 注册mmc_blk_data->gendisk到系统中 (注册块设备) **/
add_disk(md->disk); // 直接调用add_disk将mmc_blk_data->gendisk注册到系统中
// 此处略掉很多关于属性的代码
return ret;
}
mmc_init_queue(drivers\mmc\core\queue.c)
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
host->index, subname ? subname : "");
mmc_queue_thread->mmc_blk_issue_rq->mmc_blk_issue_rw_rq (drivers\mmc\core\block.c)
mmc_queue_thread
1. blk_fetch_request
2. mmc_blk_issue_rq
2.1 mmc_blk_issue_rw_rq
2.1.1 mmc_blk_rw_rq_prep
2.1.2 mmc_start_areq-->__mmc_start_data_req->mmc_start_request-->__mmc_start_request->sdhci_request
[sd card] mmc_blk层为sd card创建块设备流程_ooonebook的博客-CSDN博客_mmcblk
[sd card] sd card块设备(mmc_blk)读写流程学习笔记_ooonebook的博客-CSDN博客_mmcblk