转自:http://blog.csdn.net/junzhang1122/article/details/50175677
总论
1.1概念
1.2分类
1.3速度
.4子系统代码结构
Linux的源码里/司机/ MMC下有三个文件夹,分别存放了SD块设备,核心层和SD主控制器的相关代码,可以通过的Kconfig和Makefile文件获取更多信息。
主控制器
SD卡的控制器芯片,可以看成CPU的代言人,它为CPU分担了完成与SD卡数据通信的任务。
2.1数据结构
以PXA芯片的SD控制器驱动为例:
代码位于:\核心\ DRIVERS \ MMC \主机\ pxamci.c
2.1.1 struct mmc_host
结构体mmc_host定义于/include/linux/mmc/host.c,可以认为是Linux的为SD卡控制器专门准备的一个类,该类里面的成员是所有SD卡控制器都需要的,放之四海而皆准的数据结构,而在PXA芯片控制器的驱动程序pxamci.c中,则为该类具体化了一个对象struct mmc_host * mmc,此mmc指针即指代着该PXA芯片SD卡控制器的一个具体化对象。
mmc_card结构体内的数据结构主要存放SD卡的信息,其中RCA,CID,CSD,SCR为SD卡内部的32位寄存器。
2.1.2 struct mmc_request
结构体mmc_request定义于/include/linux/mmc/core.h,它主要存放两大数据结构的指针,分别是CMD和数据,顾名思意,一个为指令,一个为数据,也就是说,mmc_request结构体存储了进行主控制器与sd卡间通信所需要的指令和数据,struct mmc_request,struct mmc_command * cmd,struct mmc_data * data三者之间的关系如下所示,
说到结构体mmc_command和mmc_data,就必须说说SD卡的协议了。
1)物理结构
SD卡有9个销脚(微SD为8个,少一个接地销脚),如图所示,
SD的数据传输方式有两种,普通SD模式和SPI模式,以SD模式为例,9个销脚分别是VDD,VSS,CLK,以及我们需要关注的一根指令线CMD,4根数据线DAT0 〜DAT3。
2)传输模式
首先由主机向SD卡发送命令的命令,等待SD卡的回复响应,如果成功收到回复,则进行数据传输。其中,指令线和数据线上传输的指令和数据都要遵循相应的协议格式。
3)指令格式
一条指令命令共48位,其中指令索引指代这些条具具体的指令名称,参数为该指令的参数。
一条回复响应根据不同的指令有几种不同类型。
数据传输按数据线可分为一线传输和四线传输,按数据大小可分为字节传输和块传输(512字节)。
2.2驱动程序
系统初始化时间扫描平台总线上是否有名为该SD主控制器名字“pxa2xx-mci”的设备,如果有,驱动程序将主控制器挂载到平台总线上,并注册该驱动程序。
其中,除去为探头的反操作,暂停和恢复涉及电源管理的内容,本文重点讨论的探针。
SD主控制器驱动程序的初始化函数探针(struct platform_device * pdev),概要地讲,主要完成五大任务,
2.2.1注册设备
对于设备的注册,所有设备驱动的相关代码都类似
这两个函数都由/司机/ MMC /核心核心层下的host.c负责具体实现,
1)mmc_alloc_host
为主设备控制器建立数据结构,建立kobject,并初始化等待队列,工作队列,以及一些控制器的配置。其中,INIT_DELAYED_WORK(&host-> detect,mmc_rescan);将探测SD卡的函数mmc_rescan与工作队列 - >检测关联,mmc_rescan是整个SD子系统的核心函数,本文第三部分协议层将对它作重点讨论。
2)mmc_add_host
完成的kobject的注册,并调用mmc_rescan,目的在于在系统初始化的时候就扫描SD总线查看是否存在SD卡。注意到这里的工作队列的延时时间延迟为0,因为系统启动的时候不考虑插拔SD卡,关于这个延迟将在下文讨论。
系统初始化的时候,已经为该SD主控制器的名称,资源等赋上了初值,具体内容如下,
需要注意的是,platform_device数据结构里的名称,id,资源等是所有设备都用的到数据类型,那么设备自身独有的特性如何表现出来呢?事实上,结构体设备专门准备了一个成员的platform_data,就是为了挂载设备的一些特有的数据。(注意与driver_data相区别)
看看SD主控制器为什么会有这些特有数据,
2.2.3设备驱动的功能函数
至此,我们必须去接触SD主控制器的芯片手册了。
首先,SD主控制器由一系列32位寄存器组成。通过软件的方式,即对寄存器赋值,来控制SD主控制器,进而扮演SD主控制器的角色与SD卡取得通信。
当调用(* request),即host-> ops-> request(host,mrq),即上文中的pxamci_request()后,控制器与SD卡之间开始进行一次指令或数据传输,通信完毕后,主控芯片将产生一个内部中断,以告知此次指令或数据传输完毕。这个中断的具体值将保存在一个名为MMC_I_REG的中断寄存器中以供读取,中断寄存器MMC_I_REG中相关描述如下,
Linux的在内核源码的驱动程序/ MMC /核心文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile文件如下,
可见,核心文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的服务sd.c,sd_ops.c等等。
其中,最为核心的一个函数便是之前提到的位于core.c的mmc_rescan,概括来讲,主要完成两项任务,即
3.1插入SD卡
插入SD卡,主控制器产生中断,进入中断处理函数,处理工作队列,执行mmc_rescan。
插入SD卡,mmc_rescan扫描SD总线上是否存在SD卡,具体的实现方法就是通过向SD卡上电,看是否能成功,以普通SD卡为例,为普通SD卡上电的函数mmc_send_app_op_cond(主机, 0,&ocr);如果上电成功,则返回0,即执行if()里的mmc_attach_sd()进行总线与SD卡的绑定如果上电失败,则返回非0值,跳过if()尝试其他上电的方法。那么,上电方法究竟有何不同呢?具体看看mmc_send_app_op_cond()的实现过程,
这里的指令SD_APP_OP_COND只有SD2.0的协议支持,也就是说,只有普通SD卡支持,所以也只有普通SD卡能够成功上电。
如果上电成功,就开始进行总线与SD卡的绑定,通过mmc_attach_sd(),绑定过程可分为四步,
3.1.1注册总线上的操作函数
3.1.2设置时钟和总线
3.1.3启动SD卡
1)SD卡的启动过程
根据SD2.0协议,SD卡的状态可分为两种模式:卡识别模式(卡识别模式)和数据传输模式(数据传输模式)。这里,我们关注启动SD卡的卡识别模式。
结合代码,
mmc_go_idle(主机); CMD0
空闲状态
mmc_send_if_cond(host,ocr); CMD8
mmc_send_app_op_cond(host,ocr,NULL); ACMD41
准备状态
mmc_all_send_cid(host,cid); CMD2
识别状态
mmc_send_relative_addr(host,&card-> rca); CMD3
待机状态
2)寄存器CID,CSD,SCR,RCA
- >发送指令并得到寄存器的值
当主控制器向SD卡发送cmd指令,比如mmc_send_cid(card,card-> raw_cid),请求得到SD卡CID寄存器的值,当主控制器发送cmd完成后,芯片产生一个内部中断,处理结束cmd的中断函数,之后得到来自SD卡的回应,即CID寄存器的值,存放于host-> cmd-> resp [i]中。关于内部中断处理,参见上文的中断一节里的mmc_wait_for_cmd()。
mmc_send_cid(card,card-> raw_cid);这个函数发送了接收CSD寄存器的请求,并且得到了来自SD卡的CSD寄存器的值。
- >解析寄存器的值
为什么要解析?先来看看寄存器CID在SD卡协议里的定义,它是一个128位的寄存器,存放了关于这块SD卡的基本信息,就像自己的身份证。通过mmc_send_cid()将这个寄存器的数值赋给了card-> raw_cid(定义u32 raw_cid [4];),为了方便得到具体某一个信息,协议层为我们解析了寄存器里的域,并给卡 - > cid,比如厂商名称,就可以通过卡 - > cid.manfid直接读取到。
3.1.4注册SD卡设备驱动
上文已经提到,设备驱动程序都会通过alloc_xxx()和add_xxx()两步来注册驱动,其实质是调用/drivers/base/core.c里的device_initialize()和device_add(),device_add()完成建立的kobject,SYS文件,发送UEVENT,等工作。
3.2拔出SD卡
这里的mmc_bus_get /放(),为SD总线加上一个自旋锁,规定同时只能有一个线程在SD总线上操作。
3.2.1 bus_ops-> detect()
mmc_rescan()扫描SD总线,如果发现主机 - > OPS上赋了值,即之前已有SD卡注册过,就执行bus_ops->检测()操作去探测SD总线上是否还存在SD卡,如果不存在了,就执行bus_ops->删除()拔出SD卡。之前已经提到,这个bus_ops->检测()已在mmc_attach_sd()注册完成了。
mmc_send_status()发送得到SD卡状态的请求,如果未能得到状态数据,则执行mmc_sd_remove(主机)拔出SD卡。
SD协议规定,状态寄存器CSR是必须的,这个32位寄存器作为R1的一个域返回给主控制器,
状态寄存器SSR作为扩充功能,具体参考SD2.0协议。
3.2.2 bus_ops-> remove()
拔出SD卡,其实就是注册SD卡驱动的反操作,实质就是执行device_del()和device_put()。
块设备
首先,必须知道为什么要用到块设备。在linux的下,SD卡通过块块的方式(以512字节为最小单位)进行数据传输,它必须遵从块设备架构。在Linux的块设备层,I / Ø调度者通过请求队列机制负责对块数据的处理。
SD卡子系统分为三层,主设备层,协议层和块设备层。块设备驱动位于/drivers/mmc/card/block.c,主要完成两个任务,
插入SD卡,注册驱动成功,那么在开发板的目录的/ dev /块下会出现SD卡的设备节点。
179为主设备号,定义于include / linux / major.h #define MMC_BLOCK_MAJOR 179
179:0代表这块SD卡的设备节点mmcblk0,179:1代表这块SD卡的第一个分区mmcblk0p1,即主分区,如果有第二个分区,那就是179:2,最多可以有7个分区,即179:1〜179:7(定义于block.c alloc_disk(1 << 3);)。不过,SD卡一般只有一个分区。如果有第二块SD卡插入,将会建立设备节点mmcblk1 (179:8)和mmcblk1p1(179:9)。
下面通过对块设备驱动block.c的分析,看看SD卡是如何在块设备层建立节点和传输数据的。
4.1数据结构
每个驱动都会有一个数据结构。幸运的是,我们SD卡块设备驱动的数据结构相对简单,在mmc_blk_data里,主要有两个成员,struct gendisk * disk和struct mmc_queue队列。
1)struct gendisk是general disk的缩写,代表个通用的块设备,其中包括块设备的主分区结构struct hd_struct part0,块设备的行为函数struct block_device_operations * fops,以及请求队列struct request_queue * queue等。
2)struct request_queue存放所有I / O调度的算法。
3)struct request请求是I / O调度者调度的对象,其中的结构struct bio是整个请求队列的核心,具体内容请参见LDD3。
4.2块设备驱动
首先浏览一下源码,
4.2.1设备驱动的初始化函数
仍然可以将驱动程序的初始化mmc_blk_probe(struct mmc_card * card)归纳为以下内容,
1)初始化mmc_blk_data
完成初始化后,通过mmc_set_drvdata(card,md);将数据挂载到card-> dev.driver_data下。
2)功能函数
与字符驱动类似,通过dev_t的和inode的找到设备。
3)注册驱动
blk_register_region()在Linux的中实现了一种利用哈希表管理设备号的机制。
register_disk()对应alloc_disk(),完成对块设备的注册,其实质是通过register_disk() - > blkdev_get() - > __ blkdev_get() - > rescan_partitions() - > add_partitions()添加分区,建立设备节点。
blk_register_queue()对应blk_init_queue()完成对请求队列的注册,其实质是通过elv_register_queue()注册请求队列的算法。
关于块设备更为具体的代码分析可参看Linux的那些事。
4.2.2请求队列
mmc_init_queue申请并初始化一个请求队列,开启负责处理这个请求队列的守护进程。
1)mmc_request
它是处理SD卡通用的申请请求的回调函数,或者说是SD卡申请请求的算法。当CPU处理不忙状态时,会寻找一个请求,并试图执行它。
这里我们需要关注这个处理该SD卡请求队列的算法是何时申请的,也就是何时会去申请请求,何时会去唤醒内核线程。
用到回调函数Q-> request_fn有三处
blk_fetch_request() - > blk_peek_request() - > __ elv_next_request() - > blk_do_ordered() - > ...-> Q-> request_fn
我们不必深究所谓的电梯算法,只要知道,它是使数据得以高效通信的一种算法,算法自身决定何时去唤醒守护进程处理请求。
2)blk_init_queue()
如果一个块设备希望使用一个标准的请求处理步骤,那就必须使用blk_init_queue()。这个函数注册了q-> request_fn(这里就是mmc_request),并初始化请求队列的数据结构struct request_queue。
其中的RFN就是请求队列的一个算法,即这里的mmc_request。
3)kthead_run()
注意到mmc_init_queue这个函数的最后,创建并运行一个名为mmcqd的线程,顾名思意,mmc queue deamon它是一个SD卡的处理请求队列的守护进程,或者说内核线程,当系统注册SD卡块设备驱动时,就通过mmc_init_queue()开启了这个内核线程。
4)mmc_queue_thread
看看这个内核线程做了些什么,
首先,这个守护进程是一个,而(1)死循环,如果没有特殊要求,即kthread_should_stop()指定要把这个内核线程终止掉,那么它将从系统启动开始一直负责处理SD卡的请求队列。
在循环内部,内核线程首先通过set_current_state(TASK_INTERRUPTIBLE);设置当前线程为可打断的等待线程,进入睡眠状态,等待其他线程唤醒它,这里唤醒它的就是处理SD卡请求的mmc_request,当MQ-> req为空,即当前没有请求正在处理,则通过wake_up_process(mq-> thread);唤醒内核线程,接着该线程尝试从请求队列里得到一个请求req,
- >如果没有请求,则调用schedule()交出cpu的使用权让其自由调度,等到系统空闲时,再次得到cpu控制权,并且执行continue;退出当前循环,重新开始新的循环。
- >如果得到了一个请求,则通过set_current_state(TASK_RUNNING);将内核线程设置为当前正在运行的进程,并调用issue_fn(),即mmc_blk_issue_rq,处理这个请求,实现主控制器与SD卡的数据传输。
5)issue_fn
驱动初始化函数probe()里的mmc_blk_alloc()里注册了这个回调函数,md-> queue.issue_fn = mmc_blk_issue_rq ;
这个函数将REQ里的成员解析成为mmc_blk_request里的指令和数据,即mmc_command和mmc_data,然后通过mmc_wait_for_req()最终实现主控制器与SD卡间的通信。