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。只需要传输一次,大部分是做读取用。

mmc驱动的读写过程解析的更多相关文章

  1. (linux)mmccard驱动的读写过程解析

      mmc io的读写从mmc_queue_thread()的获取queue里面的request开始. 先列出调用栈,看下大概的调用顺序, 下面的内容主要阐述这些函数如何工作. host->op ...

  2. Hadoop学习总结之二:HDFS读写过程解析

    一.文件的打开 1.1.客户端 HDFS打开一个文件,需要在客户端调用DistributedFileSystem.open(Path f, int bufferSize),其实现为: public F ...

  3. Hadoop源码分析(1):HDFS读写过程解析

    一.文件的打开 1.1.客户端 HDFS打开一个文件,需要在客户端调用DistributedFileSystem.open(Path f, int bufferSize),其实现为: public F ...

  4. [Hbase]Hbase章2 Hbase读写过程解析

    写数据 Hbase使用memstore和storefile存储对表的更新.数据在更新时首先写入hlog和memstore,memstore中的数据是排序的,当memstore累计到一定的阀值时,就会创 ...

  5. Netty源码解析 -- ChannelPipeline机制与读写过程

    本文继续阅读Netty源码,解析ChannelPipeline事件传播原理,以及Netty读写过程. 源码分析基于Netty 4.1 ChannelPipeline Netty中的ChannelPip ...

  6. SpringBoot的自动配置原理过程解析

    SpringBoot的最大好处就是实现了大部分的自动配置,使得开发者可以更多的关注于业务开发,避免繁琐的业务开发,但是SpringBoot如此好用的 自动注解过程着实让人忍不住的去了解一番,因为本文的 ...

  7. 用户空间与内核驱动的交互过程 — ioctl

    在Linux内核模块的开发过程中,经常涉及到运行在用户空间上的应用程序与内核模块进行交互,ioctl系统调用是常用的一种方式.本文并不涉及vlan的具体原理,仅通过vconfig与vlan内核模块进行 ...

  8. 老调重弹:JDBC系列 之 <驱动载入原理全面解析>

    前言 近期在研究Mybatis框架,因为该框架基于JDBC.想要非常好地理解和学习Mybatis,必需要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来.好好总结一番,作为自己的笔记,也是给 ...

  9. 曹工说Redis源码(5)-- redis server 启动过程解析,以及EventLoop每次处理事件前的前置工作解析(下)

    曹工说Redis源码(5)-- redis server 启动过程解析,eventLoop处理事件前的准备工作(下) 文章导航 Redis源码系列的初衷,是帮助我们更好地理解Redis,更懂Redis ...

随机推荐

  1. 「国庆训练&知识学习」图的最大独立集与拓展(Land of Farms,HDU-5556)

    题意 一个\(N*M\)的矩阵,其中"."代表空地,"0-9"代表古代建筑,我们如果选择了一个编号的古代建筑想要建立,那么对应就要将全部该编号的建筑建立起来,如 ...

  2. RSA加密通信小结(二)-新版本APP与后台通信交互内容修改方案

    注1:本次修改分为两步,首先是内容相关的修改,待其完成之后,再进行加密通信项(粗体字备注)修改. 1.新的提交后台的格式包括:data,token(预留字段,暂时后台不校验),userId(已有的不删 ...

  3. uvaoj 10474 - Where is the Marble?(sort+lower_bound)

    https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  4. AirtestIDE实践一:梦幻西游手游师门任务自动化

    Airtest Project是网易自研的游戏自动化项目.Airtest IDE是这个项目的一个IDE,就像Eclipse.Pycharm一样,是一个集成开发工具.Airtest框架是一个基于Open ...

  5. pymsql报错:UnicodeEncodeError: 'latin-1' codec can't encode characters End,OK!!

    UnicodeEncodeError: 'latin-1' codec can't encode characters的做法基本一致,后来发现是因为使用的是mysqldb,照着网上的方法修改配置应该可 ...

  6. java学习笔记-8.对象的容纳

    1.Iterator(迭代器)和Enumeration(枚举类),都是用来遍历集合的,他们都是接口.区别是Enumeration只能读取集合的数据,而Iterator可以对数据进行删除,Iterato ...

  7. HDU 1403 Longest Common Substring(后缀自动机——附讲解 or 后缀数组)

    Description Given two strings, you have to tell the length of the Longest Common Substring of them. ...

  8. Java容器之Collections

    Collections 类来源于 java.util.Collections,从 java.lang.object继承. 此类完全由在 collection 上进行操作或返回 collection 的 ...

  9. lintcode-17-子集

    子集 给定一个含不同整数的集合,返回其所有的子集 注意事项 子集中的元素排列必须是非降序的,解集必须不包含重复的子集 样例 如果 S = [1,2,3],有如下的解: [ [3], [1], [2], ...

  10. JS高级 1

    关于string,number是大写,那么就是构造函数,变量不可能为null值,除非手动设置,要解除对象的引用的时候手动去除. in关键字操作数组的时候操作的是索引值,不是里面的内容,.在操作对象的时 ...