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

  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驱动的读写过程解析,布布扣,bubuko.com

时间: 2024-10-12 14:35:22

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

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

一.文件的打开 1.1.客户端 HDFS打开一个文件,需要在客户端调用DistributedFileSystem.open(Path f, int bufferSize),其实现为: public FSDataInputStream open(Path f, int bufferSize) throws IOException { return new DFSClient.DFSDataInputStream( dfs.open(getPathName(f), bufferSize, verif

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

一.文件的打开 1.1.客户端 HDFS打开一个文件,需要在客户端调用DistributedFileSystem.open(Path f, int bufferSize),其实现为: public FSDataInputStream open(Path f, int bufferSize) throws IOException { return new DFSClient.DFSDataInputStream( dfs.open(getPathName(f), bufferSize, verif

NPC AI驱动最基本过程

NPC AI驱动最基本过程 NPCmgr中比较重要的是加载NPC和一个NPCAI的一个指针 他利用map那个线程的定时到底做了啥呢 void NPCmgr::npcAITimer() { time_t tb = GetTickCount(); m_spNpcAI->run();//[...]测试 time_t te = GetTickCount() - tb; } void NpcAI::run() { //毫秒级随机数(不能放循环里) srand(GetTickCount()); for(au

浏览器加载渲染网页过程解析 (转)

浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下: 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请求,服务器接受到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到 text/html

android 自定义View过程解析

PS:本篇文章大多数翻译自github上一篇英文文章! 总所周知,安卓UI是基于View(屏幕上的单一节点)和ViewGroup(屏幕上节点的集合),在android中有很多widgets和layouts可以用于创建UI界面,比如最常见的View有Button,TextView等等,而最常见的布局也有RelativeLayout,LinearLayout等. 在一些应用中我们不得不自定义View去满足我们的需求,自定义View可以继承一个View或者已存在的子类去创建我们自己的自定义View,甚

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

本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重要参数,利用alsa音频驱动的API设置好参数,最后打开wav文件,定位到数据区,把音频数据依次写到音频驱动中去,开始播放,当写入完成后,退出写入的循环. 注意:本设计需要alsa的libasound-dev的库,编译链接时需要连接 —lasound. #include<stdio.h>#incl

HDFS追本溯源:租约,读写过程的容错处理及NN的主要数据结构

1.      Lease 的机制: hdfs支持write-once-read-many,也就是说不支持并行写,那么对读写的互斥同步就是靠Lease实现的.Lease说白了就是一个有时间约束的锁.客户端写文件时需要先申请一个Lease,对应到namenode中的LeaseManager,客户端的client name就作为一个lease的holder,即租约持有者.LeaseManager维护了文件的path与lease的对应关系,还有clientname->lease的对应关系.LeaseM

浏览器加载渲染网页过程解析

浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下: 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请求,服务器接受到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到 text/html

f2fs源码分析之文件读写过程

本篇包括三个部分:1)f2fs 文件表示方法: 2)NAT详细介绍:3)f2fs文件读写过程:4) 下面详细阐述f2fs读写的过程. 管理数据位置关键的数据结构是node,node包括三种:inode.直接node.间接node.其中inode记录了文件的基本信息,包括访问权限.文件大小.修改时间等,也有索引的功能:直接node和间接node单纯负责索引.F2fs的inode中有923个直接数据块索引,2个一级索引,2个二级索引,1个三级索引,文件的逻辑表示如下图: inode中有923个索引项