[sd card] mmc_blk层为sd card创建块设备流程

一、块设备框架

以下内容摘自http://blog.csdn.net/jianchi88/article/details/7212370.

1、块设备框架图

块设备框架图

avatar

2、说明

  • 各个层次说明

    • 由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。
    • 在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在IO调度层中用request结构体描述。
    • 但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求(request结构体)添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
    • Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。
  • 整体数据流程说明
    文件系统——》通用块层——》IO调度层——》块设备驱动层

    • 文件系统会向通用块层发起块读写请求
    • 通用块层将读写请求封装成bio结构体(block io),下发到IO调度层
    • IO调度层将bio封装成request结构体,并将request添加到对应块设备(gendisk)的请求队列(request_queue)中,并做一些预处理。
    • IO调度层对请求队列(request_queue)中的request进行预处理之后,调用请求队列(request_queue)的回调函数来对队列中的IO(request)进行处理
    • 而请求队列(request_queue)以及request处理回调函数则是由块设备驱动层提供,也就是块设备驱动层会从请求队列(request_queue)提取request并且进行实质性的处理。
  • 各个层次的请求(IO)以及请求队列对应的结构体

    • 通用块层:struct bio
    • IO调度层:struct request(封装了struct bio)、struct request_queue
    • 块设备驱动层:
      各类块设备驱动层对于IO请求的封装是不一样的。以sd card的mmc块设备驱动层为例:
      struct mmc_queue_req(封装了struct request)、struct mmc_queue(封装了struct request_queue)。

3、mmc块设备驱动层

块设备驱动有很多个种类,对于sd card来说,使用的是mmc块设备驱动层。
这部分内容可以参考《[mmc subsystem] 概念与框架
mmc块设备驱动层已经属于mmc subsystem结构的一部分。其框架图如下:

avatar
(上图摘自蜗窝科技http://www.wowotech.net/comm/mmc_framework_arch.html

  • 其主要分成三个层次:
    • mmc card drivers层(mmc_blk层)
      向上:为mmc card(如sd card)实现对应的request_queue以及gendisk并注册,生成的对应的块设备文件。
      请求处理:对上层传下来的request进行提取处理
      向下:向mmc core层提交mmc请求
    • mmc core层(只提取部分内容)
      可以参考《[mmc subsystem] mmc core(第一章)——概述
      向上:为mmc card drivers提供注册mmc driver的接口。为mmc card drivers提供发起mmc请求的接口。
      请求处理:异步mmc请求的处理
      向下:向mmc host层提交mmc请求
    • mmc host层(只提取部分内容)
      可以参考《[mmc subsystem] host(第一章)——概述
      对mmc core下发的mmc请求进行处理,然后通过mmc总线发起实际的通讯内容。

5、总结

综上,对于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设备驱动进行展开。

二、mmc_blk实现块设备核心框架

1、实现块设备的驱动框架

通过第一节的内容,可以知道实现块设备主要是实现如下内容

  • 创建struct request_queue
    请求队列,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并进行处理的回调函数。

    扫描二维码关注公众号,回复: 3284986 查看本文章
  • 创建struct gendisk
    struct gendisk用来代表一个独立的磁盘设备或者分区。一个块设备对应一个gendisk。块设备驱动通过创建对应的gendisk并注册来产生对应的块设备节点。
    可以调用alloc_disk来分配一个gendisk。
    分配方法eg:alloc_disk(perdev_minors),perdev_minors表示分配给每个块设备的从设备号数量

  • 初始化struct gendisk
    主要初始化如下内容:

    • 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,块设备名
  • 关联struct gendisk和struct request_queue
    因为IO调度层里面的request_queue一般有不止一个。当数据从通用块层下来的时候,IO调度层只知道要写到哪个gendisk,然后根据gendisk里面获取到对应的request_queue,然后再request往request_queue里面放。
    所以就要求gendisk必须和request_queue关联起来。
    关联方法:struct gendisk->queuerequest_queue.

  • 注册块设备
    不管前面的流程怎么样,最终的目的都是注册一个块设备到系统中。
    可以通过调用add_disk(gendisk)来注册块设备。

2、mmc_blk实现块设备的驱动框架补充

核心框架就是上述“实现块设备的驱动框架”的部分。

以下要特别注意,方便理解代码。

  • 根据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_blk的设备驱动模型实现

对应代码:drivers/mmc/card/block.c

1、mmc bus结构

注意,这里指的是软件结构,而不是指硬件结构。
建议先参考《[mmc subsystem] mmc core(第三章)——bus模块说明
mmc结构体如下图所示

avatar
(上图摘自蜗窝科技http://www.wowotech.net/comm/mmc_framework_arch.html

从硬件上来看,每一个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实现块设备的功能。

2、mmc_blk驱动模型结构实现

  • 根据上述,mmc_blk驱动模型结构要包括如下两点

    • 构造对应的struct mmc_driver
    • 调用mmc_register_driver(struct mmc_driver *drv)将其注册到mmc_bus上
  • 代码如下

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创建块设备流程”的代码说明。

四、mmc_blk层为sd card创建块设备流程(mmc_blk_probe)

0、节点

  • 块设备的dev节点
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,从设备号则是依次递增。

  • 块设备的sys节点
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
...

1、整体核心流程说明

mmc_blk层为sd card创建块设备流程是在mmc_blk_probe中实现的。
复习一下前面的内容,mmc_blk_probe主要做如下事情
1. 创建struct mmc_blk_data
2. 初始化mmc_blk_data
3. 初始化struct mmc_blk_data->mmc_queue
4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
7. 注册mmc_blk_data->gendisk到系统中 (注册块设备)

后面看代码的过程中可以发现整个probe过程都是围绕上述工作进行的。

2、mmc_blk_probe

只考虑sd card的情况,分析和sd card相关的代码。

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_card分配和设置mmc_blk_data **/
/** 在mmc_blk_alloc中会去分配和设置mmc_queue、request_queue、gendisk **/
    md = mmc_blk_alloc(card);   
      // 负责第1小节中的步骤1-6的工作,所以也是分析“mmc_blk层为sd card创建块设备流程”的核心函数
        // 1. 创建struct mmc_blk_data
        // 2. 初始化mmc_blk_data
        // 3. 初始化struct mmc_blk_data->mmc_queue
        // 4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
        // 5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
        // 6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
    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 **/
    mmc_set_drvdata(card, md);
    mmc_fixup_device(card, blk_fixups);

/** 将mmc_blk构造的gendisk注册到系统中,生成对应的块设备 **/
    if (mmc_add_disk(md))
        // 负责第1小节中的步骤7的工作
        // 注册mmc_blk_data->gendisk到系统中 (注册块设备)
        goto out;

/** 将其他物理分区的gendisk注册到系统中,生成对应的块设备。 **/
/** sd card 上只有一个分区,所以这里我们同样不关心 **/
    list_for_each_entry(part_md, &md->part, part) {
        if (mmc_add_disk(part_md))
            goto out;
    }
    return 0;
}

通过上述代码,可以知道对于“mmc_blk层为sd card创建块设备流程”来说,有两个核心代码来实现。

  • mmc_blk_alloc
  • mmc_add_disk

后续主要分析这两个函数的代码。

3、mmc_blk_alloc

需要实现如下内容:
1. 创建struct mmc_blk_data
2. 初始化mmc_blk_data
3. 初始化struct mmc_blk_data->mmc_queue
4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)

代码如下,去掉一些无关内容:

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)) {
        size = card->ext_csd.sectors;      // 对于emmc设备来说,其容量是从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;
    unsigned int percentage =
        BKOPS_SIZE_PERCENTAGE_TO_QUEUE_DELAYED_WORK;

/** 分配一个mmcblk的从设备号 **/
    devidx = find_first_zero_bit(dev_use, max_devices);
    __set_bit(devidx, dev_use);

/** 以下就是分配mmc_blk_data并进行设置,也是mmc_blk_alloc_req的核心工作 **/
/* 对应“1. 创建struct mmc_blk_data” */
    md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);

/* 以下对应“2. 初始化mmc_blk_data” */
   //---- 设置mmc_blk_data->name_idx
    if (!subname) {
        md->name_idx = find_first_zero_bit(name_use, max_devices);
        __set_bit(md->name_idx, name_use);
    } else
        md->name_idx = ((struct mmc_blk_data *)
                dev_to_disk(parent)->private_data)->name_idx;
        // 从name_use中分配一个名称的id号,mmcblk+id号会作为完整的块设备的名称
        // 例如mmcblk0的0就是名称的id号,mmcblk1的1就是名称的id号

   //---- 设置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);   // 给request_queue使用的锁
    INIT_LIST_HEAD(&md->part);   // 挂载其他物理分区的链表
    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;
}

另外,重点关注一下以下几个东西。在mmcblk块设备读写流程的时候会用到。

md->queue.issue_fn = mmc_blk_issue_rq;
md->disk->queue = md->queue.queue;
(mmc_blk_data->mmc_queue) mq->queue = blk_init_queue(mmc_request_fn, lock); 
(mmc_blk_data->mmc_queue) mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", host->index, subname ? subname : "");

4、mmc_add_disk

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

五、补充说明,软件分区对应块设备的创建

在存储设备上有软件分区,例如emmc上可能有很多个软件分区,而sd card上只有一个软件分区,他们也有自己的块设备节点,如下:

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,  65 1970-01-03 03:46 mmcblk1p1

这些都是软件分区对应的块设备节点,但是这些块设备节点是在对mmcblk进行add_disk的时候,从mmcblk上识别出来的,由通用块设备层自己完成,而不需要mmcblk层的关心。
具体代码在kernel/block/partitions/check.c check_partition()中。
调用流程可以参考如下调用堆栈:

[    7.442971] ooonebook check_partition check gendisk mmcblk1
[    7.442977] CPU: 3 PID: 49 Comm: kworker/u8:3 Tainted: G        W    3.10.49-g5e97c06-dirty #64
[    7.442988] Workqueue: kmmcd mmc_rescan
[    7.442989] Call trace:
[    7.442997] [<ffffffc000206b40>] dump_backtrace+0x0/0x270
[    7.443002] [<ffffffc000206dc0>] show_stack+0x10/0x1c
[    7.443007] [<ffffffc000c2ad74>] dump_stack+0x1c/0x28
[    7.443012] [<ffffffc00043c22c>] check_partition+0x34/0x20c      // 在这里识别软件分区
[    7.443016] [<ffffffc00043bf6c>] rescan_partitions+0x90/0x270
[    7.443022] [<ffffffc00032ba84>] __blkdev_get+0x13c/0x320
[    7.443026] [<ffffffc00032be0c>] blkdev_get+0x1a4/0x2c8
[    7.443031] [<ffffffc000439bb8>] add_disk+0x3b8/0x3e8   // 在这里把mmc_blk层创建的gendisk注册到系统中
[    7.443036] [<ffffffc00089b3b8>] mmc_add_disk+0x44/0x228
[    7.443040] [<ffffffc00089ba1c>] mmc_blk_probe+0x224/0x270
[    7.443044] [<ffffffc00088c9d4>] mmc_bus_probe+0x14/0x20
[    7.443050] [<ffffffc0005bc7d8>] driver_probe_device+0x160/0x368
[    7.443055] [<ffffffc0005bca08>] __device_attach+0x28/0x4c
[    7.443060] [<ffffffc0005baa28>] bus_for_each_drv+0x70/0x90
[    7.443064] [<ffffffc0005bc5ec>] device_attach+0x68/0x98
[    7.443069] [<ffffffc0005bb9fc>] bus_probe_device+0x28/0xa0
[    7.443073] [<ffffffc0005b9d90>] device_add+0x324/0x600
[    7.443077] [<ffffffc00088d208>] mmc_add_card+0x2a8/0x36c
[    7.443082] [<ffffffc000893758>] mmc_attach_sd+0x204/0x25c
[    7.443086] [<ffffffc00088c8cc>] mmc_rescan+0x25c/0x2ec
[    7.443091] [<ffffffc0002359e0>] process_one_work+0x258/0x3d0
[    7.443095] [<ffffffc000236ac4>] worker_thread+0x1f0/0x340
[    7.443100] [<ffffffc00023bc94>] kthread+0xac/0xb8

这里就没有进行深入的学习了。

猜你喜欢

转载自blog.csdn.net/ooonebook/article/details/61205469
sd