[sd card] sd card块设备(mmc_blk)读写流程学习笔记

零、说明

前面介绍完sd card的协议中的初始化之后,接下来就是如何将sd card实现为一个块设备以及其读写流程的实现。
对应代码在drivers/mmc/card目录下block.c 、queue.c中。

先研究sd card作为一个块设备的读写流程。
在学习sdcard块读写的过程中主要围绕以下几个问题进行学习:

  • 关于sd card 读写地址的问题?
  • 向mmc core发起mmc读写请求的接口?
  • 块设备的整个层次结构?
  • 从上层(IO调度层)传下来的是什么数据?
  • 从struct request到struct mmc_async_req的处理过程?
  • mmc_blk_issue_rq对request的整体操作、下发流程?

一、关于sd card 读写地址的问题

1、sd card 读写地址

也就是CMD17\CMD18\CMD24\CMD25的参数问题。

根据SD 3.0协议:

 If  partial block access is enabled in Standard Capacity Card (i.e. the CSD parameter READ_BL_PARTIAL 
 equals 1), the block length can be any number between 1 and 512 Bytes. The start address can be any 
 byte address in the valid address range of the card. Every block, however, shall be contained in a single 
 physical card sector.  If partial block access is disabled, only 512-Byte data length is supported. 

// partial block access 分块访问

 The High Capacity SD Memory Card only supports  512-byte block length. The start address shall be 
    aligned to the block boundary. 

The unit of "data address" in argument is byte for Standard Capacity SD Memory Card and block (512 bytes) for 
          High Capacity SD Memory Card. 

对于CMD17\CMD18\CMD24\CMD25的参数表示要读写的地址,对于SDSC card来说,数据地址的单位是字节。而对于SDHC和SDXC来说,数据地址的单位是512字节(以块为单位)。
(很好理解啊,SDSC 只支持到2GB,32bit的地址空间完全够了)。

2、代码上

根据CSD寄存器中CSD_STRUCTURE的版本来区分card的容量类型,也就是能判断其读写时的地址的单位了。

avatar

static int mmc_decode_csd(struct mmc_card *card)
{
    switch (csd_struct) {
    case 1:    // 说明为SDHC 或者SDXC card
        mmc_card_set_blockaddr(card);    // 设置mmc的MMC_STATE_BLOCKADDR属性,也就是以块作为地址。
        // CMD17 CMD18 CMD24 CMD25的参数也就是块地址。

static void mmc_blk_rw_rq_prep
在设置读写地址的时候
    brq->cmd.arg = blk_rq_pos(req);
    if (!mmc_card_blockaddr(card))    // 检测MMC_STATE_BLOCKADDR属性
        brq->cmd.arg <<= 9;    // 没有设置的话,则是以byte作为单位的,IO调度器传下来的是以扇区为单位,也就是以512byte为单位

二、向mmc core发起mmc读写请求的接口

使用的是mmc_start_req接口。

参考《[mmc subsystem] mmc core(第六章)——mmc core主模块

不阻塞等待该命令的处理流程:
(注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞)
mmc_start_req
——》mmc_wait_for_data_req_done   // 阻塞等待上一次的请求处理
——》__mmc_start_data_req   // 发起异步请求
————》mrq->done = mmc_wait_data_done
————》mmc_start_request   // 实际发起请求的操作
——》返回

机制说明如下:mmc_start_req会先判断上一次的asycn_req是否处理完成,如果没有处理完成,则会等待其处理完成。
如果处理完成了,为当前要处理的asycn_req发起请求,但是并不会等待,而是直接返回。返回上一次正常处理的异步请求
注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞。这样,可以利用等待的一部分时间来做其他操作。
为了方便理解这个函数,需要看一下其函数注释。

struct mmc_async_req *mmc_start_req(struct mmc_host *host,
                    struct mmc_async_req *areq, int *error)
 *    mmc_start_req - start a non-blocking request    // 该函数用来发起一个不阻塞的请求
 *    @host: MMC host to start command    // 要发起对应请求的host
 *    @areq: async request to start    // 要发起的异步请求
 *    @error: out parameter returns 0 for success, otherwise non zero    // 返回值,返回0表示成功,返回非零表示失败

所以上层主要要实现对应的mmc_async_req并调用mmc_start_req进行下发。
然后对返回值mmc_async_req进行处理。
对于mmc_async_req来说,主要实现其mmc_request。struct mmc_request是mmc core向host controller发起命令请求的处理单位。

struct mmc_async_req {
    /* active mmc request */
    struct mmc_request    *mrq;
    unsigned int cmd_flags; /* copied from struct request */

    /*
     * Check error status of completed mmc request.
     * Returns 0 if success otherwise non zero.
     */
    int (*err_check) (struct mmc_card *, struct mmc_async_req *);
    /* Reinserts request back to the block layer */
    void (*reinsert_req) (struct mmc_async_req *);
    /* update what part of request is not done (packed_fail_idx) */
    int (*update_interrupted_req) (struct mmc_card *,
            struct mmc_async_req *);
};

struct mmc_request {
    struct mmc_command    *sbc;        /* SET_BLOCK_COUNT for multiblock */    // 设置块数量的命令,怎么用的后续再补充
    struct mmc_command    *cmd;    // 要传输的命令
    struct mmc_data        *data;    // 要传输的数据
    struct mmc_command    *stop;    // 结束命令,怎么用的后续再补充

    struct completion    completion; // 完成量
    void            (*done)(struct mmc_request *);/* completion function */ // 传输结束后的回调函数
    struct mmc_host        *host;    // 所属host
};

三、块设备的整个层次结构?

(下图摘自http://blog.csdn.net/jianchi88/article/details/7212370

avatar

request: 描述向内核请求一个列表准备做队列处理。
request_queue: 描述内核申请request资源建立请求链表并填写BIO形成队列。

通用块设备层——》I/O调度层——》块设备驱动

对于sd/emmc的块设备层而言,又分成如下层次
mmc_blk层——》mmc_core层——》mmc_host层

而这里要学习的drivers/mmc/card/block.c则是属于mmc_blk层。

四、从上层(IO调度层)传下来的是什么数据?

块设备驱动(mmc_blk层)是通过request_queue和I/O调度层关联在一起的。
块设备驱动(mmc_blk层)需要从request_queue提取request进行处理。将request理解为IO请求,也就是IO调度层发起的请求。

所以mmc_blk需要创建对应的request_queue!!!一个mmc_blk对应一个request_queue.!!!(mmc_blk的驱动设计的核心)
也就是mmc_blk层需要调用blk_fetch_request从request_queue提取request进行处理。

关联gendisk和requeset_queue的地方
mq->queue = blk_init_queue(mmc_request_fn, lock);
md->disk->queue = md->queue.queue; // 相当于md->disk->queue = mq->queue

提取request的代码如下:

mq->queue = blk_init_queue(mmc_request_fn, lock);
mmc_request_fn是和mmc_blk的request_queue关联在一起的,当I/O调度层往request_queue放了一个数据,那么mmc_request_fn就会被执行

static void mmc_request_fn(struct request_queue *q)
{
   wake_up_process(mq->thread);   // 唤醒mmc_queue->thread,也就是mmc_queue_thread

static int mmc_queue_thread(void *d)
{
        // 不断去循环,从request_queue中获取request进行处理
    do {
        struct request *req = NULL;
        struct mmc_queue_req *tmp;
                req = blk_fetch_request(q);       
                mq->mqrq_cur->req = req;        // 关联struct mmc_queue_req和struct request    ,
                                                                    // struct mmc_queue_req是mmc_blk自己定义的一个queue request请求结构体
                                                                    // struct request    则是IO调度层定义的IO请求结构体

        if (req || mq->mqrq_prev->req) {        // 直到request_queue里面已经没有request并且前面的request已经处理完成
            mq->issue_fn(mq, req);   
                       // 调用mmc_queue的issue_fn对request进行处理,也就是mmc_blk_issue_rq
                        // 在mmc_blk_alloc_req中设置了:md->queue.issue_fn = mmc_blk_issue_rq;
        } else {
            schedule();
        }
    } while (1);
}

最终调用了mmc_blk_issue_rq(struct mmc_queue, struct request)来对request进行下发。后面说明。

struct request的数据结构部分内容如下:

struct request {
    struct list_head queuelist;      // 用于连接到request_queue的queue_head链表中
    struct request_queue *q;   // 所属request_queue

    /* the following two fields are internal, NEVER access directly */
    unsigned int __data_len;    /* total data len */    // 总长度
    sector_t __sector;        /* sector cursor */``// 起始扇区地址

    struct bio *bio;
    struct bio *biotail;

    struct hlist_node hash;    /* merge hash */
    struct gendisk *rq_disk;
    struct request *next_rq;
};

五、从struct request到struct mmc_async_req的处理过程?

1、说明:

通过上述“向mmc core发起mmc读写请求的接口”可以知道mmc_blk是通过struct mmc_async_req来向mmc core下发mmc请求的。
通过上述“从上层(IO调度层)传下来的是什么数据?”可以知道从IO调度层传递下来的的是struct request这个IO请求。
因此,mmc_blk肯定需要做struct request到struct mmc_async_req的转化的。

2、转化中会使用到的一些结构的关系如下:

mmc_blk用mmc_queue表示当前设备的mmc请求队列结构体。然后用mmc_queue_req来表示一个mmc queue request请求。
mmc_blk将request封装到mmc_queue_req中。(mmc_queue_req->request)

mmc_queue->struct mmc_queue_req *mqrq_cur则表示mmc_queue当前正在下发的mmc queue request请求。
mmc_queue->mmc_queue_req mqrq_cur->request,则表示当前IO调度层下发下来的请求。
mmc_queue->mmc_queue_req mqrq_cur->struct mmc_async_req mmc_active,当前要下发给mmc core的异步请求

在mmc_queue_thread获取到IO请求的时候,会设置mq->mqrq_cur->req = req,这就关联了struct mmc_queue_req和struct request.

mmc_queue_req 中重要的三个成员:
struct mmc_queue_req {
    struct request        *req;
    struct mmc_blk_request    brq;
    struct mmc_async_req    mmc_active;
};

struct mmc_blk_request {   // 块请求结构体
    struct mmc_request    mrq;
    struct mmc_command    sbc;
    struct mmc_command    cmd;
    struct mmc_command    stop;
    struct mmc_data        data;
};

(1)先将struct request *req转化成mmc块请求struct mmc_blk_request
(2)再将请求struct mmc_blk_request的 mrq(真正的实现)设置到struct mmc_async_req mmc_active;中

所以mmc_queue->mqrq_cur->mmc_active就表示此时要下发的异步请求。
也就是调用areq = mmc_start_req(card->host, areq, (int *) &status);就可以将其发送

3、疑问:为什么要有mmc_blk_request这个中转呢。

因为mmc_async_req——》struct mmc_request中的mmc_command都是指针格式的,

struct mmc_request {
    struct mmc_command    *sbc;        /* SET_BLOCK_COUNT for multiblock */
    struct mmc_command    *cmd;
    struct mmc_data        *data;
    struct mmc_command    *stop;

    struct completion    completion;
    void            (*done)(struct mmc_request *);/* completion function */
    struct mmc_host        *host;
};

也就是说这些mmc_command和mmc_data都是要在外部实现,因此,mmc_blk就将这些内容都封装到mmc_blk_request 中作为一个完整的块请求结构体,方便管理。

4、转化操作如下:

在mmc_blk_rw_rq_prep中进行转化:

mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
                   struct mmc_card *card,
                   int disable_multi,
                   struct mmc_queue *mq)
{
    u32 readcmd, writecmd;
    struct mmc_blk_request *brq = &mqrq->brq;      // 每一个request对应一个mmc_blk_request,也对应一个mmc_async_req(不考虑合并request的情况下)
    struct request *req = mqrq->req;
    struct mmc_blk_data *md = mq->data;
    bool do_data_tag;

    memset(brq, 0, sizeof(struct mmc_blk_request));        // 为mqrq->brq分配空间


    brq->mrq.cmd = &brq->cmd;    // 关联mmc_request和mmc_blk_request的cmd
    brq->mrq.data = &brq->data;    // 关联mmc_request和mmc_blk_request的data

// 以下根据request中提取的信息来设置mmc_blk_request中的cmd、data、stop
// 因为mmc_blk_request中的mmc_request的指针就是指向cmd、data、stop的,所以变相的在实现mmc_blk_request中的mmc_request
    brq->cmd.arg = blk_rq_pos(req);
    if (!mmc_card_blockaddr(card))
        brq->cmd.arg <<= 9;
    brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
    brq->data.blksz = 512;
    brq->stop.opcode = MMC_STOP_TRANSMISSION;
    brq->stop.arg = 0;
    brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
    brq->data.blocks = blk_rq_sectors(req);
    brq->data.fault_injected = false;
    if (brq->data.blocks > 1 || do_rel_wr) {
        if (!mmc_host_is_spi(card->host) ||
            rq_data_dir(req) == READ)
            brq->mrq.stop = &brq->stop;        // 对于读操作,需要设置STOP命令
        readcmd = MMC_READ_MULTIPLE_BLOCK;            // CMD18,多块读
        writecmd = MMC_WRITE_MULTIPLE_BLOCK;        // CMD25,多块写
    } else {
        brq->mrq.stop = NULL;
        readcmd = MMC_READ_SINGLE_BLOCK;    // CMD17,单块读
        writecmd = MMC_WRITE_BLOCK;        // CMD24,单块写
    }
    if (rq_data_dir(req) == READ) {
        brq->cmd.opcode = readcmd;
        brq->data.flags |= MMC_DATA_READ;
    } else {
        brq->cmd.opcode = writecmd;
        brq->data.flags |= MMC_DATA_WRITE;
    }
    mmc_set_data_timeout(&brq->data, card);

    brq->data.sg = mqrq->sg;
    brq->data.sg_len = mmc_queue_map_sg(mq, mqrq);

// 设置mmc_queue->mqrq_cur->mmc_active
    mqrq->mmc_active.mrq = &brq->mrq;    // 将mmc_active指向mmc_blk_request已经填充好的mmc_request
        // 到这里完成了request到mmc_async_req的转化了


// 后续继续填充mmc_queue->mqrq_cur->mmc_active的一些成员、方法
    mqrq->mmc_active.cmd_flags = req->cmd_flags;
    if (mq->err_check_fn)
        mqrq->mmc_active.err_check = mq->err_check_fn;
    else
        mqrq->mmc_active.err_check = mmc_blk_err_check;
    mqrq->mmc_active.reinsert_req = mmc_blk_reinsert_req;
    mqrq->mmc_active.update_interrupted_req =
        mmc_blk_update_interrupted_req;

    mmc_queue_bounce_pre(mqrq);
}

六、mmc_blk_issue_rq对request的整体操作、下发流程?

通过“从上层传下来的是什么数据?”可以知道mmc_queue_thread最后是调用mmc_blk_issue_rq来下发request

1、理解几个函数

(1)mmc_blk_issue_rq:mmc_blk下发请求的函数。
注意,请求有很多种,可以是“舍弃某些扇区”的请求,可以是“flush”请求,也可以是正常的读写块请求。
对于读写块请求,mmc_blk_issue_rq会调用来进行处理。
(2)mmc_blk_issue_rw_rq:mmc_blk下发读写请求的函数。
(3)mmc_blk_rw_rq_prep:mmc_blk对于读写请求的准备处理
前面说过了,主要是实现strust request到struct mmc_async_req的转换。

2、下发流程代码核心框架

static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
{
    if( 是否是读写请求 )
    {
        调用mmc_blk_issue_rw_rq(mq, req);下发读写请求
    }
}

static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
{
    调用mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq);  对strust request到struct mmc_async_req的转换,
    对应的struct mmc_async_req就是mmc_queue->mmc_queue_req->mmc_async_req,也就是mq_rq->mmc_active
    看上面的“从struct request到struct mmc_async_req的处理”

   然后调用areq = mmc_start_req(card->host, areq, (int *) &status);将异步请求下发到mmc core中,剩下发送工作就是由mmc core、mmc host来实现。

   根据mmc_start_req的机制,这里得到的areq是刚被处理完成的异步请求mmc_async_req(注意,而不是本次的,具体可以看“向下发起异步请求的接口”)
    后续就是根据status来判断areq(刚被处理完成的异步请求mmc_async_req)是否被正常处理完成以及对应的操作。

猜你喜欢

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