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

(linux)mmccard驱动的读写过程解析的更多相关文章

  1. mmc驱动的读写过程解析

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

  2. linux 内核驱动加载过程中 向文件系统中的文件进行读写操作

    utils.h 文件: #ifndef __UTILS_H__ #define __UTILS_H__ void a2f(const char *s, ...); #endif utils.c 文件: ...

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

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

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

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

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

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

  6. 网络驱动移植之解析Linux网络驱动的基本框架

    内核源码:linux-2.6.38.8.tar.bz2 概括而言,编写Linux网络驱动其实只要完成两件事即可,一是分配并初始化网络设备,二是注册网络设备. 1.分配并初始化网络设备 动态分配网络设备 ...

  7. Linux 设备驱动开发 —— platform设备驱动应用实例解析

    前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 ,下面将通过一个实例来深入我们的学习. 一.platform 驱动的工作过程 platfor ...

  8. Linux gadget驱动分析1------驱动加载过程

    为了解决一个问题,简单看了一遍linux gadget驱动的加载流程.做一下记录. 使用的内核为linux 2.6.35 硬件为芯唐NUC950. gadget是在UDC驱动上面的一层,如果要编写ga ...

  9. 基于Linux ALSA音频驱动的wav文件解析及播放程序 2012

    本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重 ...

随机推荐

  1. Spoj-BIPCSMR16 Team Building

    To make competitive programmers of BUBT, authority decide to take regular programming contest. To ma ...

  2. localStorag的一点见解

    dot方法对localStorag方法进行键值操作 设值 localStorage.hello = 'world'; localStorage.zhangsan = 'lisi'; 取值: var v ...

  3. AnyChart图表仪表控件在Flex环境下使用

    AnyChart控件是一款当前流行的数据可视化解决方案,使客户可以创建交互地.生动的图表.实时仪表和地图.同时支持Flash和HTML5显示,控件提供极好的视觉外观和配色方案能够使客户根据不同的需求设 ...

  4. msp430项目编程50

    msp430综合项目---gsm无线采集传输平台系统50 1.电路工作原理 2.代码(显示部分) 3.代码(功能实现) 4.项目总结

  5. 查看mysql库中所有表的大小和记录数

    查看mysql库中所有表的大小和记录数 ,), 'MB') as total_size FROM information_schema.TABLES WHERE TABLE_SCHEMA='datab ...

  6. android自定义Activity窗体大小

    先给大家看图吧: 看,是不是很酷呢,呵呵. 这里我说关键的地方,就是自定义Activity的窗体大小. 这个登录框它不是一个Dialog,而是一个Activity. 如何定义,即把Activity的主 ...

  7. Angular Material & Hello World

    前言 Angular Material(下称Material)的组件样式至少是可以满足一般的个人开发需求(我真是毫无设计天赋),也是Angular官方推荐的组件.我们通过用这个UI库来快速实现自己的i ...

  8. Java常用API(Scanner,Random)匿名对象

    API:即Application programming Interface,应用编程接口. Java中封装了许许多多的API供用户使用,Scanner与Random便是其中之一,API实际就是类,已 ...

  9. 关于PDF的读取与绘制

    本文方法参考了:官方文档.见A function that draw a PDF page的代码部分: void MyDisplayPDFPage (CGContextRef myContext, s ...

  10. 算法 - 求两个自然数的最大公约数(C++)

    //************************************************************************************************** ...