手机项目中描述SD部分的驱动设计架构、设计方法。为上层应用提供底层的函数接口和功能,方便上层对SD应用的正确调用,作为开发和测试人员制定测试规范的参考文档。主要读者适用于手机项目组驱动开发人员,软件代表,项目经理、测试等相关人员,供其他项目组驱动人员参考。
缩略语 |
英文全名 |
中文解释 |
SD |
Secure digital memory card |
安全数据媒体卡 |
eMMC |
embedded MultiMedia Card |
嵌入式多媒体卡 |
SDIO |
Secure Digital Input and Output Card |
安全数字输入输出卡 |
SDCC |
secure digital card controller |
安全数字卡控制器 |
SDHCI |
Secure Digital Host Controller Interface |
安全数字主控制器接口 |
JEDEC |
Joint Electron Device Engineering Council |
电子工程设计发展联合会议 |
JESD79-3E |
JEDEC DDR |
DDR3 协议标准 |
JESD84-B51 |
JEDEC SD |
eMMC5.1 协议标准 |
vold |
Volume Daemon |
存储器守护进程 |
BIO |
block I/O |
块设备IO; |
GPIO |
general purpose input/output |
通用输入输出口 |
DMA |
Direct memory access |
直接内存存取 |
FDE |
Full Disk Encryption |
全盘加密 |
SDHCI |
Secure Digital Host Controller Interface |
安全数字控制器接口 |
QTI |
Qualcomm Technologies, Inc. |
高通技术公司 |
ADMA |
Advanced DMA |
高端直接内存访问 |
2, 精通Linux设备驱动程序开发
3, 高通平台项目SD卡需求规格说明书
4, 80_NV610_64_MSM8952_MSM8956_MSM8976_Storage_Overview.pdf
5, 80-NL199-1_SDHCIARCHITECTURE AND DEBUGGING.pdf
6, memorycontroller 控制器的配置;
网页资料:
博客文档参考:
块设备:
基于块设备驱动;
Linux设备驱动--块设备(一)之概念和框架
http://blog.csdn.net/jianchi88/article/details/7212370
Linux设备驱动--块设备(二)之相关结构体
http://blog.csdn.net/jianchi88/article/details/7212599
Linux设备驱动--块设备(三)之程序设计
http://blog.csdn.net/jianchi88/article/details/7212701
Linux设备驱动--块设备(四)之“自造请求”
http://blog.csdn.net/jianchi88/article/details/7213290
Linux内核之mmc子系统-sdio
http://blog.csdn.net/mrwangwang/article/details/35997153/
ARM Linux 3.x的设备树(Device Tree) http://blog.csdn.net/21cnbao/article/details/8457546
linux device tree源代码解析 http://blog.chinaunix.net/uid-27717694-id-4274992.html
深入理解Android之设备加密DeviceEncryption http://blog.csdn.net/innost/article/details/44519775
模块单元 |
规格 |
备注 |
处理器(MCU) |
MSM8953 |
|
内核版本 |
Linux3.1.8 |
|
Android版本 |
7.0 |
|
编译环境 |
Ubuntu |
|
交叉编译环境 |
ARM Compiler Tools 5.01 update 3 |
|
调试和测试工具 |
ADB,TRACE32,串口,dump日志 |
|
|
|
|
SLC、MLC、TLC三种闪存的MOSFET是完全一样的,区别如下:
1)如何对单元进行编程:区别在于每个存储单元存储的数据位数;
SLC要么编程,要么不编程,状态只能是0、1。
MLC每个单元存储俩比特,状态就有四种00、01、10、11,电压状态对应也有四种。
TLC每个单元三个比特,状态就有八种了(000、001、010、100、011、101、110、111),电压状态就有八种。
2)性价比:
SLC=Single-LevelCell,即1bit/cell,速度快寿命长,价格超贵(约MLC3倍以上的价格),约10万次擦写寿命
MLC=Multi-LevelCell,即2bit/cell,速度一般寿命一般,价格一般,约3000---10000次擦写寿命
TLC=Trinary-LevelCell,即3bit/cell,也有Flash厂家叫8LC,速度相对慢寿命相对短,价格便宜,约500次擦写寿命
3)应用:
简单地说SLC的性能最优,价格超高。一般用作企业级或高端发烧友。MLC性能够用,价格适中为消费级SSD应用主流,TLC综合性能最低,价格最便宜。但可以通过高性能主控、主控算法来弥补、提高TLC闪存的性能。
在手机闪存中,常常使用的MLC;
为了保存重要的数据,采用增强型用户分区;将MLC重新编程为SLC,在牺牲一半容量低前提下,保证重要数据可以稳定的保存;
4)简单做一个表述;
3.2 EMMC/SD卡协议概要:
EMMC协议针对不同的平台和时间进行的演化示意图如下:
MSM8952和MSM8953,都是使用了EMMC5.1
如下图,EMC协议仅仅限于SDIO接口与Device Controller之前的通信;
通信协议分为三部分:command, data, response;
command和response,呈现在CMD线;
data,呈现在1bit, 4bit, 8bit的DATA线上;
读写,通过command触发,并会不断受到response;
以块为最小的单位,block+CRC校验作为一个传输单位;
command全部有host触发,有不同class的各级命令;
response由device被动的响应;针对不同的command命令,有不同的response;
擦除的方式:EraseTrim Discard Sanitize详解 http://www.xuebuyuan.com/2200006.html
SD1.0,2.0,3.0;
目前8953平台支持SD3.0协议;
具体协议参考:sd3.0协议_SD卡协议.pdf
SD card协议不断演进,目前已经支持到SD3.0,主要是速度和容量的变化,差别如图:
协议 |
最大容量 |
理论最大速度 |
SD1.0 |
2GB |
25 MB/S |
SD1.1 |
2GB |
50 MB/S |
SD2.0 |
32GB |
50 MB/S |
SD3.0 |
2TB |
208 MB/S |
常见的分区包括:物理分区和逻辑分区;
物理分区:eMMC本身自己的分区,即物理上的,不是通过软件就能实现的分区。
逻辑分区:Android 2.x.x 版本上使用的是MBR,4.0版本以后就是使用的GPT分区方式。
不管是MBR还是GPT,他们的分区都是指“逻辑上”的!!!即通过软件实现的,文件系统级别的。
EMMC的分区配置;http://blog.sina.com.cn/s/blog_71fdf1f00102v5c2.html
EMMC的分区有一些是AP不能修改的(如BOOT1、BOOT2和RPMB分区),有一些是可以通过特定的命令和寄存器就可以修改的(如Enhanced Partition和GPAP)。下面就来集体说明一下:
通常,从厂家出来的eMMC 主要由这几个部分组成:
1. BOOT Area Partition 1
2. BOOT Area Partition 2
3. RPMB
4. General purpose Partitions
5. Enhanced user data area
Enhanced Partition的主要功能就是将MLC配置成为SLC;将单个存储单元的存储信息由2位变成1位,在牺牲一半空间的基础上,获取稳定的特性,为保存重要的数据提供便利;
SD卡是Secure digitalmemory card的缩写,因其具有高安全性,高容量,高性能,体积小,低功耗,协议简单等多方面的优势,已经被广泛应用于各个领域。因SD主要功能是用于资料的存储,能正常存储是SD卡应用的主要功能。其次是达到用户对于性能的要求,比如读取速率,长时间读取卡的可靠性,不支持热插拨,突然掉电可靠性等。
1, emmc,烧录保存系统文件,保证系统启动;
2, 传输和存储用户数据;
性能需求
SD卡功能主要进行数据存储功能,具体性能包括:
1:能为系统应用提供存储接口
2:长时间数据传输稳定,数据没有错误
3:能支持各种厂商,不同容量的SD卡,兼容性高(1G\2G\4G\8G\16G\\32G\64G\2T)
4:休眠唤醒后,SD卡能正常使用
5:支持SD卡当U盘使用(写速度不小于2MB/S,读速度不小于3MB/S )
6:读写速度符合要求(写速度不小于2MB/S,读速度不小于8MB/S)
7:插入SD卡后,读写时工作电流和待机电流满足datasheet要求
8:SD文件系统可读写,文件系统不会损坏
序号 |
内容 |
1 |
可靠性,传输数据的稳定性 |
2 |
兼容性 |
3 |
对多次热插拨的支持 |
4 |
读写速度保证 |
EMMC接口:
(CPU端)
EMMC端的电源:
SD卡电路:
(CPU端)
(卡槽接口端)
SD卡检测管脚:(gpio_67)
6:初始化时上电流程:首先VDD上电,然后VDD_io上电,启动完后,如果中间没有对SD操作,动态将VDD电源调为0V,当有对SD卡读写时,重新对SD卡上电,VDD_io电压根据不同协议的卡去调整电压。
上电时序:(EMMC协议的P257页)
SD卡协议,P122;
7:在OTA升级时,首先卸载掉SD卡或是关掉SD卡电源,避免在升级过程中对SD卡操作,待升级完后,再重新上电,然后挂载SD卡
8:恢复出厂设置时,首先关掉SD卡电源,避免在升级过程中对SD卡写参数,导致SD卡文件损坏或是烧卡,在恢复出厂设置后,再重新上电,然后挂载SD卡
9:fastmmi模式下,上电SD,并初始化,并挂载;
SD 与CPU 的通信协议使用SDIO 方式进行通信。
需要硬件资源 GPIO网络名字 |
功能描述 |
信号方向 (相对CPU而言) |
开机时默认配置 |
On off 配置 |
备注 |
|
SDC2_CLK |
时钟线 |
CPU 到 SD |
VDD_P2 |
没有上拉 |
|
|
SDC2_CMD |
命令线 |
双向 |
VDD_P2 |
(on)输出上拉 (off)输出上拉 |
|
|
SDC2_DATA_0 |
数据线 |
双向 |
VDD_P2 |
(on)输出上拉 (off)输出上拉 |
|
|
SDC2_DATA_1 |
数据线 |
双向 |
VDD_P2 |
(on)输出上拉 (off) 输出上拉 |
|
|
SDC2_DATA_2 |
数据线 |
双向 |
VDD_P2 |
(on)输出上拉 (off) 输出上拉 |
|
|
SDC2_DATA_3 |
数据线 |
双向 |
VDD_P2 |
(on)输出上拉 (off) 输出上拉 |
|
GPIO资源表
在MSM8952.dtsi里定义:
sdhc_1: sdhci@7824000 {
compatible = "qcom,sdhci-msm";
reg = <0x7824900 0x500>,<0x7824000 0x800>, <0x7824E00 0x200>;
reg-names = "hc_mem","core_mem", "cmdq_mem";
interrupts = <0 123 0>, <0 1380>;
interrupt-names = "hc_irq","pwr_irq";
sdhc-msm-crypto =<&sdcc1_ice>;
qcom,bus-width = <8>;
qcom,qos-planes = <3>;
qcom,cpu-dma-latency-us = <2 360430>;
qcom,cpu-dma-latency-us-r = <2 360430>;
qcom,cpu-dma-latency-us-w = <2 360430>;
qcom,cpu-affinity-r ="affine_cores";
qcom,cpu-affinity-mask-r = <0xf0>;
qcom,modified-dynamic-qos;
qcom,msm-bus,name = "sdhc1";
qcom,msm-bus,num-cases = <9>;
qcom,msm-bus,num-paths = <1>;
qcom,msm-bus,vectors-KBps = <78 512 00>, /* No vote */
<78 512 1046 3200>, /* 400 KB/s*/
<78 512 52286 160000>, /* 20MB/s */
<78 512 65360 200000>, /* 25MB/s */
<78 512 130718 400000>, /* 50MB/s */
<78 512 130718 400000>, /* 100MB/s */
<78 512 261438 800000>, /* 200MB/s */
<78 512 261438 800000>, /* 400MB/s */
<78 512 1338562 4096000>; /*Max. bandwidth */
qcom,bus-bw-vectors-bps = <0 40000020000000 25000000 50000000
100000000 200000000 400000000 4294967295>;
clocks = <&clock_gccclk_gcc_sdcc1_ahb_clk>,
<&clock_gcc clk_gcc_sdcc1_apps_clk>,
<&clock_gccclk_gcc_sdcc1_ice_core_clk>;
clock-names = "iface_clk","core_clk", "ice_core_clk";
qcom,clk-rates = <400000 2500000050000000 100000000 192000000 384000000>;
qcom,ice-clk-rates = <200000000100000000>;
qcom,bus-speed-mode ="HS400_1p8v", "HS200_1p8v", "DDR_1p8v";
qcom,scaling-lower-bus-speed-mode ="DDR52";
status = "disabled";
};
sdhc_2: sdhci@7864000 {
compatible = "qcom,sdhci-msm";
reg = <0x7864900 0x11c>,<0x7864000 0x800>;
reg-names = "hc_mem","core_mem";
interrupts = <0 125 0>, <0 2210>;
interrupt-names = "hc_irq","pwr_irq";
qcom,bus-width = <4>;
qcom,cpu-dma-latency-us = <701>;
qcom,msm-bus,name = "sdhc2";
qcom,msm-bus,num-cases = <8>;
qcom,msm-bus,num-paths = <1>;
qcom,msm-bus,vectors-KBps = <81 512 00>, /* No vote */
<81 512 1046 3200>, /* 400 KB/s*/
<81 512 52286 160000>, /* 20MB/s */
<81 512 65360 200000>, /* 25MB/s */
<81 512 130718 400000>, /* 50MB/s */
<81 512 261438 800000>, /* 100MB/s */
<81 512 261438 800000>, /* 200MB/s */
<81 512 1338562 4096000>; /*Max. bandwidth */
qcom,bus-bw-vectors-bps = <0 40000020000000 25000000 50000000
100000000 2000000004294967295>;
clocks = <&clock_gccclk_gcc_sdcc2_ahb_clk>,
<&clock_gcc clk_gcc_sdcc2_apps_clk>;
clock-names = "iface_clk","core_clk";
qcom,clk-rates = <400000 2500000050000000 100000000 200000000>;
status = "disabled";
};
在MSM8952-mtp.dtsi定义:
// msm8952-mtp.dtsi
&sdhc_1 {
vdd-supply = <&pm8950_l8>;
qcom,vdd-voltage-level = <29000002900000>;
qcom,vdd-current-level = <200 570000>;
vdd-io-supply = <&pm8950_l5>;
qcom,vdd-io-always-on;
qcom,vdd-io-lpm-sup;
qcom,vdd-io-voltage-level = <18000001800000>;
qcom,vdd-io-current-level = <200325000>;
pinctrl-names = "active","sleep";
pinctrl-0 = <&sdc1_clk_on&sdc1_cmd_on &sdc1_data_on &sdc1_rclk_on>;
pinctrl-1 = <&sdc1_clk_off&sdc1_cmd_off &sdc1_data_off &sdc1_rclk_off>;
qcom,nonremovable;
status = "ok";
};
&sdhc_2 {
vdd-supply = <&pm8950_l11>;
qcom,vdd-voltage-level = <29500002950000>;
qcom,vdd-current-level = <15000400000>;
vdd-io-supply = <&pm8950_l12>;
qcom,vdd-io-voltage-level = <18000002950000>;
qcom,vdd-io-current-level = <20022000>;
pinctrl-names = "active","sleep";
pinctrl-0 = <&sdc2_clk_on&sdc2_cmd_on &sdc2_data_on &sdc2_cd_on>;
pinctrl-1 = <&sdc2_clk_off&sdc2_cmd_off &sdc2_data_off &sdc2_cd_off>;
#address-cells = <0>;
interrupt-parent = <&sdhc_2>;
interrupts = <0 1 2>;
#interrupt-cells = <1>;
interrupt-map-mask = <0xffffffff>;
interrupt-map = <0 &intc 0 125 0
1 &intc 0 221 0
2 &msm_gpio 67 0>;
interrupt-names = "hc_irq","pwr_irq", "status_irq";
cd-gpios = <&msm_gpio 67 0x1>;
status = "ok";
};
detect检测管脚:
//msm8952-CPA8_931-P0.dtsi.c(dts) 39207 2016/3/31
// SD卡检测管脚配置:msm8952-CPA8_931-P0.dtsi.c
/* Begin add pinmux*/
&tlmm_pinmux {
/delete-node/ cross-conn-det;
/delete-node/tpiu_setb_1;
/delete-node/tpiu_setb_2;
/delete-node/tpiu_setb_3;
/delete-node/tpiu_setb_4;
/*configure sdcard gpio*/
sdhc2_cd_pin {
qcom,pins = <&gp67>;
qcom,num-grp-pins =<1>;
qcom,pin-func = <0>;
label ="cd-gpio";
sdc2_cd_on: cd_on {
drive-strength= <2>;
bias-disable;
};
sdc2_cd_off: cd_off {
drive-strength= <2>;
bias-disable;
};
};
}
msm8952-CPA8_931-P0.dtsi.c (dts) 39207 2016/3/31
参考文档80-NV610-4_MSM8952CLOCK PLAN.pdf
如图为系统内部分时钟,GPLL4为SDCC提供时钟,并最大可以达到1.2GHZ;经过锁相环、分频等得到EMMC和SD卡所需要的时钟;
在dts里设置的速率有:
qcom,msm-bus,vectors-KBps = <78 512 00>, /* No vote */
<78 512 1046 3200>, /* 400 KB/s*/
<78 512 52286 160000>, /* 20MB/s */
<78 512 65360 200000>, /* 25MB/s */
<78 512 130718 400000>, /* 50MB/s */
<78 512 130718 400000>, /* 100MB/s */
<78 512 261438 800000>, /* 200MB/s */
<78 512 261438 800000>, /* 400MB/s */
<78 512 1338562 4096000>; /*Max. bandwidth */
当EMMC支持HS200,可以达到200MHZ;HS400模式,可以达到400MHZ
SD3.0,最大速度50MHZ;
中断 |
中断名称 |
中断号 |
中断方式 |
控制器中断 |
hc_irq |
125 |
cpu内部中断 |
控制器中断 |
pwr_irq |
221 |
cpu内部中断 |
Detect 中断 |
Gpio 67 |
边沿触发 |
SDIO控制器中断用于当SD在传输,出错,FIFO满等状态下产生,当此中断产生后读取MCI_STATUS寄存器判断是什么事件导致产生中断,并对其进行相应处理,而Detect 中断则在插拔卡时产生。
在arch\arm\mach-msm\include\mach\dma.h中如下定义
#define DMOV_SDC1_CHAN 8
#define DMOV_SDC1_CRCI 6
#define DMOV_SDC2_CHAN 8
#define DMOV_SDC2_CRCI 7
#define DMOV_SDC3_CHAN 8
#define DMOV_SDC3_CRCI 12
#define DMOV_SDC4_CHAN 8
#define DMOV_SDC4_CRCI 13
在代码中可以设置的宏定义如下:
#define SDHCI_USE_SDMA (1<<0) /* Host is SDMA capable */
#define SDHCI_USE_ADMA (1<<1) /* Host is ADMA capable */
#define SDHCI_REQ_USE_DMA (1<<2) /*Use DMA for this req. */
工作状态机切换示意图如下,参考:80-NL199-1_SDHCI ARCHITECTURE AND DEBUGGING.pdf
Linux设备驱动分三类,字符驱动、块设备驱动、网络设备驱动;存储类使用块设备,进行高效的数据传输管理;
块设备:是一种能够具有一定结构的随机存取设备,读写按块进行;使用缓冲区存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次那个姓读取到缓冲区;
与之不同的字符设备:是一种顺序的数据流设备,按字符进行;连续的字符形成一个数据流,不具备缓冲区,读写是实时的;
块设备和字符设备最大的区别在于读写数据的基本单元不同。
块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的基本单元为字节。
从实现角度来看,字符设备的实现比较简单,内核例程和用户态API一一对应,这种映射关系由字符设备的file_operations维护。块设备接口则相对复杂,读写API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。
Linux内核与块设备,处理数据的基本单元有所区别;
扇区(sectors):任何块设备对数据处理的基本单位;通常,1sector = 512Byte
块(block):有Linux指定的,对内核或文件系统数据处理的基本单位;1block有N个sector组成;
段(segment):若干个相邻的block组成,是Linux内存管理机制中内存的组成部分;
关系如下:
总结:(linux数据在内存中处理,以页为单位进行,到文件系统以块为单位进行,传输到块设备则以扇区为单位处理)
第一层:文件系统部分:在kernel/fs目录。
文件系统部分三大块:一是上层的文件系统的系统调用,是用户应用层许对内核空间的调用交互方式之一;二是虚拟文件系统 VFS(Virtual Filesystem Switch),三是挂载到 VFS 中的各实际文件系统;
1)系统调用(SystemCall)是操作系统为在用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口;
2)虚拟文件系统(VFS)是linux内核和具体I/O设备之间的封装的一层共通访问接口,通过这层接口,linux内核可以以同一的方式访问各种I/O设备。(open,read, write,ioctl,close的公用接口);虚拟文件系统本身是linux内核的一部分,是纯软件的东西,并不需要任何硬件的支持。
3)实际文件系统,对实际的设备数据进行布局和寻址;对于EMMC,常用ext2,ext3,ext4等;对SD卡,常用FAT,FAT32,exFAT等文件系统;包含磁盘文件系统,和固态硬盘文件系统;前者用于电脑的硬盘,后者则是手机的存储器;对于原有的nand flash则是mtd文件系统,如jffs2,yaffs2等;
第二层:块设备中间层:在kernel/block/,kernel/drivers/block/目录
1) 通用块层(GenericBlock Layer):在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。
Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。
2) I/O调度程序层(IOScheduler Layer):但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
当多个请求提交给块设备时,执行效率依赖于请求的顺序。如果所有的请求是同一个方向(如:写数据),执行效率是最大的。内核在调用块设备驱动程序例程处理请求之前,先收集I/O请求并将请求排序,然后,将连续扇区操作的多个请求进行合并以提高执行效率(内核算法会自己做,不用你管),对I/O请求排序的算法称为电梯算法(elevatoralgorithm)。电梯算法在I/O调度层完成。内核提供了不同类型的电梯算法,电梯算法有
1 noop(实现简单的FIFO,基本的直接合并与排序),
2anticipatory(延迟I/O请求,进行临界区的优化排序),
3Deadline(针对anticipatory缺点进行改善,降低延迟时间),
4 Cfq(均匀分配I/O带宽,公平机制)
PS:其实IO调度层(包括请求合并排序算法)是不需要用户管的,内核已经做好
相关数据结构
block_device: 描述一个分区或整个磁盘对内核的一个块设备实例
block_device_operations: 描述块设备的具体操作;
gendisk: 描述一个通用硬盘(generic hard disk)对象。
hd_struct: 描述分区应有的分区信息
bio: 描述块数据传送时怎样完成填充或读取块给driver
request: 描述向内核请求一个列表准备做队列处理。
request_queue: 描述内核申请request资源建立请求链表并填写BIO形成队列。
第三层:Linux mmc子系统(在kernel/drivers/mmc目录)
Linuxkernel把mmc,sd以及sdio三者的驱动代码整合在一起,俗称mmc子系统。源码位于drivers/mmc下。其下有三个子目录,分别是:
card
core
host
其中,
1) card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;把操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取
2) core抽象了mmc,sd,sdio三者的通用操作;将数据以何种格式,何种方式在MMC/SD主机控制器与MMC/SD卡的记忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议
3) host则是高通平台上的host驱动代码,动手实现的具体MMC/SD设备驱动了。
第四层:实际的文件传输;
由host到device的传输;有command和data线传输实际的数据;
参考EMMC协议的传输状态机;
根据不同的操作方式,进入不同的状态,可以通过响应的CMD命令进行出发;
1,识别过程:
2,传输模式:
驱动的初始化顺序:(参考system.map内核符号表,可以查看到init初始化加载的顺序);
mmc主要的结构体:
1. struct mmc_host 用来描述卡控制器位kernel/include/linux/mmc/host.h下面。
a) 使用sdhci标准接口,所以有了多级host的嵌套;
b) mmc_host->private = (struct sdhci_host*)host;
c) host->private = (struct sdhci_pltfm_host*)pltfm_host;
d) pltfm_host->private = (struct sdhci_msm_host*)msm_host;
2. struct cmdq_host_ops,基于新特性command queue,添加的操作函数;
3. struct mmc_card 用来描述卡位于kernel/include/linux/mmc/card.h下面
4. struct mmc_driver 用来描述mmc卡驱动在kernel/include/linux/mmc/card.h下面。
5. struct file_operations,文件系统针对用户程序提供的接口;也是对底层最大程度的封装。
6. struct mmc_host_ops用来描述卡控制器操作集,用于从主机控制器向core层注册操作函数,从而将core层与具体的主机控制器隔离。也就是说core要操作主机控制器,就是这个ops当中给的函数指针操作,不能直接调用具体主控制器的函数。 位于kernel/include/linux/mmc/host.h下面。
7. struct mmc_ios用于描述了控制器对卡的I/O状态。位于kernel/include/linux/mmc/host.h下面。
8. struct mmc_request用于描述读写MMC卡的请求,它包括命令,数据以及请求完成后的回调函数。位于kernel/include/linux/mmc/core.h中。
9. struct mmc_queue是MMC的请求队列结构,它封装了通用请求队列结构,加入了MMC卡相关结构。位于kernel/drivers/mmc/card/queue.h中。
10.structmmc_data描述了MMC卡读写的数据相关信息,如:请求,操作命令,数据以及状态等。位于kernel/include/linux/mmc/core.h中。
11.structmmc_blk_data描述块设备的数据;
12.structmmc_command描述了MMC卡操作相关命令及数据,状态信息等。位于kernel/include/linux/mmc/core.h中。
13.structmmc_cid, mmc_csd, mmc_ext_csd, sd_scr, sd_ssr等,读写配置存储器的寄存器;
重要函数:
host主要函数:
structmmc_host *mmc_alloc_host(int extra, struct device *dev)
intmmc_add_host(struct mmc_host *host)
voidmmc_remove_host(struct mmc_host *host)
voidmmc_remove_host(struct mmc_host *host)
card主要函数:
staticstruct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
staticint mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md)
staticint mmc_add_disk(struct mmc_blk_data *md)
mmc_driver主要函数:
extern int mmc_register_driver(struct mmc_driver *);
extern void mmc_unregister_driver(struct mmc_driver *);
mmc_blk主要函数:
int register_blkdev(unsigned int major, const char *name)
staticstruct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
struct device *parent,
sector_t size,
bool default_ro,
const char *subname,
int area_type)
staticint mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
staticvoid mmc_blk_remove_req(struct mmc_blk_data *md)
通信的基础:
参考协议中的command, host等时序进行通信;
按照EMMC和SD3.0的协议,内置CID,CSD,EXT_CSD等寄存器,保存了各项信息;保存了MMC的工作条件和支持项,host根据寄存器的值做适合通信的配置初始化;
structmmc_cid ;
structmmc_csd ;
structmmc_ext_csd ;
对mmc的操作具体函数实现:
mmc_init_card()读取具体的寄存器值,并做出初始化配置;
并通过device_create_file(),将其在/sys目录下反馈;
在mmc_ops.h里有各个具体的操作函数;
intmmc_select_card(struct mmc_card *card);
intmmc_deselect_cards(struct mmc_host *host);
intmmc_go_idle(struct mmc_host *host);
intmmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
intmmc_all_send_cid(struct mmc_host *host, u32 *cid);
intmmc_set_relative_addr(struct mmc_card *card);
intmmc_send_csd(struct mmc_card *card, u32 *csd);
intmmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
intmmc_send_status(struct mmc_card *card, u32 *status);
intmmc_send_cid(struct mmc_host *host, u32 *cid);
intmmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
intmmc_spi_set_crc(struct mmc_host *host, int use_crc);
intmmc_card_sleepawake(struct mmc_host *host, int sleep);
intmmc_bus_test(struct mmc_card *card, u8 bus_width);
intmmc_send_hpi_cmd(struct mmc_card *card, u32 *status);
int mmc_discard_queue(structmmc_host *host, u32 tasks);
通过device_create_file(),将其反馈到/sys目录下显示,并进行交互;方便调试;
block主要结构体:
结构体block_device代表一个块设备对象,如:整个硬盘或特定分区。如果该结构代表一个分区,则其成员bd_part指向设备的分区结构。如果该结构代表设备,则其成员bd_disk指向设备的通用硬盘结构gendisk
结构体gendisk代表了一个通用硬盘(generic hard disk)对象,它存储了一个硬盘的信息,包括请求队列、分区链表和块设备操作函数集等。块设备驱动程序分配结构gendisk实例,装载分区表,分配请求队列并填充结构的其他域。
结构体block_device_operations字符设备通过 file_operations 操作结构使它们的操作对系统可用. 一个类似的结构用在块设备上是 struct block_device_operations,定义在
block_device_operations是块设备对应的操作接口,是连接抽象的块设备操作与具体块设备操作之间的枢纽。并不能完全提供文件操作全部的API,实际上只提供了open、release等函数,其他的文件操作依赖于def_blk_fops:
conststruct file_operations def_blk_fops = {
.open =blkdev_open,
.release =blkdev_close,
.llseek =block_llseek,
.read =do_sync_read,
.write =do_sync_write,
.aio_read =blkdev_aio_read,
.aio_write =blkdev_aio_write,
.mmap =generic_file_mmap,
.fsync =blkdev_fsync,
.unlocked_ioctl = block_ioctl,
#ifdefCONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read =generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
};
结构request代表了挂起的I/O请求,每个请求用一个结构request实例描述,存放在请求队列链表中,由电梯算法进行排序,每个请求包含1个或多个结构bio实例;IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个bio。
请求队列结构request_queue,每个块设备都有一个请求队列,每个请求队列单独执行I/O调度,请求队列是由请求结构实例链接成的双向链表,链表以及整个队列的信息用结构request_queue描述,称为请求队列对象结构或请求队列结构。它存放了关于挂起请求的信息以及管理请求队列(如:电梯算法)所需要的信息。结构成员request_fn是来自设备驱动程序的请求处理函数。
重要函数:
块设备的注册与注销:
intregister_blkdev(unsigned int major, const char *name);
int unregister_blkdev(unsigned int major, const char*name);
对磁盘:
externvoid add_disk(struct gendisk *disk);
extern voiddel_gendisk(struct gendisk *gp);
队列:
structrequest_queue *blk_alloc_queue(gfp_t gfp_mask)
structrequest_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
structrequest *blk_make_request(struct request_queue *q, struct bio *bio, gfp_t gfp_mask)
}
static structsdhci_ops sdhci_msm_ops = {
.crypto_engine_cfg = sdhci_msm_ice_cfg,
.crypto_cfg_reset = sdhci_msm_ice_cfg_reset,
.crypto_engine_reset = sdhci_msm_ice_reset,
.platform_reset_enter =sdhci_msm_reset_enter,
.set_uhs_signaling =sdhci_msm_set_uhs_signaling,
.check_power_status =sdhci_msm_check_power_status,
.execute_tuning = sdhci_msm_execute_tuning,
.enhanced_strobe =sdhci_msm_enhanced_strobe,
.toggle_cdr = sdhci_msm_toggle_cdr,
.get_max_segments = sdhci_msm_max_segs,
.set_clock = sdhci_msm_set_clock,
.get_min_clock = sdhci_msm_get_min_clock,
.get_max_clock = sdhci_msm_get_max_clock,
.disable_data_xfer =sdhci_msm_disable_data_xfer,
.dump_vendor_regs =sdhci_msm_dump_vendor_regs,
.config_auto_tuning_cmd =sdhci_msm_config_auto_tuning_cmd,
.enable_controller_clock =sdhci_msm_enable_controller_clock,
.reset_workaround = sdhci_msm_reset_workaround,
.clear_set_dumpregs =sdhci_msm_clear_set_dumpregs,
.notify_load = sdhci_msm_notify_load,
.notify_pm_status =sdhci_msm_notify_pm_status,
.enhanced_strobe_mask =sdhci_msm_enhanced_strobe_mask,
};
其中调用了sdhci的公共接口:
static const structmmc_host_ops sdhci_ops = {
.pre_req =sdhci_pre_req,
.post_req =sdhci_post_req,
.request =sdhci_request,
.set_ios =sdhci_set_ios,
.get_cd =sdhci_get_cd,
.get_ro =sdhci_get_ro,
.hw_reset =sdhci_hw_reset,
.enable_sdio_irq = sdhci_enable_sdio_irq,
.start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
.execute_tuning = sdhci_execute_tuning,
.enhanced_strobe = sdhci_enhanced_strobe,
.card_event =sdhci_card_event,
.card_busy =sdhci_card_busy,
.enable =sdhci_enable,
.disable =sdhci_disable,
.stop_request = sdhci_stop_request,
.get_xfer_remain = sdhci_get_xfer_remain,
.notify_load =sdhci_notify_load,
.notify_pm_status = sdhci_notify_pm_status,
};
驱动模型,按照总线,设备,驱动的形式来大概讲解框架;
总线:由mmc总线和sdio总线:
static structbus_type mmc_bus_type = {
.name ="mmc",
.dev_attrs =mmc_dev_attrs,
.match =mmc_bus_match,
.uevent =mmc_bus_uevent,
.probe =mmc_bus_probe,
.remove =mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm =&mmc_bus_pm_ops,
};
static structbus_type sdio_bus_type = {
.name ="sdio",
.dev_attrs =sdio_dev_attrs,
.match =sdio_bus_match,
.uevent =sdio_bus_uevent,
.probe =sdio_bus_probe,
.remove =sdio_bus_remove,
.pm =SDIO_PM_OPS_PTR,
};
分别通过mmc_register_bus()和sdio_register_bus()进行注册;
总线操作:
static const structmmc_bus_ops mmc_ops = {
.awake = mmc_awake,
.sleep = mmc_sleep,
.remove = mmc_remove,
.detect = mmc_detect,
.suspend = NULL,
.resume = NULL,
.power_restore = mmc_power_restore,
.alive = mmc_alive,
.change_bus_speed = mmc_change_bus_speed,
};
static const structmmc_bus_ops mmc_sd_ops = {
.remove = mmc_sd_remove,
.detect = mmc_sd_detect,
.suspend = NULL,
.resume = NULL,
.power_restore = mmc_sd_power_restore,
.alive = mmc_sd_alive,
.change_bus_speed = mmc_sd_change_bus_speed,
};
static const structmmc_bus_ops mmc_sdio_ops = {
.remove = mmc_sdio_remove,
.detect = mmc_sdio_detect,
.suspend = mmc_sdio_suspend,
.resume = mmc_sdio_resume,
.power_restore = mmc_sdio_power_restore,
.alive = mmc_sdio_alive,
};
最上层的操作:
static const structblock_device_operations mmc_bdops = {
.open =mmc_blk_open,
.release =mmc_blk_release,
.getgeo =mmc_blk_getgeo,
.owner =THIS_MODULE,
.ioctl =mmc_blk_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mmc_blk_compat_ioctl,
#endif
};
const struct file_operationsext4_dir_operations = {
.llseek =ext4_dir_llseek,
.read =generic_read_dir,
.readdir =ext4_readdir,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.fsync =ext4_sync_file,
.release =ext4_release_dir,
};
const struct file_operationsfat_file_operations = {
.llseek =generic_file_llseek,
.read =do_sync_read,
.write =do_sync_write,
.aio_read =generic_file_aio_read,
.aio_write =generic_file_aio_write,
.mmap =generic_file_mmap,
.release =fat_file_release,
.unlocked_ioctl = fat_generic_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fat_generic_compat_ioctl,
#endif
.fsync =fat_file_fsync,
.splice_read =generic_file_splice_read,
};
文件信息的打印:
void ext4_msg(structsuper_block *sb, const char *prefix, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
printk_ratelimited("%sEXT4-fs (%s):%pV\n", prefix, sb->s_id, &vaf);
va_end(args);
}
如:
ext4_msg(sb,KERN_INFO, "recovery complete");
[010108:00:28.335140]@4 EXT4-fs (mmcblk0p27): recovery complete
fat_msg(sb,KERN_WARNING, "Volume was not properly "
"unmounted. Some data may be corrupt."
"Please run fsck.");
[0101 08:00:52.213219]@7FAT-fs (mmcblk1p1): Volume was not properly unmounted. Some data may becorrupt. Please run fsck.
内部接口函数名 |
功能 |
备注 |
msmsdcc_enable |
在该函数中打开SD 的时钟 |
|
msmsdcc_disable |
该函数主要关闭SD的时钟 |
|
msmsdcc_request |
处理SD的所有访问请求,对SD的所有操作都要经过该函数 |
|
msmsdcc_set_ios sdcc_set_ios |
该函数是对SD控制器的操作函数,主要用于配置SD的控制器 |
|
msmsdcc_get_ro |
确认SD卡是否为写保护 |
|
msmsdcc_enable_sdio_irq |
对SDIO 设备的操作函数,SD驱动未使用 |
|
msmsdcc_start_signal_voltage_switch |
SD的IO电压进行设置切 换到该模式 |
|
msmsdcc_execute_tuning |
UHS 型SD卡用于调整时序 |
|
|
|
|
|
|
|
|
|
|
2. 块设备向文件系统层接口
内部接口函数名 |
功能 |
备注 |
mmc_blk_open |
当文件描述符被打开时调用 |
|
mmc_blk_release |
当文件被关闭时调用 |
|
mmc_blk_getgeo |
获得SD的柱面,头,块信息 |
|
mmc_blk_ioctl |
当用Ioctl函数访问SD设备文件时调用该函数 |
|
mmc_blk_compat_ioctl, |
与mmc_blk_ioctl 函数作用相同,在该驱动中未使用 |
|
1. 对磁盘的访问节点为
/dev/block/mmcblk1*当该SD卡还有分区时*可以从p1开始到p25,一般对SD的访问都是通过挂载的方式访问,在通过各文件节点对SD上的数据访问。也可以用字符设备的方式访问,这样只能按照IO命令的方式进行操作。
2. SD卡一些特征在filesystem中的节点
/sys/devices/platform/msm_sdcc.3/mmc_host/mmc1/mmc1:1234/block/mmcblk0
内核提供给用户空间的接口有:
设备文件接口 |
功能描述 |
/dev/ mmcblk1 |
SD卡接口 |
/sys/devices/platform/msm_sdcc.2/mmc_host/mmc1 |
SD属性文件接口 |
|
|