zoukankan      html  css  js  c++  java
  • Linux mmc framework2:基本组件之block

    1.前言

    本文主要block组件的主要流程,在介绍的过程中,将详细说明和block相关的流程,涉及到其它组件的详细流程再在相关文章中说明。

    2.主要数据结构和API

    2.1 struct mmc_card

    Elemete Name struct mmc_card
    Path include/linux/mmc/card.h
    Responsiblities

     是对mmc device的抽象,由于定义了mmc_bus_type类型的总线,此处mmc_card是与mmc_bus_type配套

    Attributions
    • host:struct mmc_host *类型,这个mmc device属于哪个host管理;
    • dev:struct device类型,代表设备驱动模型中的一个 device
    • ocr:当前的操作电压设置
    • rca:device的relative card address
    • type:卡的类型,包括MMC/SD/SDIO/COMBO(SDIO+MEM)
    • state:卡的状态,在线、只读、是否使用block地址、是否是SDXC卡、卡被移除、卡在BKOPS、卡在suspend
    • quirks:卡的一些其它怪癖属性
    • erase_size:单位sectors
    • erase_shift:可以擦除的 sectors是2的多少次方
    • pref_erase:单位sectors
    • eg_boundary
    • erased_byte:擦除的字节数
    • raw_cid:原始的CID值
    • raw_csd:原始的CSD值
    • raw_scr:原始的raw_scr值
    • cid:struct mmc_cid类型,卡identification
    • csd:struct mmc_csd类型,保存从卡的CSD寄存器读取的内容
    • ext_csd:struct mmc_ext_csd类型,卡扩展信息
    • scr:其它的SD信息
    • ssr:更多的SD信息
    • sw_caps:swicth能力
    • sdio_funcs:SDIO功能的个数
    • cccr:struct sdio_cccr类型,卡的通常信息
    • cis:struct sdio_cis
    • sd_bus_speed:bus speed mode
    • mmc_avail_type:host和card都支持的设备类型
    • drive_strength:驱动能力,用于UHS-I, HS200 or HS400
    • debugfs_root:struct dentry *类型,用于debugfs显示根目录
    • part:struct mmc_part类型,物理分区
    • nr_parts:物理分区的个数
    Operations

    2.2 struct mmc_driver

    Elemete Name struct mmc_driver
    Path include/linux/mmc/card.h
    Responsiblities

    mmc driver,由于定义了mmc_bus_type类型的总线,此处mmc_driver是与mmc_bus_type配套

    Attributions
    • drv:struct device_driver类型
    • probe,remove,shutdown:mmc driver相关函数
    Operations
    •  int mmc_register_driver(struct mmc_driver *drv)

     设置总线类型,并将drv加入到设备驱动模型中

    • void mmc_unregister_driver(struct mmc_driver *drv)

    将drv从设备驱动模型中移除

    2.3 struct mmc_blk_data

    Elemete Name struct mmc_blk_data
    Path drivers/mmc/card/block.c
    Responsiblities

    mmc_blk_data为block的核心结构体,用于存放mmc block的一些数据,与mmc slot对应

    Attributions
    • lock:spinlock_t类型
    • disk:struct gendisk *类型,代表一个磁盘设备
    • queue:struct mmc_queue类型,请求队列
    • part:分区链表
    • flags:
    • usage:
    • read_only:
    • part_type:
    • name_idx:
    • reset_done:
    • part_curr:
    • force_ro:
    • power_ro_lock:
    • area_type:
    Operations

    3. 主要流程

    3.1 mmc_blk_init

    mmc_blk_init->

      初始化max_devices

      register_blkdev

      mmc_register_driver

    module_init(mmc_blk_init)会执行到此函数

    • 初始化max_devices:设定最多支持多少个mmc块设备给max_devices
    每类块设备支持256个次设备号,每个块设备有16个次设备号(16个分区),由此得出支持的最大的mmc块设备数max_devices为256/16=16,每个此设备号对应一个分区?
    • register_blkdev:向全局的struct blk_major_name类型的数组major_names注册本块设备的主设备号和设备名
    mmc子系统对于上层block子系统来讲是首先抽象为一个普通的块设备。
    通过register_blkdev向block子系统注册一个block设备,主设备号为MMC_BLOCK_MAJOR,设备名为“mmc”。

    通过分配一个blk_major_name结构体,来保存主设备号和设备名,blk_major_name被保存到全局的blk_major_name数组中。
    如果不指定主设备号,将查询全局的blk_major_name结构体找到一个未用的主设备号来使用,并将此主设备号作为返回值返回。

    major_names中的信息会出现在/proc/devices中。
    因此可以看出,注册做的事情实际上非常少。注册完成后,除了能够在/proce/devices中看到设备之外,不能对设备做任何事情,设备还无法使用,只有当block_device与gendisk建立关联用户空间才可以访问
    • mmc_register_driver(&mmc_driver)
    设备驱动模型中通过driver_register将mmc_driver注册到mmc_bus_type上

    3.2 mmc_blk_exit

    mmc_blk_exit->

      mmc_unregister_driver

      unregister_blkdev

    在退出的时候会执行mmc_blk_exit,与mmc_blk_init相反的动作,主要包括:

    • mmc_unregister_driver(&mmc_driver)
    从mmc_bus_type上将mmc_driver注销
    •  unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
    从全局的struct blk_major_name类型的数组major_names中注销主设备号为MMC_BLOCK_MAJOR名为mmc的blk_major_name结构体
    释放对应的blk_major_name结构体

    3.3 mmc_blk_probe

    mmc_blk_probe->

           mmc_blk_alloc->

                  mmc_blk_alloc_req->

            alloc_disk

                         mmc_init_queue->

              blk_queue_prep_rq

                                kthread_run(mmc_queue_thread, mq);

      mmc_blk_alloc_parts(card, md))

      mmc_add_disk->

          block_add_disk

    初始化时mmc_blk_init中会执行mmc_register_driver,而前文所述执行mmc_attach_mmc时会通过mmc_add_card将mmc_card注册到mmc bus,这样就触发了执行前文所述的mmc_blk_probe函数,后面有详细解释mmc_blk_probe的执行过程

    mmc_blk_probe最主要的是初始化了request queue;初始化disk,同时通过mmc_add_disk将磁盘添加到系统中,使之可用

    • mmc_blk_alloc_req

    创建并初始化请求队列,启动线程循环抓取请求队列中的request,调用request处理函数进行处理

    1分配mmc_blk_data结构体md并初始化,同时mmc_queue作为mmc_blk_data的成员也被创建
    mmc_blk_data为block的核心结构体,与mmc_card关联,用于存放mmc_card相关数据,每个mmc slot即每个mmc设备对应一个mmc_blk_data结构体。
    此处会分配mmc_blk_data结构体md,同时mmc_queue作为mmc_blk_data的成员也被创建。并标识dev_use的bitmap来记录已经分配的mmc device,

    也就是说dev_use是与实际的物理设备相对应的,不是跟分区对应的,dev_use的index用dev_idx来记录
    注意到此处MMC_BLK_DATA_AREA_MAIN表示主分区的区域(mmc_blk_data与设备对应,此处看又像是与分区对应??)。
    MMC分区类型包括如下几种:
    #define MMC_BLK_DATA_AREA_MAIN  (1<<0)
    #define MMC_BLK_DATA_AREA_BOOT  (1<<1)
    #define MMC_BLK_DATA_AREA_GP    (1<<2)
    #define MMC_BLK_DATA_AREA_RPMB  (1<<3)2) alloc_disk(perdev_minors):分配gendisk结构体保存到md中,gendisk与磁盘设备对应
    
    (3)mmc_init_queue(queue_c):创建并初始化请求队列
    通过调用block子系统接口blk_init_queue来初始化请求队列,其中mmc_request_fn为处理请求的回调函数
    blk_queue_prep_rq(mq->queue, mmc_prep_request)设定requet_queue的prep回调函数;
    mmc_alloc_sg(host->max_segs, &ret)分配max_segs个scatterlist用于request请求(只是分配scatterlist,并未分配存放数据的内存),
    返回分配的scatterlist个数
    kthread_run(mmc_queue_thread, mq) 起一个kennel thread运行
    mmc_queue_thread来处理上层发送下来的request,对每个reqeust执行issue_fn回调

    注:issue_fn回调在下面指定为mmc_blk_issue_rq
    (4)指定issue_fn回调为mmc_blk_issue_rq,mmc_blk_issue_rq是具体的mmc request处理函数
    • mmc_blk_alloc_parts(card, md))
    • mmc_add_disk
    为了将一个磁盘添加到系统中,对系统可用,必须初始化磁盘数据结构并调用add_disk方法。
    需要特别注意的是一旦调用了add_disk,磁盘就被“激活”了,系统随时都可能会调用该磁盘提供的各种方法,
    甚至在该函数返回之前就会调用,因而在完成磁盘结构的初始化之前,不要调用add_disk。

    3.4 mmc_add_disk

    mmc_add_disk->

      device_add_disk

    device_add_disk的原型为void device_add_disk(struct device *parent, struct gendisk *disk)
    
    它完成的工作主要包括:
    (1)根据磁盘的主次设备号信息为磁盘分配设备号;
    (2)调用disk_alloc_events初始化磁盘的事件(alloc|add|del|release)处理机制。在最开始磁盘事件会被设置为被阻塞的。
    (3)调用bdi_register_dev将磁盘注册到bdi_list,注:bdi用于将page_cache或buffer_cache中的脏数据刷新到磁盘
    (4)调用blk_register_region将磁盘添加到bdev_map中(通过设备号可以获取kobject从而得到包含它的父对象进行操作)
    (5)调用register_disk将磁盘添加到系统中。
    (6)调用blk_register_queue注册磁盘的请求队列。主要是为队列和队列的调度器在设备的sys文件系统目录中创建相应的sys目录/文件,并且发出uevent事件。
    (7)调用disk_add_events完成在/sys文件系统的设备目录下创建磁盘的事件属性文件,将磁盘事件添加到全局链表disk_events中,解除对磁盘事件的阻塞。

    关于probe函数是如何被调用到的?

    一般我们认为mmc_blk_probe的执行一定需要mmc_driver与mmc_device的匹配才可以,实际上没有mmc_device,  而是有mmc_card,mmc_blk_probe的执行经历如下历程:

    (1)先来看mmc_register_driver的流程

           mmc_register_driver->

                  driver_register->

                         driver_find//bus查看driver是否已经注册,如果已经注册则退出,否则bus add driver

                         bus_add_driver->

                                driver_attach->

                                       bus_for_each_dev//此处由于还没有device注册,因此会退出

    显然mmc_blk_probe的执行不是在mmc_register_driver的时候,那么肯定是在device_register的时候,看看我们的假设是否正确,继续往下看

     

    (2)mmc_alloc_card, mmc_add_card

    通过浏览代码,我们发现在mmc/core/bus_c中有mmc_alloc_card和mmc_add_card

    mmc_alloc_card:mmc_attach_mm->mmc_init_card初始化并分配一个新的mmc_card结构体,实际上是创建device设备;

    mmc_add_card:mmc_attach_mmc->mmc_add_card时调用,通过调用device_add(&card->dev)来完成设备的注册,过程如下:

                  mmc_add_card->

                                device_add->

                                       bus_probe_device->

                                              device_attach->

                                                     __device_attach->

                                                                   driver_match_device->

                                                                   mmc_bus_match//此函数的特殊之处在于总是返回值为1

                                                            driver_probe_device->

                                                                   really_probe->

                                                                          mmc_bus_probe->

                                                                                 mmc_blk_probe

    mmc_blk_probe的执行不是靠device和driver的匹配,而是将匹配函数mmc_bus_match总是返回1,如下:

    static int mmc_bus_match(struct device *dev, struct device_driver *drv)

    {

           return 1;

    }

    这样就可以执行到mmc_bus_type的probe函数进而执行到mmc_blk_probe。

    3.5 mmc_queue_thread

    线程处理函数,用于循环抓取请求队列中的request并交给请求处理函数进行处理

    mmc_queue_thread->

           blk_fetch_request

             issue_fn(mmc_blk_issue_rq)->

                 mmc_blk_issue_rw_rq->

                     mmc_blk_rw_rq_prep

                     mmc_start_req –>

                         mmc_wait_for_data_req_done->

                             mmc_blk_err_check

                             host->ops->request

     mmc_queue_thread是在mmc_init_quene中起的线程,主要作用是完成上层发送的请求进行处理

    •  blk_fetch_request
    从请求队列中取出一个request
    • issue_fn
    由前面可知issue_fn在mmc_blk_probe->mmc_blk_alloc_req时将issue_rq初始化为mmc_blk_issue_rq,请求有几种包括:discard, flush, 以及rw
    • mmc_blk_issue_rw_rq
    首先通过mmc_blk_rw_rq_prep来做一些准备工作,获取命令号、命令参数等,然后通过mmc_start_req发起请求
    • mmc_start_req
    通过mmc_wait_for_data_req_done发起真正的请求,并等待请求结束。
    mmc_wait_for_data_req_done会回调控制器的request函数发起请求,然后mmc_blk_err_check检查是否有错误发生,
    如果有错误发生将尝试recovery进行修复开始新的传输

    3.6 mmc_blk_issue_rq

    mmc_blk_issue_rq->

       mmc_claim_host

      mmc_blk_part_switch

      mmc_blk_issue_rw_rq->

        mmc_blk_prep_packed_list

        mmc_blk_rw_rq_prep

        mmc_start_req->

          __mmc_start_data_req

        mmc_queue_bounce_post

        检查mmc_start_req返回的状态

    mmc_blk_issue_rq对发送的mmc request进行具体的处理。

    • mmc_claim_host
    实际上是声明当前进程占有host controller,如果有其它进程占有则需要等待,详细的可参考Linux mmc framework2:基本组件之core
    • mmc_blk_part_switch
    通过MMC_SWITCH命令对EXT_CSD寄存器的PARTITION_CONFIG(bit[179])进行设置,主要包括boot是否使能、用哪个分区做boot分区、选择要访问的分区。
    如果MMC_SWITCH命令出错,将通过blk_end_request_all终止request
    • mmc_blk_issue_rw_rq
    根据req->cmd_flags的命令做不同的事情。REQ_SANITIZE、REQ_DISCARD、REQ_FLUSH分别为
    . mmc_blk_issue_secdiscard_rq 和mmc_blk_issue_discard_rq
    . mmc_blk_issue_flush
    . mmc_blk_issue_rw_rq(这个是我们要分析的读写数据流程
    1. mmc_blk_prep_packed_list尝试把当前request和队列中的其他request合并,以增强性能。是否可以合并,要依赖于:
    控制器支持packed功能;
    device的MAX_PACKED_WRITES 大于0;
    只对写request进行packed

    2. mmc_blk_rw_rq_prep:正常情况下执行mmc_blk_rw_rq_prep函数,从request构造mmc_request,毕竟下发给host请求,是mmc_request,而不是block层通用的request。
    如果支持packed功能,那么就用pack_list来构造mmc_request

    3. mmc_start_req:mmc_start_req 启动一个非阻塞的request,这个函数会等待前一个request完成,然后启动当前requeset,并立刻返回
    如果mmc_start_req返回的areq不为空,说明完成了上一次的request
    • mmc_start_req
    mmc_start_req 启动一个非阻塞的request,这个函数会等待前一个request完成,然后启动当前requeset,并立刻返回
    如果mmc_start_req返回的areq不为空,说明完成了上一次的request

    1. 首先它会执行到mmc_wait_for_data_req_done函数,等待上一次的命令的完成,如果上一次未完成就会将当前进程加入等待队列休眠,等待被唤醒。

       当上一次完成后会立即返回,并将上一次命令执行的状态返回给mmc_blk_issue_rw_rq。

    2、if (host->areq) {

        err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq);

    host->areq不为空,说明有正在处理的reuqest,函数mmc_wait_for_data_req_done用来等待这个host->areq,有两个条件会唤醒该MMC上下文: is_done_rcv和is_new_req

    3. if (!err && areq)      

       start_err = __mmc_start_data_req(host, areq->mrq);

    进入__mmc_start_data_req(host, areq->mrq);

    (1)首先会将函数指针mmc_wait_data_done赋给mrq->done.

    mmc_wait_data_done会设置context_info->is_done_rcv=true,这正好是唤醒mmc_wait_for_data_req_done的条件之一,然后调wake_up_interruptible(&context_info->wait);唤醒之。

    (2)然后会调用mmc_start_request(host, mrq);

     mmc_start_reuqest实际调用host->ops->request方法,进入了平台特定的request函数

    进入特定的平台之后,会进入相应的中断对硬件进行读写的命令的执行,当命令执行完毕后,会进行函数回调调到刚才的mmc_wait_data_done唤醒等待的进程进行下一次命令的执行。

    •  mmc_queue_bounce_post
    如果使用了bounce buffer,那么需要把传输结果从bounce buffer复制会sg buffer。
    所谓bounce buffer是因为某些DMA控制器只能处理连续物理内存,此时需要通过bounce buffer来达到物理内存连续性。
    • 检查mmc_start_req返回的状态
    1. 如果是MMC_BLK_SUCCESS或者MMC_BLK_PARTIAL,需要调用blk_end_request通知block设备层,完成了本次读写request。
    2. 如果是MMC_BLK_CMD_ERR,那么调用mmc_blk_reset复位host。调用mmc_blk_cmd_err尝试blk_end_request,如果发现reuqest未完成,说明本次操作失败,反之成功start_new_req

    TODO

  • 相关阅读:
    centos8 将SSSD配置为使用LDAP并要求TLS身份验证
    Centos8 搭建 kafka2.8 .net5 简单使用kafka
    .net core 3.1 ActionFilter 拦截器 偶然 OnActionExecuting 中HttpContext.Session.Id 为空字符串 的问题
    Springboot根据不同环境加载对应的配置
    VMware Workstation12 安装 Centos8.3
    .net core json配置文件小结
    springboot mybatisplus createtime和updatetime自动填充
    .net core autofac依赖注入简洁版
    .Net Core 使用 redis 存储 session
    .Net Core 接入 RocketMQ
  • 原文地址:https://www.cnblogs.com/smartjourneys/p/6723389.html
Copyright © 2011-2022 走看看