mmc子系统
Linux kernel把mmc、sd以及sdio三者的驱动代码整合在一起,俗称mmc子系统。源码位于drivers/mmc下。其下有三个子目录,分别是:card、core、host,其中,card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;core抽象了mmc、sd、sdio三者的通用操作;host则是各类平台上的host驱动代码,包括如TI Omap的omap_hsmmc,三星的s3cmci等。具体的SOC,会根据实际情况再次封装(比如rockchip采用新思designwave的IP),就重新封装为struct dw_mci。DesignWare是SoC/ASIC设计者最钟爱的设计IP库和验证IP库。它包括一个独立于工艺的、经验证的、可综合的虚拟微架构的元件集合,包括逻辑、算术、存储和专用元件系列,超过140个模块。
一、mmc子系统涉及总线(SD为例)
1.1 整体框架
①Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。设备树指定compatible属性与源码匹配后调用对应的probe函数来初始化对应平台的host硬件。
②Card驱动相应的driver和device挂载在mmc自己创建的虚拟总线mmc_bus_type下。该总线的结构体定义在driver/mmc/core/bus.c;因为是统一的协议,mmc_driver在内核已经写好并已注册:
1 | driver/mmc/card/block.c |
③SD卡中extended CSD寄存器中存储分区信息,上电检测到了SD卡,或者上电之后的热拔插操作,触发mmc_rescan查找检测到SD卡插入后,会通过读取该寄存器的值,获取mmc card的分区信息等,然后注册对应的mmc_card device,因mmc driver仅有一个,因此针对mmc bus而言其match函数直接返回1,表示匹配成功(因为是统一的协议),而不需要像spi/i2c总线的match接口那样对设备和驱动进行匹配检测;随后调用mmc_driver的probe函数(拔出卡则反之操作)。
④mmc_driver的probe函数针对该mmc card的每一个分区,均执行如下操作:
- 为每一个分区,创建对应的通用磁盘设备gendisk;
- 设置gendisk的fops为mmc_bdops;
- 为每一个通用磁盘设备,均创建request_queue及其处理方法;
- 为该通用磁盘设备创建mmc block块i/o请求机制;
- 将该分区的通用磁盘设备注册至块设备i/o,以便应用层可通过块设备访问该mmc card 分区;
1.2 设备-总线-驱动模型
在分析MMC子系统的设备-总线-驱动模型时,可以借助已经分析的i2c驱动模型、spi驱动模型的实现,来学习mmc子系统的驱动模型,通过与i2c驱动模型、spi驱动模型的对比,也可以加深我们对mmc子系统驱动模型的理解(从已知去学习未知,可提供我们的学习效率)。
我们知道针对复杂的设备,则需要借助控制器进行通信,而针对i2c、spi、mmc而言,均需要对应的控制器实现与该类设备的通信。而针对这三类控制器,其模块都进行了抽象。
i2c控制器抽象为i2c adapter、spi控制器抽象为spi master,而针对mmc控制器,则抽象为mmc host;而针对这三种类型设备的抽象,i2c设备抽象为i2c client、spi设备抽象为spi device、mmc device抽象为mmc card。
1.3 三类驱动模型的异同
①均为控制器创建了对应的class,用于将对应的控制器设备链接至对应的class上
②针对i2c adapter、spi master这两类而言,其驱动模型中均创建了对应的链表,将所有注册的控制器设备链接在一起,而mmc子系统并没有为mmc host创建类似的链表;
③I2c/spi设备不属于热插拔设备,因此在具体系统的板级文件或者设备树中,需要针对系统中存在的i2c/spi设备定义注册信息,从而在LINUX系统初始化时完成i2c/spi设备的注册;而针对mmc子系统其属于热插拔的,因此在系统初始化时不需要单独进行设备的注册;
④针对mmc子系统而言,针对mmc card的注册,mmc子系统提供了rescan接口,而该接口作为延迟工作队列的回调函数。针对mmc card而发起rescan扫描的时机如下:
- 在mmc host添加的时候,执行一次mmc card的扫描操作。
- 热拔插过程执行扫描。
这里大概分三个情况来描述热插拔识别流程:
- SOC带卡检测引脚,即mmc控制器部分带专门的中断寄存器位(MCI中断)
- SOC不带卡检测引脚,即用普通中断引脚做检测(即sdmmc_cd引脚)
- 没有专门的寄存器,也没有用cd的gpio中断,采用轮询POLL
二、mmc子系统函数调用流程(SD为例)
2.1 设备树mmc节点
关于SD卡的HOST最初始设备树节点在rk3399.dtsi
1 | sdmmc: dwmmc@fe320000 { |
2.2 Host驱动流程
Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。我们设备树host device与driver匹配后调用对应的probe函数来初始化对应平台的host硬件。
1 | drivers\mmc\host\dw_mmc-rockchip.c |
2.3 热拔插中断流程
假设热拔插SD卡引起了中断:
1 | dw_mci_interrupt() |
放一张图中好看些:
三、mmc_host相关数据结构
3.1 数据结构关联
3.2 mmc_host
3.3 mmc_card
mmc_card结构体变量的定义如下,主要内容如下:
- host完成mmc_card与mmc_host的绑定;
- dev完成将该mmc_card与系统中的设备、总线、驱动的关联(即完成与系统设备驱动总线的关联与绑定操作);
- 卡类型与卡状态记录;
- 卡相关的寄存器信息记录(主要从mmc card中读取),包括cid、csd、scr、ssr等内容;
- Mmc card的分区信息,主要完成块设备的创建(由mmc driver实现);
- 若为sdio设备,则有cccr、cis等(关于sdio的部分,请参考sdio协议相关的文档);
3.4 mmc_ios
该结构体主要定义mmc总线相关的参数,主要包括时钟频率、电源、总线模式、电源状态、总线带宽、支持的信号电压值等等这些参数的设置可通过mmc_host_ops->set_ios实现。
3.5 mmc_host_ops
该数据结构定义了mmc_host的操作方法,主要包括如下:
- 若mmc_host支持enable、disable,则需要实现这两个接口(可参考mmc_controller的用户手册);
- pre_req、request、post_req主要定义了mmc controller访问mmc card的方法,mmc controller驱动至少应完成request接口的定义;
- set_ios接口主要用于设置mmc_controller总线相关的参数,mmc_host需要完成该接口的定义;
- get_ro用于获取mmc card 的读写权限,若mmc controller提供检测mmc card读写权限的功能,则需要提供该接口;
- get_cd为卡是否在位的检测接口(该接口非必须),可查看mmc controller的用户手册,确定是否支持;
- init_card为mmc card初始化接口,主要是对mmc card/mmc controller一些特定的初始化参数,若没有特殊对待项,该接口不需要处理;
- 剩下几个接口大多数驱动也没有实现,这几个接口可参考具体的mmc协议,大多数的mmc host驱动基本上不需要实现;
3.6 mmc_bus_ops
该结构主要是针对mmc card的sleep/awake的操作,因mmc card也需要进行sleep/awake等接口,这些接口类似于设备驱动的电源管理(suspend/resume)相关的接口。该结构体中的成员,不需要我们进行实现,mmc子系统已经完成,mmc子系统根据mmc各协议版本针对sleep/awake的支持情况,实现对应的操作:
若为mmc 4.3及以上的协议,则支持sleep/awake接口;
remove接口实现mmc card的移除操作,主要是解除mmc card与mmc host的绑定,并将mmc card从mmc 总线上移除;
detect/alive主要是卡在位检测以及卡是否移除检测;
针对suspend/resume接口,针对mmc 不同版本的协议,其功能有所不同:
4.1. 若card支持poweroff notify机制,则进入poweroff状态;
4.2. 若card支持sleep状态,则进入sleep状态,针对mmc rev>=1.3的情况;
4.3. card不支持以上两种状态,则向card发送deselect命令;power_restore接口,即为mmc_power_restore接口,该接口目前即调用mmc_init_card,根据mmc 协议,完成mmc card的初始化操作。