zoukankan      html  css  js  c++  java
  • (linux)mmccard驱动的读写过程解析

     

     

    mmc io的读写从mmc_queue_thread()的获取queue里面的request开始。

     

    先列出调用栈,看下大概的调用顺序, 下面的内容主要阐述这些函数如何工作。

    host->ops->request() // sdhci_request()

    mmc_start_request()

    mmc_start_req()

    mmc_blk_issue_rw_rq()

    mmc_blk_issue_rq()

    Mmc_queue_thread()

     

    mmc_queue_thread()  struct request *req = NULL; 用来提取req

    req = blk_fetch_request(q); 从块设备队列提取存储的req。存储到这次处理mqrq_cur里面mq->mqrq_cur->req = req; blk_fetch_request()可以多次调用,如果queue里面没有内容,req将返回NULL。

    接下来调用mq->issue_fn()对req进行处理

    处理完毕后把mq->mqrq_prev = mq->mqrq_cur, 然后清空mq->mqrq_cur。

    倘若req || mq->mqrq_prev->req 这次获取的req和上次的req都为NULL的话,线程进入睡眠状态kthread_should_stop() –> schedule();

     

    mmc_blk_issue_rq()

    1. if (req && !mq->mqrq_prev->req) 如果是第一次命令mmc_claim_host(card->host); 需要占住host,激活时钟。
    2. ret = mmc_blk_part_switch(card, md); 选择对应的分区。
    3. 根据req->cmd_flags的命令做不同的事情。REQ_SANITIZE、REQ_DISCARD、REQ_FLUSH
    4. 关注结构体host->context_info

     

    mmc_blk_issue_rw_rq() 开始读写

    1. Req参数变换名称struct request *rqc
    2. 如果req有值,则进入一个关键的函数mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);做一些准备工作。然后areq = &mq->mqrq_cur->mmc_active; 取到异步request结构体 areq (struct mmc_async_req)。
    3. 正式启动areq = mmc_start_req(card->host, areq, (int *) &status);
    4. 命令完成之后,对命令的完成状态做各种判断,是否正确完成,是否出错,是否需要retry。
    5. 有几个部分用于获取执行状态。mq_rq从areq反向抽取得到, brq = &mq_rq->brq;  req  = mq_rq->req; status变量。 通过switch case来判断status返回的是什么状态,决定接下来如何做。可以mmc_blk_reinsert_req()重新把req放回queue里面。可以  blk_end_request (req, 0, brq->data.bytes_xfered); 完成本次传输,说明数据已正确读写。该函数本质是req->end_io(req, error); 有上层request queue的时候注册的回调。一般可能是做unlock buffer或page的动作。 如果是MMC_BLK_CMD_ERR,则mmc_blk_reset()把控制器都reset一遍,要做重新上下电的动作。如果是retry MMC_BLK_RETRY,则循环体重试五次。如果是MMC_BLK_DATA_ERR 也要reset控制器。如果是MMC_BLK_ECC_ERR, 并且发现是多块读,则切换到单块读,如果还是失败,没办法blk_end_request(req, -EIO, 给上层直接EIO的错误。如果是MMC_BLK_NOMEDIUM没设备了,直接退出。
    6. 剩余的都是为了命令出错处理,或者重试,start_new_req()。

     

    mmc_blk_rw_rq_prep()

    1. 光从函数名就可以看出这是一个prepare的函数。注意这里面的几个结构体struct mmc_blk_request  struct mmc_request。 brq = &mqrq->brq; brq->mrq.cmd = &brq->cmd; brq->mrq.data = &brq->data; 将来这个mrq将是承载命令发送的结构。
    2. 整个函数的宗旨就是填充各种结构体,用正确的值,譬如cmd号,是读还是写,是单块还是多块。MMC_READ_MULTIPLE_BLOCK MMC_READ_SINGLE_BLOCK MMC_WRITE_BLOCK MMC_WRITE_MULTIPLE_BLOCK。也包含一些特殊情况需要做的事情,譬如特殊命令。
    3. 另一个该函数重要的工作,是mmc_queue_map_sg。要把request里面包含的数据buffer指针,给map到data.sg结构里面。struct scatterlist       *sg; 结构是分散聚拢DMA的描述,从这点可以看出mmc host的处理是通过dma来完成,sgdma的好处是,它可以处理非连续的多个命令,而不需要cpu干扰。Cpu只需要填充好命令,剩下的事情交个dma处理即可。简单说就是,普通dma可以处理单个命令,sgdma可以在一次dma里面处理一组命令。
    4. mqrq->mmc_active.mrq = &brq->mrq; mqrq->mmc_active.cmd_flags = req->cmd_flags; 之后完成退出。

     

    Mmc_start_req()

    1. 该函数的写法有点饶,首先是看得出来,mmc_start_req()的目的其实是要处理本次areq。 所以一开始对areq做mmc_pre_req(host, areq->mrq, !host->areq); 预准备。
    2. 但接下来是一个if (host->areq) {    err = mmc_wait_for_data_req_done (host,  host->areq->mrq, 一个很明显的等待操作,从函数名就可以得知这是一个同步等待,会放弃处理器等待命令完成。但从上文可以看到,一直还未正在处理命令,也没往host发送命令,此时就开始等待命令完成显然是毫无道理。但有时候代码容易看漏,该等待是针对的host->areq,并不是参数传递的areq。本次等待的是上一次传递未完成的动作。如果上次的传输以及完成,则该等待函数会很快返回。并把成功还是错误的情况反馈给外面的mmc_blk_issue_rw_rq()
    3. 接下来的if (!err && areq) { 才是真正本次的处理如果不是urgent事件的话,start_err = __mmc_start_data_req(host, areq->mrq); 开始。
    4. 完成之后对应的做一个mmc_post_req(host, areq->mrq, -EINVAL); 和之前的mmc_pre_req(host, areq->mrq, !host->areq); 对应起来。试想一下,如果想在命令前或命令后做自定义的事情,则可以考虑在这里添加。
    5. 如果这其中没发生错误,host->areq = areq; 就保存起来了,即current操作变成pre操作。并且这里面不需要在做等待,因为等待的操作将在下次函数在进来时的第2步进行。可以看出设计者为了最大化数据吞吐量,把函数设计成最大限度的流水线处理,压缩所有可能的耗时操作。试想如果不这么做,则每次操作都需要完成准备,等待,准备,等待的循环。函数设计成这样,则把同步等待的时间利用起来,做另一次传输的准备,减少无谓的带宽损失。
    6. 把参数state赋值为函数返回值err,返回上一次的传输结果,host->areq ,为什么?因为本次的传输肯定还未完成,需要等待硬件处理,但上次的host->areq已经完成,可以处理后续事情。这就是为什么mmc_blk_issue_rw_rq()在发起命令后返回需要mq_rq = container_of(areq, struct mmc_queue_req, mmc_active);用这样的方式获得 mmc_queue_req。

     

    mmc_start_request ()

    1. 第一件事情,我们观察传递的参数,是areq->mrq。可以知道mmc最终命令的承载都是用struct mmc_request *mrq 这样的结构完成。
    2. 在调用mmc_start_request()前,mrq->done=mmc_wait_data_done就确定了,是request完成之后的回调函数。
    3. 函数开始就不断的对mrq->cmd和mrq->data结构做判断,mmc_start_request其实是个通用函数,我们知道mmc命令有些是单命令,有些是命令数据合并型,对于有数据传输要求的命令,要对mmc->data结构错误判断。
    4. 如无意外的话,mmc_start_request要交给各个host完成处理了。Mmc驱动是一个通用框架驱动,不同的host对应的命令处理必定有所差别。针对sdhci标准的host mmc驱动。host->ops->request(host, mrq);的执行将交给,sdhci_request()完成。

     

    sdhci_request

    1. 注意函数一进来,host结构体发生变化,已经不再是mmc_host结构,而是各具体的厂商的host,如这里的struct sdhci_host *host; 其实是host = mmc_priv(mmc);这么的得来的。
    2. host->mrq = mrq; 保存起mrq结构。函数有不少对sdhci host寄存器的读写,此时开始真正与硬件设备打交道,即准备把控制信息交托给我们的mmc host控制器。
    3. 之后的sdhci_send_command(host, mrq->cmd); 控制host启动命令。
    4. 最后的mmiowb();是为了保证编译器顺序编译,防止编译器优化打乱执行顺序。

     

    sdhci_send_command()

    1. 该函数还值得推敲,从上文看出,request里面的buffer数据被放在mrq->data->sg里面存好了,仅仅是存在代码结构体里面,和真正的DMA还没建立联系,此时说命令发送出去,必定不够合理。所以DMA的初始化必不可少。
    2. 前面的也主要做出错检查工作,把host->cmd = cmd;命令保存起来。
    3. sdhci_prepare_data(host, cmd); 看这个函数名,准备数据,就知道个大概了。里面的关键函数sdhci_pre_dma_transfer()就是准备DMA,dma_map_sg(),从data->sg里面获取到信息,填充到DMA控制器里面。
    4. 数据都准备好之后,sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); 来个终极的,数据发送,这才是真正的控制host发送命令的操作,到这类,mmc控制器才开始跟sd卡做交互。

     

    命令等待

    1. 前文说道,命令发送之后是在Mmc_start_req()的第二步mmc_wait_for_data_req_done()里面做等待。mmc_wait_for_data_req_done函数大量用到了host->context_info的结构体。context_info->wait是等待queue的标志,__add_wait_queue(q, wait); 再io_schedule()出去。从此mmcqd线程将交换出去,知道有人唤醒wait queue。
    2. 如何能激活等待队列呢?还记得mmc_start_request()的第2步,mmc_wait_data_done回调,wake_up_interruptible(&mrq->host->context_info.wait);wakeup这个 context_info.wait wait queue。说明命令结束之后,会有人调用该回调来唤醒mmcqd线程。
    3. 在哪里调用回调?既然mmc命令是有sdhci host启动发送,必定mrq->done这个回调也要在sdhci host阶段完成。而这个正是由sdhci host的irq中断来完成的。想想也合理,线程启动命令之后,由host控制器完成命令,然后触发中断通知cpu事情完成,中断处理里面启动回调函数,唤醒mmcqd线程。
    4. sdhci_irq就是上步我们说的中断处理函数。根据中断类型的不同,分为sdhci_cmd_irq()处理和sdhci_data_irq()处理。所以可以看出,命令处理中断和数据处理中断是不同的,一条既有命令又有数据的mmc cmd,会至少激活2次中断,1次给命令,1次给数据。
    5. done回调的地方在tasklet_schedule(&host->finish_tasklet);的finish_tasklet里面,中断完成上部处理之后,启动finish_tasklet完成后面的事情。finish_tasklet的定义是sdhci_tasklet_finish。 mmc_request_done() -> mrq->done(mrq);

     

    并不是所有的mmc命令都是读写命令,那其他的命令该如何完成呢,他们与mmc的读写命令有什么差别。我们用mmc的CMD8 SEND_IF_COND作为例子,mmc_send_if_cond()是发送CMD8的函数。

    1. 函数很简单,进来就初始化一个局部变量struct mmc_command cmd。填好命令CMD8,给定返回的RSP参数值,无需初始化cmd->data,因为CMD8没有数据阶段。直接通过mmc_wait_for_cmd() 发送出去。
    2. mmc_wait_for_cmd()里面创建mrq结构变量,之前说过mrq变量的意义, mrq.cmd = cmd; cmd->data = NULL; mmc_wait_for_req(host, &mrq);
    3. __mmc_start_req() 启动 mmc_start_request() 这基本跟读写命令的流程就一致了。
    4. mmc_wait_for_req_done() 等待wait_for_completion_io(&mrq->completion); 看得出来这里面和读写流程的不同,在本次传输启动后,立刻同步等待中断到来。因为单次的CMD8命令并没有其他的循环处理,因此如果不再本次处理等待,将来也没有机会再进入同步等待阶段。
    5. 本次的wait等待是mrq->completion,和读写命令的也有所不同。仔细看__mmc_start_req() mrq->done = mmc_wait_done; 而读写的是mrq->done=mmc_wait_data_done。剩下的事情就是返回处理结果。
    6. 对于又有命令又有数据的单次命令,譬如mmc_send_cxd_data(). mrq.data也需要赋值,我们知道读写命令里面,需要初始化data->sg变量。这里也不例外,data->sg的初始化由sg_init_one(&sg, data_buf, len);完成,看函数名就知道,这是一个初始化单一数据处理的dma。只需要传输一次,大部分是做读取用。
  • 相关阅读:
    hadoop再次集群搭建(3)-如何选择相应的hadoop版本
    48. Rotate Image
    352. Data Stream as Disjoint Interval
    163. Missing Ranges
    228. Summary Ranges
    147. Insertion Sort List
    324. Wiggle Sort II
    215. Kth Largest Element in an Array
    快速排序
    280. Wiggle Sort
  • 原文地址:https://www.cnblogs.com/yanghong-hnu/p/4671343.html
Copyright © 2011-2022 走看看