当前位置: 首页 > >

mmc驱动的读写过程解析

发布时间:

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()


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

?


mmc_blk_issue_rw_rq() 开始读写


    Req参数变换名称struct request *rqc如果req有值,则进入一个关键的函数mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);做一些准备工作。然后areq = &mq->mqrq_cur->mmc_active; 取到异步request结构体 areq (struct mmc_async_req)。正式启动areq = mmc_start_req(card->host, areq, (int *) &status);命令完成之后,对命令的完成状态做各种判断,是否正确完成,是否出错,是否需要retry。有几个部分用于获取执行状态。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没设备了,直接退出。剩余的都是为了命令出错处理,或者重试,start_new_req()。

?


mmc_blk_rw_rq_prep()


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

?


Mmc_start_req()


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


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

?


sdhci_request


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

?


sdhci_send_command()


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

?


命令等待


    前文说道,命令发送之后是在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。如何能激活等待队列呢?还记得mmc_start_request()的第2步,mmc_wait_data_done回调,wake_up_interruptible(&mrq->host->context_info.wait);wakeup这个 context_info.wait wait queue。说明命令结束之后,会有人调用该回调来唤醒mmcqd线程。在哪里调用回调?既然mmc命令是有sdhci host启动发送,必定mrq->done这个回调也要在sdhci host阶段完成。而这个正是由sdhci host的irq中断来完成的。想想也合理,线程启动命令之后,由host控制器完成命令,然后触发中断通知cpu事情完成,中断处理里面启动回调函数,唤醒mmcqd线程。sdhci_irq就是上步我们说的中断处理函数。根据中断类型的不同,分为sdhci_cmd_irq()处理和sdhci_data_irq()处理。所以可以看出,命令处理中断和数据处理中断是不同的,一条既有命令又有数据的mmc cmd,会至少激活2次中断,1次给命令,1次给数据。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的函数。


    函数很简单,进来就初始化一个局部变量struct mmc_command cmd。填好命令CMD8,给定返回的RSP参数值,无需初始化cmd->data,因为CMD8没有数据阶段。直接通过mmc_wait_for_cmd() 发送出去。mmc_wait_for_cmd()里面创建mrq结构变量,之前说过mrq变量的意义, mrq.cmd = cmd; cmd->data = NULL; mmc_wait_for_req(host, &mrq);__mmc_start_req() 启动 mmc_start_request() 这基本跟读写命令的流程就一致了。mmc_wait_for_req_done() 等待wait_for_completion_io(&mrq->completion); 看得出来这里面和读写流程的不同,在本次传输启动后,立刻同步等待中断到来。因为单次的CMD8命令并没有其他的循环处理,因此如果不再本次处理等待,将来也没有机会再进入同步等待阶段。本次的wait等待是mrq->completion,和读写命令的也有所不同。仔细看__mmc_start_req() mrq->done = mmc_wait_done; 而读写的是mrq->done=mmc_wait_data_done。剩下的事情就是返回处理结果。对于又有命令又有数据的单次命令,譬如mmc_send_cxd_data(). mrq.data也需要赋值,我们知道读写命令里面,需要初始化data->sg变量。这里也不例外,data->sg的初始化由sg_init_one(&sg, data_buf, len);完成,看函数名就知道,这是一个初始化单一数据处理的dma。只需要传输一次,大部分是做读取用。



友情链接: