LINUX之MMC子系统分析
夜暝 2024-08-21 09:07:03 阅读 98
目录
1. 概念1.1 MMC卡1.2 SD卡1.3 SDIO
2. 总线协议2.1 协议2.2 一般协议2.3 写数据2.4 读数据2.5 卡模式2.5.1 SD卡模式2.5.2 eMMC模式
2.6 命令2.6.1 命令类2.6.2 详细命令
2.7 应答2.8 寄存器2.8.1 OCR2.8.2 CID2.8.3 CSD2.8.4 RCA2.8.5 扩展CSD
3. 关键结构3.1 struct sdhci_host3.2 struct sdhci_ops3.3 struct mmc_host3.4 struct mmc_host_ops3.5 struct mmc_card3.6 struct mmc_request3.7 struct mmc_command3.8 struct mmc_data
4. 注册4.1 mmc_host层I. sdhci-xxxxII. sdhci_pltfmIII. sdhcia. sdhci_alloc_hostb. sdhci_add_host
4.2 mmc_core层I. hosta. mmc_alloc_hostb. mmc_add_host
II. corea. mmc_initb. mmc_start_hostc. mmc_claim/release_hostd. _mmc_detect_change
5. 扫卡(识别)5.1 mmc_rescan5.2 mmc_rescan_try_freq5.3 mmc_attach_xx(主要以emmc进行分析)5.4 mmc_init_cardI. mmc_bus_type描述:II. 扩展CSD寄存器175寄存器描述:III. 扩展CSD寄存器179寄存器描述:IV. 扩展CSD寄存器34寄存器描述:V. 扩展CSD寄存器187寄存器描述:VI. 扩展CSD寄存器161寄存器描述:VII. 扩展CSD寄存器15寄存器描述:
5.5 mmc_add_cardI. Debugfs下可以查看的一些属性
5.6 e•MMC状态图(设备识别模式)5.7 e•MMC状态图(数据传输模式)
6. 通信(命令)6.1 mmc_go_idle6.2 mmc_wait_for_cmd6.3 mmc_wait_for_req6.4 __mmc_start_req6.5 mmc_start_request6.6 sdhci_requestI. sdhci_send_commanda. sdhci主机控制器24H寄存器b. sdhci主机控制器08H寄存器c. sdhci主机控制器0CH寄存器d. sdhci主机控制器0EH寄存器
II. sdhci_cmd_irqa. sdhci主机控制器34H寄存器b. sdhci主机控制器38H寄存器c. sdhci主机控制器30H寄存器(中断状态)d. sdhci主机控制器32H寄存器(中断错误状态)
III. sdhci_finish_commanda. sdhci主机控制器10H寄存器
IV. sdhci_timeout_timer
6.7 sdhci_finish_mrqI. __sdhci_finish_mrqII. sdhci_request_done
6.8 mmc_wait_for_req_done
7. 通信(数据)7.1 mmc_send_cxd_data7.2 sdhci_prepare_data7.3 sdhci_set_block_infoI. sdhci主机控制器04H寄存器II. sdhci主机控制器06H寄存器
7.4 sdhci_data_irq7.5 sdhci_transfer_pioI. sdhci主机控制器20H寄存器
7.6 sdhci_finish_data7.7 sdhci_timeout_data_timer
8. 中断8.1 sdhci_irq8.2 sdhci_thread_irq
9. 块设备9.1 作为块设备的注册I. mmc_blk_probeII. mmc_blk_alloc_reqIII. mmc_blk_alloc_partsIV. 用户分区识别
9.2 block层的调用I. mmc_blk_mq_issue_rq
10. 参考文档
1. 概念
MMC(Multi-Media Card)子系统是Linux内核中的一个模块,主要用于管理SD卡和eMMC等可移动存储设备。使用MMC子系统可以使得SD卡等存储设备在Linux内核中被识别为一个块设备,并可以使用标准的块设备驱动程序进行管理。同时,MMC子系统也为SDIO卡提供了标准的接口,便于开发各种不同类型的SDIO卡设备驱动。
对与MMC,主要包括几个部分:MMC控制器、MMC总线、card
对于卡而言,包括MMC卡(7pin,支持MMC和spi两种通信模式)、SD卡(9pin,支持sd和spi两种通信模式)、TF卡(8pin,支持sd和spi两种通信模式),这些卡其总线规范类似,都是从MMC总线规范演化过来的
基于MMC这种通信方式,又演化了SDIO,SDIO强调的是IO一种总线,可以链接任何支持SDIO的外设(包括蓝牙设备、wifi设备等)。
CPU、MMC controller、存储设备之间的关联如下图所示,主要包括了MMC controller、总线、存储卡等内容的连接,针对控制器与设备的总线连接,主要包括时钟、数据、命令三种类型的引脚,而这些引脚中的cd引脚主要用于卡的在位检测,当mmc controller检测到该位的变化后,则会进行mmc card的注册或注销操作
MMC从本质上看,是一种用于固态非易失性存储的内存卡(memory card)规范,定义了诸如卡的形态、尺寸、容量、电气信号、和主机之间的通信协议等方方面面的内容。
从1997年MMC规范发布至今,基于不同的考量(物理尺寸、电压范围、管脚数量、最大容量、数据位宽、clock频率、安全特性、是否支持SPI mode、是否支持DDR[DDR: Dual data rate] mode、等等),进化出了MMC、SD、microSD、SDIO、eMMC等不同的规范。
下面是不同接口规范的封装引脚和大小对比图,当SD接口要和MMC兼容时,8和9脚留空即可
MMC、SD、SDIO的技术本质是一样的(使用相同的总线规范,等等),都是从MMC规范演化而来;
MMC强调的是多媒体存储(MM,MultiMedia);
SD强调的是安全和数据保护(S,Secure);
SDIO是从SD演化出来的,强调的是接口(IO,Input/Output),不再关注另一端的具体形态(可以是WIFI设备、Bluetooth设备、GPS等等)。
1.1 MMC卡
MMC(Multimedia Card)卡是一种较早的记忆卡标准,目前已经被SD标准所取代,目前基本上仅存eMMC。卡的管脚有VDD、GND、RST、CLK、CMD和DATA等,VDD和GND提供power,RST用于复位,CLK、CMD和DATA为MMC总线协议的物理通道
CLK有一条,提供同步时钟,可以在CLK的上升沿(或者下降沿,或者上升沿和下降沿)采集数据;
CMD有一条,用于传输双向的命令。
DATA用于传输双向的数据,根据MMC的类型,可以有一条(1-bit)、四条(4-bit)或者八条(8-bit)。
电压范围为1.65V和3.6V,根据工作电压的不同,MMC卡可以分为两类:
High Voltage MultiMediaCard,工作电压为2.7V~3.6V。
Dual Voltage MultiMediaCard,工作电压有两种,1.70V1.95V和2.7V3.6V,CPU可以根据需要切换。
CLK的频率范围,包括0-20MHz、0-26MHz、0-52MHz等几种,结合数据线宽度,基本决定了MMC的访问速度
1.2 SD卡
SD(Secure Digital)是一种flash memory card的标准,是一般常见的SD记忆卡。SD协议支持三种模式:4-wire mode, 1-wire mode, SPI mode。三种模式的信号定义如下:
SD卡按供电范围划分,分两种:
High Voltage SD Memory Card: 操作的电压范围在2.7-3.6V
UHS-II SD Memory Card: 操作的电压范围VDD1: 2.7-3.6V, VDD2: 1.70-1.95V
SD卡按总线速度模式来分,有下面几种:
Default Speed mode: 3.3V供电模式,频率上限25MHz,速度上限 12.5MB/sec
High Speed mode: 3.3V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR12: UHS-I卡, 1.8V供电模式,频率上限25MHz,速度上限 12.5MB/sec
SDR25: UHS-I卡, 1.8V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR50: UHS-I卡, 1.8V供电模式,频率上限100MHz,速度上限 50MB/sec
SDR104: UHS-I卡, 1.8V供电模式,频率上限208MHz,速度上限 104MB/sec
DDR50: UHS-I卡, 1.8V供电模式,频率上限50MHz,性能上限 50MB/sec
UHS156: UHS-II RCLK Frequency Range 26MHz - 52MHz, up to 1.56Gbps per lane.
注:UHS(Ultra High Speed)
1.3 SDIO
SDIO(Secure Digital I/O):就是SD的I/O接口(interface)的意思,更具体的说明,SD本来是记忆卡的标准,但是现在也可以把SD拿来插上一些外围接口使用,这样的技术便是SDIO
SDIO总线和USB总线类似,SDIO总线也有两端,其中一端是主机(HOST)端,另一端是设备端(DEVICE),采用HOST- DEVICE这样的设计是为了简化DEVICE的设计,所有的通信都是由HOST端发出命令开始的。在DEVICE端只要能解析HOST的命令,就可以同HOST进行通信了,SDIO的HOST可以连接多个DEVICE
2. 总线协议
2.1 协议
SDIO(EMMC通用,只不过数据线不一样)协议,其中包括“无数据传输的一般命令”,“有数据传输的写命令”,“有数据传输的读命令”。协议包含三个要素:命令Command,应答Response和数据Data。
Command:由HOST发送,DEVICE接收,在CMD信号线上传输。每一个命令Token都由一个起始位(’0’)前导,以一个停止位(’1’)终止。总长度是48比特。每一个Token都用CRC保护,因此可以检测到传输错误,可重复操作。
Response:由DEVICE发送,HOST接收,在CMD信号线上传输。应答根据不同命令分为4种(R1R3,R6)/EMMC有5种(R1R5),长度有48位或136位。
Data:数据是双向的传送的。可以设置为1线/4线模式,eMMC可以8线。数据是通过DAT0-DAT3/ DAT7信号线传输的(注:下图是SDR[区别DDR是时钟上下沿都进行通信]通信模式)。
2.2 一般协议
一般协议传输无应答(no response)命令和无数据(no data)命令。无应答命令只需要传输CMD,不需要应答,命令即携带了所有信息。无数据命令需要在CMD传输之后,返回相应的应答,但无数据传输。应答格式有多种
2.3 写数据
写数据的传输是分块进行的。数据块后面总是跟着CRC位。定义了单块和多块操作。多块操作模式更适合更快的写入操作。当CMD线路上出现停止命令时,多块传输终止。块写操作期间通过 Data0 信号线指示Busy 状态。
2.4 读数据
当无数据传输时,DAT0-DAT7总线上为高电平。传输数据块由各个DAT线上的起始位(低),以及随后连续的数据流所组成。数据流以各条线上的停止位(高)终结。数据传输是以时钟信号同步的
2.5 卡模式
除通用的协议之外,eMMC和SDIO协议已有较大不同,因此需要分别说明,比如卡模式,SD卡有3种eMMC有5种(3种和SD卡模式相同,另外多出2种),并且有各自不同的卡状态。
2.5.1 SD卡模式
非活动模式:设备工作电压范围或访问模式无效,则设备进入非活动模式。设备也可以通过使用GO_INACTIVE_STATE命令(CMD15)进入非活动模式
设备识别模式:复位后,Host 处于卡识别模式,寻找总线上的新卡。卡将处于这个模式,直到接收到Send_RCA命令(CMD3)
数据传输模式:卡的 RCA 首次发布后,卡进入数据传输模式。Host 识别完毕总线上的所有卡后,进入数据传输模式
2.5.2 eMMC模式
引导模式:上电周期后,接受参数为0xF0F0F0F0的CMD0或硬件复位信号有效,设备处于引导模式。
设备识别模式:在引导模式结束或主机、设备不支持引导操作模式时,设备处于设备识别模式。设备将在此模式下,直至接收到SET_RCA命令(CMD3)。在设备识别模式下,主机复位设备,验证工作电压范围和访问模式,识别设备并为总线上的设备分配相对设备地址(RCA)在设备识别模式下的所有数据通讯都仅采用命令线CMD
中断模式:主机与设备同时进入中断模式。在中断模式下没有数据传输。唯一允许的消息是来自主机或设备的中断服务请求。e•MMC系统的中断模式使主机能够向从机(设备)授予同时传输的许可。这种模式减少了主机轮询的负担,因而降低了系统功耗,同时保持了主机对设备服务请求的足够的责任。支持e•MMC中断模式是可选的,对主机和设备都是如此
数据传输模式:一旦分配了RCA,设备就进入数据传输模式。主机在识别总线上的设备后即进入数据传输模式。当设备处于stand-by状态,在CMD和DAT线上的通讯都将以推拉模式执行。直到主机知道CSD寄存器内容之前,fPP时钟速率都必须保持在fOD。主机发送SEND_CSD(CMD9)来获取设备专用数据(CSD寄存器),如块长度、设备存储容量、最大时钟速率等等。
当设备处于Stand-by状态时,CMD7被用于通过参数中包含设备的相对地址来选定设备并将其置于Transfer状态。如果设备此前已经被选定并处于Transfer状态,则当CMD7通过参数中不等于该设备自己相对地址的任意地址来取消选定时,它与主机的连接被释放,并返回Stand-by状态。当CMD7以保留的相对设备地址0x0000发送时,该设备返回Stand-by状态。处于Transfer状态的设备接收到有设备自己的相对地址的CMD7时,将忽略该命令,也可能当作非法命令来处理。在设备被分配了一个RCA之后,就不再应答识别命令——CMD1、CMD2和CMD3
非活动模式:如果设备工作电压范围或访问模式无效,则设备进入非活动模式。设备也可以通过使用GO_INACTIVE_STATE命令(CMD15)进入非活动模式。上电周期后,设备将复位到Pre- idle模式。
2.6 命令
协议定义了4种命令:
无应答广播命令(bc)有应答广播命令(bcr)寻址(点对点)命令(ac),无DAT线数据传输寻址数据传输(点对点)命令(adtc),数据在DAT线传输
所有的命令有固定 48Bit 编码长度,需要传输时间1.92us@25MHz 和0.96us@50MHz
2.6.1 命令类
在SD协议中Class 0,2,4,5 和8 是强制的,应该被所有的卡支持,在emmc协议中Class 0是必需的,所有的设备均应支持。其他类对特定类型设备可能是必需的,也可能是可选的
2.6.2 详细命令
下面皆以eMMC为例,CMD0~15为基本命令
CMD16~18,CMD21为块读命令
CMD23~27,块写
CMD35,CMD36,CMD38块擦除
2.7 应答
所有的应答均通过命令线CMD发送。编码长度取决于应答类型。应答总是以起始位开始(总是‘0’),紧接表示传输方向的比特(设备= ‘0’)。下面表中标记为 ‘x’ 的值表示一个变量。除R3类型之外,所有应答将被CRC保护。每一个应答编码都以停止位结束(总是‘1’)
R1(正常应答类型):编码长度48 bit。bits 45:40表示应答相对的命令索引数字(0到63)。设备的状态编码用32 bit表示。(R1b额外在数据线 DAT0上发送可选的忙信号,其余与R1相同)
R2(CID、CSD寄存器):编码长度136 bit。CID寄存器的内容作为对CMD2和CMD10的应答发送。CSD寄存器的内容作为CMD9的应答发送。CID和CSD只有bit [127:1]被发送,保留的bit 0被应答的停止位替代
R3(OCR寄存器):编码长度48 bit。OCR寄存器的内容作为CMD1的应答发送
R4(快速I/O):编码长度48 bit。参数域包含被寻址设备的RCA、要读写的寄存器地址及其内容。如果操作成功参数中的结果位被置位。
R5(中断请求):编码长度48 bit。如果应答是主机生成的,参数中的RCA应为0。
2.8 寄存器
协议中规定的几个寄存器分别为:OCR、CID、CSD(扩展CSD)、RCA、DSR(可选),和SDIO协议中的SCR。下面皆以eMMC寄存器为例
2.8.1 OCR
32比特的工作条件寄存器(OCR)寄存着设备的VDD电压概况和访问模式指示。另外,此寄存器包括一个状态信息位。此状态位当设备上电例程结束时置位。所有设备均应实现OCR寄存器。
该寄存器的作用:
存储Vdd电压曲线存储器件访问模式,从而可以知道器件是否为大容量设备(即设备容量大于2G,只有大容量设备才支持扇区访问模式,小容量设备只支持字节访问模式)指示设备上电过程(即bit31:如果设备没有完成上电例程,此位设置为低)
2.8.2 CID
卡的识别寄存器(CID)是一个128Bit 宽度。其包括了卡的鉴别信息,其用于在卡的鉴别相中。每一个读/写(RW)卡应该有一个唯一的鉴别号。其中MID号由JEDEC管理,分配给eMMC制造商,每个制造商的MID都是独一无二的(同理SD卡的MID由SD-3C控制并分配)。
2.8.3 CSD
设备专用数据寄存器(CSD)提供设备内容访问方式的信息。CSD定义了数据格式、纠错类型、最长数据访问时间、数据传输速度、DSR寄存器是否可用等等。寄存器可编程的部分(标有W或E的项目,见下)可以用CMD27命令更改。
R: 只读
W: 可一次编程且不可读
R/W: 可一次编程且可读
W/E: 可多次写,其值在掉电、硬件复位和任何CMD0复位后保持,不可读
R/W/E: 可多次写,其值在掉电、硬件复位和任何CMD0复位后保持,可读
R/W/C_P: 在掉电和硬件复位清除值后(值不被CMD0复位清除)可写,可读
R/W/E_P: 可多次写,其值在掉电、硬件复位和任何CMD0复位后复位,可读
W/E_P: 可多次写,其值在掉电、硬件复位和任何CMD0复位后复位,不可读
2.8.4 RCA
可写的16-bit 设备相对地址(RCA)寄存器,载有在设备识别期间主机分配的设备地址。此地址用于设备识别例程之后寻址的主机-设备通讯。RCA寄存器缺省值是0x0001。值0x0000是为将所有设备以CMD7置于Stand-by状态而保留的。
2.8.5 扩展CSD
扩展CSD寄存器定义了设备属性和选定的模式。它长512字节。高320字节是属性段,定义了设备能力,不能被主机更改。低192字节是模式段,定义了设备的工作配置。这些模式可以被主机通过SWITCH命令改变。具体寄存器略
3. 关键结构
3.1 struct sdhci_host
/* Data set by hardware interface driver */
const char *hw_name; /* Hardware bus name */
// 癖好,可以理解为硬件sdhci controller和标准sdhci规范不符合的地方
unsigned int quirks; /* Deviations from spec. */
unsigned int quirks2; /* More deviations from spec. */
// sdhci的中断
int irq; /* Device IRQ */
// sdhci寄存器的基地址
void __iomem *ioaddr; /* Mapped address */
phys_addr_t mapbase; /* physical address base */
char *bounce_buffer; /* For packing SDMA reads/writes */
dma_addr_t bounce_addr;
unsigned int bounce_buffer_size;
// 底层硬件的操作接口
const struct sdhci_ops *ops; /* Low level hw interface */
// struct mmc_host,用于注册到mmc subsystem中
/* Internal data */
struct mmc_host *mmc; /* MMC structure */
struct mmc_host_ops mmc_host_ops; /* MMC host ops */
u64 dma_mask; /* custom DMA mask */
spinlock_t lock; /* Mutex */
int flags; /* Host attributes */
unsigned int version; /* SDHCI spec. version */
// 该sdhci支持的最大时钟频率
unsigned int max_clk; /* Max possible freq (MHz) */
// 超时频率
unsigned int timeout_clk; /* Timeout freq (KHz) */
// 当前倍频值
unsigned int clk_mul; /* Clock Muliplier value */
// 当前工作频率
unsigned int clock; /* Current clock (MHz) */
// 当前工作电压
u8 pwr; /* Current voltage */
// 是否支持某些状态
bool runtime_suspended; /* Host is runtime suspended */
bool bus_on; /* Bus power prevents runtime suspend */
bool preset_enabled; /* Preset is enabled */
bool pending_reset; /* Cmd/data reset is pending */
bool irq_wake_enabled; /* IRQ wakeup is enabled */
bool v4_mode; /* Host Version 4 Enable */
bool use_external_dma; /* Host selects to use external DMA */
bool always_defer_done; /* Always defer to complete requests */
// 正在处理的请求,允许同一时间存在一个命令请求和数据命令请求
struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */
// 当前的命令
struct mmc_command *cmd; /* Current command */
struct mmc_command *data_cmd; /* Current data command */
// 推迟的命令请求
struct mmc_command *deferred_cmd; /* Deferred command */
// 当前的数据请求
struct mmc_data *data; /* Current data request */
// 表示在CMD处理完成前,data已经处理完成
unsigned int data_early:1; /* Data finished before cmd */
/* 删除部分 */
// 工作队列,基本上所有事情都在工作队列上实现
struct workqueue_struct *complete_wq; /* Request completion wq */
struct work_struct complete_work; /* Request completion work */
// 两个用于超时的定时器
struct timer_list timer; /* Timer for timeouts */
struct timer_list data_timer; /* Timer for data timeouts */
// 表示该sdhci controller的属性
u32 caps; /* CAPABILITY_0 */
u32 caps1; /* CAPABILITY_1 */
bool read_caps; /* Capability flags have been read */
bool sdhci_core_to_disable_vqmmc; /* sdhci core can disable vqmmc */
// 在该sdhci controller上可用的ocr值(代表了其可用电压)
unsigned int ocr_avail_sdio; /* OCR bit masks */
unsigned int ocr_avail_sd;
unsigned int ocr_avail_mmc;
u32 ocr_mask; /* available voltages */
unsigned timing; /* Current timing */
u32 thread_isr;
/* cached registers */
u32 ier;
bool cqe_on; /* CQE is operating */
u32 cqe_ier; /* CQE interrupt mask */
u32 cqe_err_ier; /* CQE error interrupt mask */
wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */
unsigned int tuning_done; /* Condition flag set when CMD19 succeeds */
unsigned int tuning_count; /* Timer count for re-tuning */
unsigned int tuning_mode; /* Re-tuning mode supported by host */
unsigned int tuning_err; /* Error code for re-tuning */
/* Delay (ms) between tuning commands */
int tuning_delay;
int tuning_loop_count;
/* Host SDMA buffer boundary. */
u32 sdma_boundary;
/* Host ADMA table count */
u32 adma_table_cnt;
u64 data_timeout;
unsigned long private[] ____cacheline_aligned;
};
3.2 struct sdhci_ops
sdhci core只是提供了一些接口和符合mmc core的操作集方法给对应的host driver使用。由于各个host的硬件有所差异,所以实际和硬件交互的驱动部分还是在host driver中实现。
所以sdhci core要求host提供标准的访问硬件的一些方法。而这些方法就被定义在了struct sdhci_ops结构体内部。
struct sdhci_ops {
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
// 表示host另外提供了一套访问寄存器的方法,
// 没有定义的话,则说明使用通用的读写寄存器的方法
u32 (*read_l)(struct sdhci_host *host, int reg);
u16 (*read_w)(struct sdhci_host *host, int reg);
u8 (*read_b)(struct sdhci_host *host, int reg);
void (*write_l)(struct sdhci_host *host, u32 val, int reg);
void (*write_w)(struct sdhci_host *host, u16 val, int reg);
void (*write_b)(struct sdhci_host *host, u8 val, int reg);
#endif
// 设置时钟频率,电源
void (*set_clock)(struct sdhci_host *host, unsigned int clock);
void (*set_power)(struct sdhci_host *host, unsigned char mode,
unsigned short vdd);
// 平台host的中断回调
u32 (*irq)(struct sdhci_host *host, u32 intmask);
// 设置,使能dma
int (*set_dma_mask)(struct sdhci_host *host);
int (*enable_dma)(struct sdhci_host *host);
// 获取支持的最大/最小时钟频率
unsigned int (*get_max_clock)(struct sdhci_host *host);
unsigned int (*get_min_clock)(struct sdhci_host *host);
/* get_timeout_clock should return clk rate in unit of Hz */
unsigned int (*get_timeout_clock)(struct sdhci_host *host);
unsigned int (*get_max_timeout_count)(struct sdhci_host *host);
void (*set_timeout)(struct sdhci_host *host,
struct mmc_command *cmd);
void (*set_bus_width)(struct sdhci_host *host, int width);
void (*platform_send_init_74_clocks)(struct sdhci_host *host,
u8 power_mode);
unsigned int (*get_ro)(struct sdhci_host *host);
// 软复位
void (*reset)(struct sdhci_host *host, u8 mask);
int (*platform_execute_tuning)(struct sdhci_host *host, u32 opcode);
void (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
// 硬复位
void (*hw_reset)(struct sdhci_host *host);
void (*adma_workaround)(struct sdhci_host *host, u32 intmask);
void (*card_event)(struct sdhci_host *host);
// 电压切换
void (*voltage_switch)(struct sdhci_host *host);
void (*adma_write_desc)(struct sdhci_host *host, void **desc,
dma_addr_t addr, int len, unsigned int cmd);
void (*copy_to_bounce_buffer)(struct sdhci_host *host,
struct mmc_data *data,
unsigned int length);
void (*request_done)(struct sdhci_host *host,
struct mmc_request *mrq);
void (*dump_vendor_regs)(struct sdhci_host *host);
};
3.3 struct mmc_host
struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器。
struct mmc_host {
struct device *parent; // 对应的host controller的device
struct device class_dev; // mmc_host的device结构体,会挂在class/mmc_host下
int index; // 该host的索引号
const struct mmc_host_ops *ops; // 该host的操作集,由host controller设置
struct mmc_pwrseq *pwrseq;
unsigned int f_min; // 该host支持的最低频率
unsigned int f_max; // 该host支持的最大频率
unsigned int f_init; // 该host初始化时使用的频率
/*
* OCR(Operating Conditions Register)
* 是MMC/SD/SDIO卡的一个32-bit的寄存器,
* 其中有些bit指明了该卡的操作电压。
* MMC host在驱动这些卡的时候,
* 需要和Host自身所支持的电压范围匹配之后,
* 才能正常操作,这就是ocr_avail的存在意义
*/
u32 ocr_avail; // 该host可用的ocr值(电压相关)
u32 ocr_avail_sdio; /* SDIO-specific OCR */
u32 ocr_avail_sd; /* SD-specific OCR */
u32 ocr_avail_mmc; /* MMC-specific OCR */
struct wakeup_source *ws; /* Enable consume of uevents */
u32 max_current_330; // 3.3V时的最大电流
u32 max_current_300; // 3.0V时的最大电流
u32 max_current_180; // 1.8V时的最大电流
// host属性
u32 caps; /* Host capabilities */
u32 caps2; /* More host capabilities */
int fixed_drv_type; /* fixed driver type for non-removable media */
// 电源管理属性
mmc_pm_flag_t pm_caps; /* supported pm features */
/* host specific block data */
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
unsigned short max_segs; /* see blk_queue_max_segments */
unsigned short unused;
unsigned int max_req_size; /* maximum number of bytes in one req */
unsigned int max_blk_size; /* maximum size of one mmc block */
unsigned int max_blk_count; /* maximum number of blocks in one req */
unsigned int max_busy_timeout; /* max busy timeout in ms */
/* private data */
spinlock_t lock; /* lock for claim and bus ops */
// 用于保存MMC bus的当前配置
struct mmc_ios ios; /* current io bus settings */
/* group bitfields together to minimize padding */
unsigned int use_spi_crc:1;
// host是否已经被占用
unsigned int claimed:1; /* host exclusively claimed */
// host的bus是否处于激活状态
unsigned int bus_dead:1; /* bus has been released */
unsigned int doing_init_tune:1; /* initial tuning in progress */
unsigned int can_retune:1; /* re-tuning can be used */
unsigned int doing_retune:1; /* re-tuning in progress */
unsigned int retune_now:1; /* do re-tuning at next req */
unsigned int retune_paused:1; /* re-tuning is temporarily disabled */
unsigned int use_blk_mq:1; /* use blk-mq */
unsigned int retune_crc_disable:1; /* don't trigger retune upon crc */
unsigned int can_dma_map_merge:1; /* merging can be used */
// 禁止rescan的标识,禁止搜索card
int rescan_disable; /* disable card detection */
// 是否已经rescan过的标识,对应不可移除的设备只能rescan一次
int rescan_entered; /* used with nonremovable devices */
int need_retune; /* re-tuning is needed */
int hold_retune; /* hold off re-tuning */
unsigned int retune_period; /* re-tuning period in secs */
struct timer_list retune_timer; /* for periodic re-tuning */
bool trigger_card_event; /* card_event necessary */
// 和该host绑定在一起的card
struct mmc_card *card; /* device attached to this host */
wait_queue_head_t wq;
// 该host的占有者进程
struct mmc_ctx *claimer; /* context that has host claimed */
// 占有者进程对该host的占用计数
int claim_cnt; /* "claim" nesting count */
struct mmc_ctx default_ctx; /* default context */
// 检测卡槽变化的工作
struct delayed_work detect;
// 需要检测卡槽变化的标识
int detect_change; /* card detect flag */
// 卡槽的结构体
struct mmc_slot slot;
// host的mmc总线的操作集
const struct mmc_bus_ops *bus_ops; /* current bus driver */
unsigned int bus_refs; /* reference counter */
unsigned int sdio_irqs;
struct task_struct *sdio_irq_thread;
struct delayed_work sdio_irq_work;
bool sdio_irq_pending;
atomic_t sdio_irq_thread_abort;
mmc_pm_flag_t pm_flags; /* requested pm features */
struct led_trigger *led; /* activity led */
#ifdef CONFIG_REGULATOR
bool regulator_enabled; /* regulator state */
#endif
struct mmc_supply supply;
struct dentry *debugfs_root;
/* Ongoing data transfer that allows commands during transfer */
struct mmc_request *ongoing_mrq;
unsigned int actual_clock; /* Actual HC clock rate */
unsigned int slotno; /* used for sdio acpi binding */
int dsr_req; /* DSR value is valid */
u32 dsr; /* optional driver stage (DSR) value */
/* Command Queue Engine (CQE) support */
const struct mmc_cqe_ops *cqe_ops;
void *cqe_private;
int cqe_qdepth;
bool cqe_enabled;
bool cqe_on;
/* Host Software Queue support */
bool hsq_enabled;
unsigned long private[] ____cacheline_aligned;
};
3.4 struct mmc_host_ops
mmc core将host需要提供的一些操作方法封装成struct mmc_host_ops。mmc core主模块的很多接口都是基于这里面的操作方法来实现的,通过这些方法来操作host硬件达到对应的目的。所以struct mmc_host_ops也是host controller driver需要实现的核心部分
struct mmc_host_ops {
/*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
* pre_req() must always be followed by a post_req().
* To undo a call made to pre_req(), call post_req() with
* a nonzero err condition.
*/
// post_req和pre_req是为了实现异步请求处理而设置的
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,
// 可以先准备另外一个异步请求而不必等待
void (*post_req)(struct mmc_host *host, struct mmc_request *req,
int err);
void (*pre_req)(struct mmc_host *host, struct mmc_request *req);
// host处理mmc请求的方法,在mmc_start_request中会调用
void (*request)(struct mmc_host *host, struct mmc_request *req);
/* Submit one request to host in atomic context. */
int (*request_atomic)(struct mmc_host *host,
struct mmc_request *req);
/*
* 避免过于频繁或在“快速路径”中调用接下来的三个函数,因为底层控制器可能以昂贵
* 和/或缓慢的方式实现它们。还需要注意的是,这些函数可能会进入睡眠状态,
* 所以不要在原子上下文中调用它们!
*/
// 设置host的总线的io setting
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
// 获取host上的card的读写属性
int (*get_ro)(struct mmc_host *host);
// 检测host的卡槽中card的插入状态
int (*get_cd)(struct mmc_host *host);
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
/* Mandatory callback when using MMC_CAP2_SDIO_IRQ_NOTHREAD. */
void (*ack_sdio_irq)(struct mmc_host *host);
// 初始化card的方法
void (*init_card)(struct mmc_host *host, struct mmc_card *card);
// 切换信号电压的方法
int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);
/* Check if the card is pulling dat[0:3] low */
// 用于检测card是否处于busy状态
int (*card_busy)(struct mmc_host *host);
/* The tuning command opcode value is different for SD and eMMC cards */
int (*execute_tuning)(struct mmc_host *host, u32 opcode);
/* Prepare HS400 target operating frequency depending host driver */
int (*prepare_hs400_tuning)(struct mmc_host *host, struct mmc_ios *ios);
/* Prepare switch to DDR during the HS400 init sequence */
int (*hs400_prepare_ddr)(struct mmc_host *host);
/* Prepare for switching from HS400 to HS200 */
void (*hs400_downgrade)(struct mmc_host *host);
/* Complete selection of HS400 */
void (*hs400_complete)(struct mmc_host *host);
/* Prepare enhanced strobe depending host driver */
void (*hs400_enhanced_strobe)(struct mmc_host *host,
struct mmc_ios *ios);
int (*select_drive_strength)(struct mmc_card *card,
unsigned int max_dtr, int host_drv,
int card_drv, int *drv_type);
/* Reset the eMMC card via RST_n */
void (*hw_reset)(struct mmc_host *host);
void (*card_event)(struct mmc_host *host);
/*
* Optional callback to support controllers with HW issues for multiple
* I/O. Returns the number of supported blocks for the request.
*/
int (*multi_io_quirk)(struct mmc_card *card,
unsigned int direction, int blk_size);
};
3.5 struct mmc_card
struct mmc_card是mmc core由mmc设备抽象出来的card设备的结构体,用于代表一个mmc设备。
struct mmc_card {
// 该mmc_card所属host
struct mmc_host *host; /* the host this device belongs to */
struct device dev; /* the device */
u32 ocr; /* the current OCR setting */
// 该card的RCA地址
unsigned int rca; /* relative card address of device */
unsigned int type; /* card type */
#define MMC_TYPE_MMC 0 /* MMC card */
#define MMC_TYPE_SD 1 /* SD card */
#define MMC_TYPE_SDIO 2 /* SDIO card */
#define MMC_TYPE_SD_COMBO 3 /* SD combo (IO+mem) card */
unsigned int state; /* (our) card state */
unsigned int quirks; /* card quirks */
unsigned int quirk_max_rate; /* max rate set by quirks */
bool reenable_cmdq; /* Re-enable Command Queue */
unsigned int erase_size; /* erase size in sectors */
unsigned int erase_shift; /* if erase unit is power 2 */
unsigned int pref_erase; /* in sectors */
unsigned int eg_boundary; /* don't cross erase-group boundaries */
unsigned int erase_arg; /* erase / trim / discard */
u8 erased_byte; /* value of erased bytes */
// 原始的各寄存器的值
u32 raw_cid[4]; /* raw card CID */
u32 raw_csd[4]; /* raw card CSD */
u32 raw_scr[2]; /* raw card SCR */
u32 raw_ssr[16]; /* raw card SSR */
// 从cid寄存器的值解析出来的信息
struct mmc_cid cid; /* card identification */
// 从csd寄存器的值解析出来的信息
struct mmc_csd csd; /* card specific */
// 从ext_csd寄存器的值解析出来的信息
struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */
// 外部sdcard的信息
struct sd_scr scr; /* extra SD information */
// 更多关于sd card的信息
struct sd_ssr ssr; /* yet more SD information */
// sd的切换属性
struct sd_switch_caps sw_caps; /* switch (CMD6) caps */
unsigned int sdio_funcs; /* number of SDIO functions */
atomic_t sdio_funcs_probed; /* number of probed SDIO funcs */
struct sdio_cccr cccr; /* common card info */
struct sdio_cis cis; /* common tuple info */
struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */
struct sdio_func *sdio_single_irq; /* SDIO function when only one IRQ active */
u8 major_rev; /* major revision number */
u8 minor_rev; /* minor revision number */
unsigned num_info; /* number of info strings */
const char **info; /* info strings */
struct sdio_func_tuple *tuples; /* unknown common tuples */
unsigned int sd_bus_speed; /* Bus Speed Mode set for the card */
unsigned int mmc_avail_type; /* supported device type by both host and card */
unsigned int drive_strength; /* for UHS-I, HS200 or HS400 */
struct dentry *debugfs_root;
// 物理分区
struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */
// 分区数量
unsigned int nr_parts;
unsigned int bouncesz; /* Bounce buffer size */
struct workqueue_struct *complete_wq; /* Private workqueue */
};
3.6 struct mmc_request
struct mmc_request是mmc core向host controller发起命令请求的处理单位。其包含了要传输的命令和数据。
struct mmc_request {
// 设置块数量的命令(多块通信)
struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */
// 要传输的命令
struct mmc_command *cmd;
// 要传输的数据
struct mmc_data *data;
// 结束命令
struct mmc_command *stop;
struct completion completion;
struct completion cmd_completion;
// 传输结束后的回调函数
void (*done)(struct mmc_request *);/* completion function */
/*
* 通知上层(例如mmc块驱动程序)由于与mmc_request相关的错误需要恢复。目前仅由CQE使用。
*/
void (*recovery_notifier)(struct mmc_request *);
struct mmc_host *host;
/* Allow other commands during this ongoing data transfer or busy wait */
bool cap_cmd_during_tfr;
int tag;
};
3.7 struct mmc_command
struct mmc_command {
// 命令的操作码,如MMC_GO_IDLE_STATE、MMC_SEND_OP_COND等等
u32 opcode;
// 命令的参数
u32 arg;
// response值
u32 resp[4];
// 期待的response的类型
unsigned int flags; /* expected response type */
// 失败时的重复尝试次数
unsigned int retries; /* max number of retries */
int error; /* command error */
/*
* Standard errno values are used for errors, but some have specific
* meaning in the MMC layer:
*
* ETIMEDOUT Card took too long to respond
* EILSEQ Basic format problem with the received or sent data
* (e.g. CRC check failed, incorrect opcode in response
* or bad end bit)
* EINVAL Request cannot be performed because of restrictions
* in hardware and/or the driver
* ENOMEDIUM Host can determine that the slot is empty and is
* actively failing requests
*/
unsigned int busy_timeout; /* busy detect timeout in ms */
struct mmc_data *data; /* data segment associated with cmd */
struct mmc_request *mrq; /* associated request */
};
3.8 struct mmc_data
mmc core用struct mmc_data来表示一个命令包
struct mmc_data {
// 超时时间,以ns为单位
unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */
// 超时时间,以clock为单位
unsigned int timeout_clks; /* data timeout (in clocks) */
// 块大小
unsigned int blksz; /* data block size */
// 块数量
unsigned int blocks; /* number of blocks */
unsigned int blk_addr; /* block address */
int error; /* data error */
// 传输标识
unsigned int flags;
unsigned int bytes_xfered;
struct mmc_command *stop; /* stop command */
// 该命令关联到哪个request
struct mmc_request *mrq; /* associated request */
unsigned int sg_len; /* size of scatter list */
int sg_count; /* mapped sg entries */
struct scatterlist *sg; /* I/O scatter list */
s32 host_cookie; /* host private data */
};
4. 注册
注册流程按照mmc_host和mmc_core两层分别分析,从mmc_host层的主机控制器的probe开始进行分析。mmc控制器的注册流程如下图所示:
4.1 mmc_host层
host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线。
host controller会控制命令线、数据线和时钟线,从而实现mmc总线上的通讯。 上层发送mmc请求时,就是通过host controller产生对应的mmc通讯时序,下发至mmc设备,与mmc设备通讯。注意,host的部分主要是实现card的通讯和检测,不去负责card的具体功能。
平台实现mmc驱动,核心内容就是要实现host controller的驱动。在mmc subsystem中,把host controller的驱动都放在了/drivers/mmc/host目录下。
一个host driver要做的事情如下:
申请mmc_host设置mmc_host的成员,包括操作集等等完成host controller的初始化(哪些方面的初始化)注册mmc_host,注册之后会去搜索card
再次说明:应实际的card设备(emmc card、sd card),mmc core部分已经实现了其协议中初始化的部分,而其card设备具体功能的实现则是在card模块中进行实现。host驱动只负责card的通讯和检测等等,并不会去实现card的具体功能。
这里我们主要以sdhci类host进行源码分析(SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的设计标准,其寄存器偏移以及意义都有一定的规范,并且提供了对应的驱动程序,方便vendor进行host controller的开发)
I. sdhci-xxxx
sdhci-XXXX.c:我们将符合sdhci标准的host称之为sdhci类host。像/drivers/mmc/host目录一些命名为“sdhci-XXXX.c”(sdhci-pltfm除外)的驱动都表示对应的host是sdhci类host。例如我目前使用的平台mmc host设计就使用了sdhci的标准,因此符合的就属于sdhci类host,具体代码对应sdhci-cadence.c。该部分代码通过识别并解析设备树进行probe然后执行sdhci中相关接口(主要是寄存器读写等,填充struct sdhci_ops)进行注册,如下为probe函数分析
<code>static int sdhci_cdns_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
const struct sdhci_pltfm_data *data;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_cdns_priv *priv;
struct clk *clk;
unsigned int nr_phy_params;
int ret;
struct device *dev = &pdev->dev;
static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
// 该结构体指向一个 const struct sdhci_ops *ops;
// 定义了实际上平台操作控制器的实际寄存器读写方法
data = &sdhci_cdns_pltfm_data;
// 从设备树中获取对应phy的可能的参数数量,见下图
nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
// 该函数在pltfm层重点分析
host = sdhci_pltfm_init(pdev, data,
struct_size(priv, phy_params, nr_phy_params));
if (IS_ERR(host)) {
ret = PTR_ERR(host);
goto disable_clk;
}
// 控制器硬件层面的复位和初始化
sdhci_mmc_host_reset_init(host);
// 获取host的私有数据
pltfm_host = sdhci_priv(host);
pltfm_host->clk = clk;
// pltfm的私有数据
priv = sdhci_pltfm_priv(pltfm_host);
priv->nr_phy_params = nr_phy_params;
// sdhci寄存器基址赋值,该ioaddr在sdhci_pltfm_init中获取的
priv->hrs_addr = host->ioaddr;
// 这里实际上说明不支持HS400ES模式
priv->enhanced_strobe = false;
host->ioaddr += SDHCI_CDNS_SRS_BASE;
// HS400和HS400ES模式的切换函数接口
host->mmc_host_ops.hs400_enhanced_strobe =
sdhci_cdns_hs400_enhanced_strobe;
// 使能V4模式
sdhci_enable_v4_mode(host);
// 获取控制器版本和特性
__sdhci_read_caps(host, &version, NULL, NULL);
// 从设备树中获取部分属性,比如总线宽度,是否需要cd(non-removable),
// 以及其他的一些标明该路控制器的属性比如no-mmc/no-sdio/no-sd
// 如果设备树中标明了cd脚,还会申请cd gpio
ret = mmc_of_parse(host->mmc);
if (ret)
goto free;
// 上面获取数量,这里进行解析,如果没有就拉倒
sdhci_cdns_phy_param_parse(dev->of_node, priv);
// 对phy进行相关初始化,如果上面有参数配置的话
ret = sdhci_cdns_phy_init(priv);
if (ret)
goto free;
// 下面详细分析
ret = sdhci_add_host(host);
if (ret)
goto free;
return 0;
free:
sdhci_pltfm_free(pdev);
disable_clk:
clk_disable_unprepare(clk);
return ret;
}
II. sdhci_pltfm
sdhci_pltfm_host:虽然平台host符合sdhci标准,但是有些内容是由平台决定,但是又是sdhci core需要的,这部分内容被封装到sdhci_pltfm_data中。相应的,平台设备的host可以通过sdhci_pltfm_host来实现和sdhci_host的关联,也就是一个中间层。对应代码:drivers/mmc/host/sdhci-pltfm.c
<code>struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
const struct sdhci_pltfm_data *pdata,
size_t priv_size)
{
struct sdhci_host *host;
void __iomem *ioaddr;
int irq, ret;
// 解析设备树中寄存器基址并直接ioremap
ioaddr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ioaddr)) {
ret = PTR_ERR(ioaddr);
goto err;
}
// 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto err;
}
// 下面详细分析
host = sdhci_alloc_host(&pdev->dev,
sizeof(struct sdhci_pltfm_host) + priv_size);
if (IS_ERR(host)) {
ret = PTR_ERR(host);
goto err;
}
// 给申请到的host赋值
host->ioaddr = ioaddr;
host->irq = irq;
host->hw_name = dev_name(&pdev->dev);
if (pdata && pdata->ops)
host->ops = pdata->ops;
else
host->ops = &sdhci_pltfm_ops;
if (pdata) {
host->quirks = pdata->quirks;
host->quirks2 = pdata->quirks2;
}
platform_set_drvdata(pdev, host);
return host;
err:
dev_err(&pdev->dev, "%s failed %d\n", __func__, ret);
return ERR_PTR(ret);
}
III. sdhci
sdhci_host:对于sdhci类host(也就是符合sdhci标准)的host来说,直接通过sdhci core来实现host controller的使用。而sdhci core会为对应的host抽象出对应的struct sdhci_host结构体进行管理。对应代码:drivers/mmc/host/sdhci.c
a. sdhci_alloc_host
sdhci_alloc_host为host driver分配一个sdhci_host和mmc_host,并实现其初始化,以及sdhci_host和mmc_host的关联
struct sdhci_host *sdhci_alloc_host(struct device *dev,
size_t priv_size)
{
// 以下变量要注意区分
// host是指要注册的sdhci host
// mmc是指要注册到mmc subsystem的host,封装在sdhci host中
struct mmc_host *mmc;
struct sdhci_host *host;
WARN_ON(dev == NULL);
// 分配mmc_host的同时也分配了sizeof(struct sdhci_host) +
// priv_size的私有数据空间,这部分就是作为sdhci_host及其私有数据使用的
// 该函数在mmc_core host中在详细分析
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
if (!mmc)
return ERR_PTR(-ENOMEM);
// 将sdhci_host作为mmc_host的私有数据,
// sdhci_host = mmc_host->private
host = mmc_priv(mmc);
host->mmc = mmc;
// sdhci_ops见下图
host->mmc_host_ops = sdhci_ops;
mmc->ops = &host->mmc_host_ops;
// 默认先使用3.3V电压
host->flags = SDHCI_SIGNALING_330;
// CQE: Command Queueing Engine,关于CQE的一些配置
host->cqe_ier = SDHCI_CQE_INT_MASK;
host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;
// tuning相关配置,最大tuning次数:40次
host->tuning_delay = -1;
host->tuning_loop_count = MAX_TUNING_LOOP;
host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG;
/*
* The DMA table descriptor count is calculated as the maximum
* number of segments times 2, to allow for an alignment
* descriptor for each segment, plus 1 for a nop end descriptor.
*/
host->adma_table_cnt = SDHCI_MAX_SEGS * 2 + 1;
return host;
}
b. sdhci_add_host
在add_host之前,已经完成的底层解析,并传上来的sdhci_host中包含以下信息:
sdhci的寄存器的映射过后的基地址(sdhci_host->ioaddr)sdhci的癖好quirks、quirks2(sdhci_host->quirks,sdhci_host->quirks2)sdhci的中断号(sdhci_host->irq)host提供给sdhci core用来操作硬件的操作集(sdhci_host->ops)
自此,可以正式完成host的注册,完成以下过程:sdhci host复位:读取该host的sdhci的信息(从sdhci相关寄存器中读取)并设置sdhci_host相关成员中断的注册sdhci host初始化:调用sdhci_init注册mmc_host到mmc core中:调用mmc_add_host使能card插入状态的检测
<code>int sdhci_add_host(struct sdhci_host *host)
{
int ret;
// 该函数源码巨长,因此不贴源码,只列出该函数实现的部分功能:
// 1. 获取sdhci controller支持的属性- sdhci_read_caps
// |--1.1 执行sdhci_do_reset,如果host不需要复位则直接判断cd
// |--1.2 设置v4模式,读取host版本,读取caps
// 2. 设置sdhci_host->flags中和DMA相关的flag和部分DMA配置
// 3. 获取sdhci controller支持的最大频率以及倍频
// 4. 根据quirks或caps做的一堆标志处理(不详细分析了)
// 5. 设置各个电压下的最大电流值(max_current_330/300/180)
// 6. 设置可用的ocr(ocr_avail* [mmc][sd][sdio])
// 7. 设置max_segs/seg_size/blk_size/blk_count
ret = sdhci_setup_host(host);
if (ret)
return ret;
// 源码见下
ret = __sdhci_add_host(host);
if (ret)
goto cleanup;
return 0;
cleanup:
sdhci_cleanup_host(host);
return ret;
}
int __sdhci_add_host(struct sdhci_host *host)
{
unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
struct mmc_host *mmc = host->mmc;
int ret;
// 看控制器支不支持CQE,我手里的平台是不支持的,就不详细分析了
if ((mmc->caps2 & MMC_CAP2_CQE) &&
(host->quirks & SDHCI_QUIRK_BROKEN_CQE)) {
mmc->caps2 &= ~MMC_CAP2_CQE;
mmc->cqe_ops = NULL;
}
// 完成request的工作队列创建
host->complete_wq = alloc_workqueue("sdhci", flags, 0);
if (!host->complete_wq)
return -ENOMEM;
// 初始化工作线程,sdhci_request_done
INIT_WORK(&host->complete_work, sdhci_complete_work);
// 创建两个定时器
timer_setup(&host->timer, sdhci_timeout_timer, 0);
timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0);
// Buffer Read Ready interrupt 等待队列创建
init_waitqueue_head(&host->buf_ready_int);
// 做了一次复位,在使能v4模式,然后使能host的硬件中断
sdhci_init(host, 0);
// 中断处理与中断线程和中断号绑定,其中sdhci_irq返回IRQ_WAKE_THREAD
// 时唤醒sdhci_thread_irq,类似于中断上半部和底半部
ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq,
IRQF_SHARED, mmc_hostname(mmc), host);
if (ret) {
pr_err("%s: Failed to request IRQ %d: %d\n",
mmc_hostname(mmc), host->irq, ret);
goto unwq;
}
/* 删除sdhci_led部分 */
// 在mmc_core host中在详细分析
ret = mmc_add_host(mmc);
if (ret)
goto unled;
// 走到这里基本上已经彻底完成控制器的初始化了,见下图
pr_info("%s: SDHCI controller on %s [%s] using %s\n",
mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
host->use_external_dma ? "External DMA" :
(host->flags & SDHCI_USE_ADMA) ?
(host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" :
(host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
// 使能控制器卡插拔中断(如果不支持gpio cd的话)
sdhci_enable_card_detection(host);
return 0;
/* 删除失败处理:unled,unirq,unwq */
return ret;
}
完成控制器注册和初始化
4.2 mmc_core层
I. host
mmc core通过struct mmc_host来管理host。不管是什么类型的host,最终都是要实现出对应的mmc_host并注册到mmc core中交由mmc子系统进行管理。对应代码drivers/mmc/core/host.c。为底层host controller driver实现mmc host的申请以及注册的API等等,以及host相关属性的实现。其中和注册相关的两个接口如下:
a. mmc_alloc_host
底层host controller驱动调用,用来分配一个struct mmc_host结构体,将其于mmc_host_class关联,并且做部分初始化操作
主要工作:
分配内存空间初始化其class device(对应/sys/class/mmc*节点)初始化detect工作队列(也就是检测工作)为mmc_rescan初始化 max_segs、max_req_size等
<code>/**
* mmc_alloc_host - initialise the per-host structure.
* @extra: sizeof private data structure
* @dev: pointer to host device model structure
*
* Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int index;
struct mmc_host *host;
int alias_id, min_idx, max_idx;
// 申请mmc_host的空间,连带上主机控制器层中要用的私有数据大小
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
/* scanning will be enabled when we're ready */
// 这里禁用rescan,先不允许扫卡
host->rescan_disable = 1;
// 从设备树中解析mmc* 以获取索引(这个索引是按照设备树中mmc的顺序来的)
alias_id = of_alias_get_id(dev->of_node, "mmc");
if (alias_id >= 0) {
index = alias_id;
} else {
min_idx = mmc_first_nonreserved_index();
max_idx = 0;
index = ida_simple_get(&mmc_host_ida, min_idx, max_idx, GFP_KERNEL);
if (index < 0) {
kfree(host);
return NULL;
}
}
host->index = index;
// 根据获取的索引设置mmc名
dev_set_name(&host->class_dev, "mmc%d", host->index);
host->ws = wakeup_source_register(NULL, dev_name(&host->class_dev));
// 这个dev实际上是platform传下来的&pdev->dev
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
device_enable_async_suspend(&host->class_dev);
// 如果设备树中有cd或者ro引脚,这里会将对应信息填充到ctx中
// 实际我手里的平台未使用该方案,不详细分析
if (mmc_gpio_alloc(host)) {
put_device(&host->class_dev);
return NULL;
}
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
// 扫卡用工作队列,后续调度host->detect来检测是否有card插入
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);
timer_setup(&host->retune_timer, mmc_retune_timer, 0);
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_segs = 1;
host->max_seg_size = PAGE_SIZE;
host->max_req_size = PAGE_SIZE;
host->max_blk_size = 512;
host->max_blk_count = PAGE_SIZE / 512;
host->fixed_drv_type = -EINVAL;
host->ios.power_delay_ms = 10;
host->ios.power_mode = MMC_POWER_UNDEFINED;
return host;
}
b. mmc_add_host
底层host controller驱动调用,注册mmc_host到设备驱动中,添加到sys类下面,并设置相应的debug目录。然后启动mmc_host。
主要工作:
将mmc_host的class_dev添加到设备驱动模型中,在sysfs中生成相应的节点初始化mmc_host相关的debug目录调用mmc_start_host启动host
int mmc_add_host(struct mmc_host *host)
{
int err;
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
// 通过device_add将mmc_host->class_dev添加到设备驱动模型中,
// 在sys下生成相应节点
err = device_add(&host->class_dev);
if (err)
return err;
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
// 启动host,卡识别
mmc_start_host(host);
return 0;
}
II. core
mmc core主模块是mmc core的实现核心,对应代码位置drivers/mmc/core/core.c。其中mmc core初始化包括注册mmc bus、mm host class等等
a. mmc_init
负责初始化整个mmc core。主要工作:1.注册mmc bus;2.注册mmc host class;3.注册sdio bus
static int __init mmc_init(void)
{
int ret;
// 注册mmc bus 见下图,这条bus称之为mmc_bus,节点:/sys/bus/mmc
ret = mmc_register_bus();
if (ret)
return ret;
// 注册mmc_host class
ret = mmc_register_host_class();
if (ret)
goto unregister_bus;
// 注册sdio bus,这条bus称之为sdio_bus,节点:/sys/bus/sdio
ret = sdio_register_bus();
if (ret)
goto unregister_host_class;
return 0;
unregister_host_class:
mmc_unregister_host_class();
unregister_bus:
mmc_unregister_bus();
return ret;
}
mmc_bus这部分代码在drivers/mmc/core/bus.c中,初始化这里不详细分析
b. mmc_start_host
当底层host controller调用mmc_add_host来注册host时,在mmc_add_host中就会调用mmc_start_host来启动一个host了。
<code>void mmc_start_host(struct mmc_host *host)
{
host->f_init = max(min(freqs[0], host->f_max), host->f_min);
// 设置rescan_disable标志为0,说明已经可以进行card检测了
host->rescan_disable = 0;
// 如果mmc属性设置了MMC_CAP2_NO_PRESCAN_POWERUP,
// 也就是在rescan前不需要进行power up,否则就需要
if (!(host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)) {
// 因为上电操作涉及到对host的使用和设置,需要先占用host
// 关于host_claim下面详细解析一下
mmc_claim_host(host);
// 为MMC设备供电。这是一个两阶段的过程。
// 首先,我们在不运行时钟的情况下为卡启用电源
// 然后稍等片刻,让电源稳定。最后启用总线驱动程序和给时钟到卡
// 在电源稳定之前,我们必须不启用时钟
mmc_power_up(host, host->ocr_avail);
// 解除占用
mmc_release_host(host);
}
// 如果是设备树标明的gpio cd则去申请该gpio中断
mmc_gpiod_request_cd_irq(host);
// 调用mmc_detect_change检测card变化
_mmc_detect_change(host, 0, false);
}
c. mmc_claim/release_host
这2个接口的配对使用,目的是为了独占式的使用host,凡是需要独占使用host的场景都可以调用这两个接口,比如扫卡/识别卡阶段,host上下电阶段,卡的suspend/ resume
int __mmc_claim_host(struct mmc_host *host, struct mmc_ctx *ctx,
atomic_t *abort)
{
struct task_struct *task = ctx ? NULL : current;
// 创建等待队列成员
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
int stop;
bool pm = false;
might_sleep();
// 加入到等待队列中
add_wait_queue(&host->wq, &wait);
// 加锁保护
spin_lock_irqsave(&host->lock, flags);
while (1) {
// 该进程不可中断唤醒
set_current_state(TASK_UNINTERRUPTIBLE);
// 传入的abort非空?
stop = abort ? atomic_read(abort) : 0;
// 如果abort的锁读出来是0,则可以申请该host
// !host->claimed表示已经该host还没被占用,可以申请该host
// 如果上下文相同,或者没有上下文但任务相同,可以申请该host
if (stop || !host->claimed || mmc_ctx_matches(host, ctx, task))
break;
spin_unlock_irqrestore(&host->lock, flags);
// 如果能进这里,说明之前已经有申请过的了,需要等待释放wait
// 才会在调度回来,然后在进行上面的判断
schedule();
spin_lock_irqsave(&host->lock, flags);
}
set_current_state(TASK_RUNNING);
if (!stop) {
// 可以申请host的情况,该host需要被占用
host->claimed = 1;
// host-> claimer进行上下文绑定
mmc_ctx_set_claimer(host, ctx, task);
// 计数++
host->claim_cnt += 1;
// 如果是第一次占用,则执行pm相关的电源管理,这里不详细分析
if (host->claim_cnt == 1)
pm = true;
} else
// 否则则去唤醒上一次的占用
wake_up(&host->wq);
spin_unlock_irqrestore(&host->lock, flags);
// 然后将本次的等待队列中的成员删掉,完成一次循环
remove_wait_queue(&host->wq, &wait);
if (pm)
pm_runtime_get_sync(mmc_dev(host));
// 返回申请情况,返回非0说明之前想要占用该host失败了
return stop;
}
释放函数如下:
void mmc_release_host(struct mmc_host *host)
{
unsigned long flags;
// 如果没占用就释放(调用了该函数)就报警告,肯定有点问题
WARN_ON(!host->claimed);
spin_lock_irqsave(&host->lock, flags);
// 计数自减如果不是0,说明可能同一个task或者相同上下文多次占用中
if (--host->claim_cnt) {
/* Release for nested claim */
spin_unlock_irqrestore(&host->lock, flags);
} else {
// 这里说明能真的释放了
host->claimed = 0;
host->claimer->task = NULL;
host->claimer = NULL;
spin_unlock_irqrestore(&host->lock, flags);
// 唤醒等待队列,本次唤醒后,其他在等待该host的task就可以去占用了
wake_up(&host->wq);
// 电源管理相关,不分析
pm_runtime_mark_last_busy(mmc_dev(host));
if (host->caps & MMC_CAP_SYNC_RUNTIME_PM)
pm_runtime_put_sync_suspend(mmc_dev(host));
else
pm_runtime_put_autosuspend(mmc_dev(host));
}
}
d. _mmc_detect_change
到这里触发扫卡工作流程
void _mmc_detect_change(struct mmc_host *host, unsigned long delay, bool cd_irq)
{
/*
* Prevent system sleep for 5s to allow user space to consume the
* corresponding uevent. This is especially useful, when CD irq is used
* as a system wakeup, but doesn't hurt in other cases.
*/
if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL))
__pm_wakeup_event(host->ws, 5000);
host->detect_change = 1;
// 触发INIT_DELAYED_WORK(&host->detect, mmc_rescan);
// 开始扫卡流程
mmc_schedule_delayed_work(&host->detect, delay);
}
5. 扫卡(识别)
当_mmc_detect_change执行后,触发了扫卡的工作队列,并且使能了卡检测,则正式进入扫卡识别阶段,通过唤醒工作队列,执行mmc_rescan,如下图为实际设备识别emmc的流程(注:dump_stack加到mmc_add_card函数的最后)
5.1 mmc_rescan
用于检测host的卡槽状态,并对状态变化做相应的操作。有card插入时,重新扫描mmc card,mmc card rescan的方式有如下几种:
mmc card是不可移除的(如emmc),则在mmc host初始化时设置mmc host为nonremovable(仅在mmc_add_host时,调用mmc_detect_change完成一次mmc rescan,此后不再执行mmc rescan操作);mmc host支持mmc card detect功能(通过提供mmc detect中断,进行mmc card detect),此种情况在mmc card detect中断对应的中断处理接口中,调用mmc_detect_change接口,对延迟工作队列进行调度,从而调用接口mmc_rescan,完成一次mmc card的rescan;mmc host不支持mmc card detect功能,针对此情形,可以设置mmc host为poll模式。针对此种模式,在mmc_add_host执行一次mmc rescan时,在mmc rescan的最后会执行延迟1s调度该延迟工作队列,从而完成每秒执行一次mmc rescan操作。
<code>void mmc_rescan(struct work_struct *work)
{
// 通过work获取mmc_host结构体指针
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;
// 是否禁用扫卡?如果禁用则不执行
if (host->rescan_disable)
return;
/* If there is a non-removable card registered, only scan once */
// 对于不可移除(non-removable)的卡(emmc就是不可移除,
// 因此设备树中会标注有non-removable属性),scan只做一次
if (!mmc_card_is_removable(host) && host->rescan_entered)
return;
host->rescan_entered = 1;
/* 删除部分 */
// 执行mmc_host->bus_refs++,说明执行到这里该总线已经算被使用了
// 引用计数+1
mmc_bus_get(host);
/* Verify a registered card to be functional, else remove it. */
// 在首次扫卡执行时(还未执行mmc_attach_bus)时,mmc_host->bus_ops = NULL
// 再次执行时,host->bus_ops存在的话说明之前是有card插入的状态
// 即执行mmc_host->bus_ops->detect判断之前的卡还在不在
// 不在则执行对应卡(mmc/sd/sdio)的移除操作
// 后面进行详细分析
if (host->bus_ops && !host->bus_dead)
host->bus_ops->detect(host);
host->detect_change = 0;
/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
// 释放总线,执行mmc_host->bus_refs--,
// 如果mmc_host->bus_ops不为NULL,并且引用计数为0,
// 则还会执行mmc_host->bus_ops = NULL;的操作
mmc_bus_put(host);
mmc_bus_get(host);
/* if there still is a card present, stop here */
// 如果bus_ops还非空,说明该总线还有被占用
if (host->bus_ops != NULL) {
// 引用计数减1,然后退出扫描流程
mmc_bus_put(host);
goto out;
}
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
// 占用该mmc host
mmc_claim_host(host);
// 卡是可移除的,并且host支持get_cd功能,
// 由控制器执行对应功能函数,判断cd引脚返回
// 如果返回0,说明卡拔出去的状态,否则卡是插入状态
if (mmc_card_is_removable(host) && host->ops->get_cd &&
host->ops->get_cd(host) == 0) {
mmc_power_off(host);
mmc_release_host(host);
goto out;
}
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
unsigned int freq = freqs[i];
if (freq > host->f_max) {
if (i + 1 < ARRAY_SIZE(freqs))
continue;
freq = host->f_max;
}
// 尝试用最高的频率去识别卡,其中freqs可能的值为:
// 400K,300K,200K,100K
if (!mmc_rescan_try_freq(host, max(freq, host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
// 释放该host
mmc_release_host(host);
out:
// 如果设置了是轮询扫卡的话,这里就相当于poll的一个过程
// 按照HZ的频率持续性的执行该工作队列,轮询扫卡
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
5.2 mmc_rescan_try_freq
以一定频率搜索host bus上的card。
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
pr_debug("%s: %s: trying to init card at %u Hz\n",
mmc_hostname(host), __func__, host->f_init);
// 用可用电压选择一个最基础的模式上电
mmc_power_up(host, host->ocr_avail);
// 一些eMMC(VCCQ始终开启)在上电后可能不会重置,
// 因此如果可能的话,进行硬件重置。
mmc_hw_reset_for_init(host);
// sdio_reset通过发送CMD52来重置卡。
// 由于我们不知道卡是否正在重新初始化,只需发送它即可。
// CMD52应该被SD/eMMC卡忽略。
// 如果我们已经知道不支持SDIO命令,就跳过它。
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
sdio_reset(host);
// 给设备发cmd0,设备进入idle状态(协议)
mmc_go_idle(host);
// sd卡需发送cmd8,获取card的可用电压,存储到host->ocr_avail中
// 协议见下
if (!(host->caps2 & MMC_CAP2_NO_SD))
mmc_send_if_cond(host, host->ocr_avail);
/* Order's important: probe SDIO, then SD, then MMC */
// 识别是不是一个sdio设备
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
if (!mmc_attach_sdio(host))
return 0;
// 识别是不是一个sd卡
if (!(host->caps2 & MMC_CAP2_NO_SD))
if (!mmc_attach_sd(host))
return 0;
// 识别是不是一个(e)mmc
if (!(host->caps2 & MMC_CAP2_NO_MMC))
if (!mmc_attach_mmc(host))
return 0;
// 如果都识别不到,就下电
mmc_power_off(host);
return -EIO;
}
5.3 mmc_attach_xx(主要以emmc进行分析)
这里以mmc_attach_mmc为例进行分析
<code>int mmc_attach_mmc(struct mmc_host *host)
{
int err;
u32 ocr, rocr;
WARN_ON(!host->claimed);
/* Set correct bus mode for MMC before attempting attach */
// bus_mode,两种信号模式,open-drain(MMC_BUSMODE_OPENDRAIN)
// 和push-pull(MMC_BUSMODE_PUSHPULL),对应不同的高低电平
if (!mmc_host_is_spi(host)) // 不是spi通信方式
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);
// 协议,发送cmd1(协议),获取mmc ocr
err = mmc_send_op_cond(host, 0, &ocr);
if (err)
return err;
// 总线ops匹配到mmc_ops,见下图
// host->bus_ops = ops; host->bus_refs = 1; host->bus_dead = 0;
mmc_attach_bus(host, &mmc_ops);
// 控制器的供电能力赋值
if (host->ocr_avail_mmc)
host->ocr_avail = host->ocr_avail_mmc;
/*
* We need to get OCR a different way for SPI.
*/
if (mmc_host_is_spi(host)) {
err = mmc_spi_read_ocr(host, 1, &ocr);
if (err)
goto err;
}
// 控制器和mmc的ocr电压匹配,选择一个合适的电压,重新供电
rocr = mmc_select_voltage(host, ocr);
/*
* Can we support the voltage of the card?
*/
if (!rocr) {
err = -EINVAL;
goto err;
}
/*
* Detect and init the card.
*/
// 下面详细分析
err = mmc_init_card(host, rocr, NULL);
if (err)
goto err;
mmc_release_host(host);
// 下面具体分析
err = mmc_add_card(host->card);
if (err)
goto remove_card;
// 最后占用住该host
mmc_claim_host(host);
return 0;
remove_card:
mmc_remove_card(host->card);
mmc_claim_host(host);
host->card = NULL;
err:
mmc_detach_bus(host);
pr_err("%s: error %d whilst initialising MMC card\n",
mmc_hostname(host), err);
return err;
}
5.4 mmc_init_card
该函数巨长,因此源码部分删减,该函数主要是协议的收发并配置卡,主要操作如下:
mmc_go_idle,发送cmd0,置emmc进idle状态mmc_send_op_cond,发送cmd1,获取ocr寄存器值(包括bit30,访问模式)mmc_send_cid,发送cmd2,获取cid寄存器值申请mmc_card结构空间,并部分初始化发送cmd3,将rca赋值给emmc,此时emmc进入standby状态,并将信号从开漏转为上推拉模式发送cmd9,获取csd寄存器,并解码cid和csd如果卡支持DSR寄存器,host需要发送cmd4对dsr进行配置,发送cmd7,card由stand-by切换到transfer-mode发送cmd8 读取扩展CSD,并解析。注:此时设备已经处于发送模式发送cmd6设置扩展csd的175寄存器,设置值为0,设置擦除组定义大小,这里使用默认大小发送cmd6设置扩展csd的179寄存器,访问权限设置,设置成默认(只访问用户分区)发送cmd6设置扩展csd的34寄存器,设置值为1,设置主机下电通知设备开始调速HS400ES/HS200/HS,以及位宽选择(这个过程不详细分析了,也是各种发命令进行设置)发送cmd6设置扩展csd的187寄存器,根据位宽选择一个合适的功耗等级发送cmd6配置扩展csd的161寄存器,使能HPI (High Priority Interrupt高优先级中断,该机制可以中断一些还没有完成的优先级比较低的操作,来满足对高优先级操作的需求)发送cmd6配置扩展csd的15寄存器,使能CQEmmc_host->card 赋值,识别卡到这里基本就成功一半了
<code>static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
int err;
u32 cid[4];
u32 rocr;
/* 删减 */
// 1. mmc_go_idle,发送cmd0,置emmc进idle状态
mmc_go_idle(host);
// 2. mmc_send_op_cond,发送cmd1,获取ocr寄存器值(包括bit30,访问模式)
err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
if (err)
goto err;
/* 删减 */
// 3. mmc_send_cid,发送cmd2,获取cid寄存器值
err = mmc_send_cid(host, cid);
if (err)
goto err;
if (oldcard) {
/* 删减,就是cid对比,如果不一样就go err,否则card=oldcard */
} else {
// 4. 申请mmc_card结构空间,并执行以下操作:
// card->host = mmc_host,初始化card->dev,
// card->dev.bus = &mmc_bus_type(见下图)
// card->dev.type = mmc_type
card = mmc_alloc_card(host, &mmc_type);
if (IS_ERR(card)) {
err = PTR_ERR(card);
goto err;
}
card->ocr = ocr;
card->type = MMC_TYPE_MMC;
card->rca = 1; //rca地址先写死成1了
// 把cid赋值给card_raw_cid
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
}
/* 删减 */
/*
* For native busses: set card RCA and quit open drain mode.
*/
if (!mmc_host_is_spi(host)) {
// 5. 发送cmd3,将rca赋值给emmc
// 一旦接收到RCA,设备就变为Stand-by状态,
// 设备将其输出驱动器从开漏切换到推拉,到这里已经完成了设备识别
// 具体见下图emmc状态图(设备识别模式)
err = mmc_set_relative_addr(card);
if (err)
goto free_card;
// 信号模式改为push-pull,第一章MMC卡接口含义图有说明
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
}
if (!oldcard) {
// 6. 发送cmd9,获取csd寄存器
err = mmc_send_csd(card, card->raw_csd);
if (err)
goto free_card;
// 7. 对返回的csd寄存器的值进行解码(主要看寄存器手册了)
err = mmc_decode_csd(card);
if (err)
goto free_card;
// 8. 对返回的cid寄存器的值进行解码(主要看寄存器手册了)
err = mmc_decode_cid(card);
if (err)
goto free_card;
}
/*
* handling only for cards supporting DSR and hosts requesting
* DSR configuration
*/
// 9. 如果卡支持DSR寄存器,host需要对dsr进行配置,发送cmd4
if (card->csd.dsr_imp && host->dsr_req)
mmc_set_dsr(host);
/*
* Select card, as all following commands rely on that.
*/
if (!mmc_host_is_spi(host)) {
// 10. 发送cmd7,card由stand-by切换到transfer-mode
err = mmc_select_card(card);
if (err)
goto free_card;
}
if (!oldcard) {
/* Read extended CSD. */
// 11. 发送cmd8 读取扩展CSD,并解析。此时设备已经处于发送模式
err = mmc_read_ext_csd(card);
if (err)
goto free_card;
// 如果ocr的bit30被置位,就是扇区寻址方式
// emmc容量大于2G的话,最小单位是扇区,小于2G可以字节寻址
if (rocr & BIT(30))
mmc_card_set_blockaddr(card);
/* Erase size depends on CSD and Extended CSD */
// 根据寄存器值设置擦除大小
mmc_set_erase_size(card);
}
/* Enable ERASE_GRP_DEF. This bit is lost after a reset or power off. */
if (card->ext_csd.rev >= 3) {
// 12. 擦除组定义大小用默认大小,发送cmd6,
// 设置扩展csd的175寄存器,设置值为0,寄存器描述见下图
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_ERASE_GROUP_DEF, 1,
card->ext_csd.generic_cmd6_time);
if (err && err != -EBADMSG)
goto free_card;
if (err) {
err = 0;
/*
* Just disable enhanced area off & sz
* will try to enable ERASE_GROUP_DEF
* during next time reinit
*/
card->ext_csd.enhanced_area_offset = -EINVAL;
card->ext_csd.enhanced_area_size = -EINVAL;
} else {
card->ext_csd.erase_group_def = 1;
/*
* enable ERASE_GRP_DEF successfully.
* This will affect the erase size, so
* here need to reset erase size
*/
mmc_set_erase_size(card);
}
}
/*
* Ensure eMMC user default partition is enabled
*/
if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
// 13. 访问权限设置,设置成默认(只访问用户分区),发送cmd6
// 设置扩展csd的179寄存器,设置值为0,寄存器描述见下图
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
card->ext_csd.part_config,
card->ext_csd.part_time);
if (err && err != -EBADMSG)
goto free_card;
}
/*
* Enable power_off_notification byte in the ext_csd register
*/
if (card->ext_csd.rev >= 6) {
// 14. 设置主机下电通知设备,发送cmd6
// 设置扩展csd的34寄存器,设置值为1,寄存器描述见下图
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_POWER_OFF_NOTIFICATION,
EXT_CSD_POWER_ON,
card->ext_csd.generic_cmd6_time);
if (err && err != -EBADMSG)
goto free_card;
/*
* The err can be -EBADMSG or 0,
* so check for success and update the flag
*/
if (!err)
card->ext_csd.power_off_notification = EXT_CSD_POWER_ON;
}
/* 删除部分 */
/*
* Select timing interface
*/
// 15. 调速:HS400ES/HS200/HS,以及位宽选择,这个过程略,不详细分析
err = mmc_select_timing(card);
if (err)
goto free_card;
/* 删除部分调速和位宽选择代码 */
/*
* Choose the power class with selected bus interface
*/
// 16. 根据位宽选择一个合适的功耗等级,不详细分析,
// 主要是设置扩展csd的187寄存器
mmc_select_powerclass(card);
/*
* Enable HPI feature (if supported)
*/
/* 删除部分使能HPI的代码,主要是配置扩展csd的161寄存器 */
/* 删除部分设置cache代码,保留注释,主要是配置扩展csd的33寄存器
如果缓存大小大于0,这表示存在缓存,并且可以打开。请注意,
来自Micron的一些eMMC已经被报告需要在突然断电测试后
启用缓存时需要约800毫秒的超时时间。
让我们将超时时间扩展到至少DEFAULT_CACHE_EN_TIMEOUT_MS,
并为所有卡片执行此操作。
*/
/* 删除CQE相关配置(命令队列使能) 主要是配置扩展csd的15寄存器 */
// mmc_host->card 赋值
if (!oldcard)
host->card = card;
return 0;
free_card:
if (!oldcard)
mmc_remove_card(card);
err:
return err;
}
I. mmc_bus_type描述:
II. 扩展CSD寄存器175寄存器描述:
III. 扩展CSD寄存器179寄存器描述:
IV. 扩展CSD寄存器34寄存器描述:
V. 扩展CSD寄存器187寄存器描述:
VI. 扩展CSD寄存器161寄存器描述:
VII. 扩展CSD寄存器15寄存器描述:
5.5 mmc_add_card
将卡正式进行注册,打印一些卡信息,并注册到debugfs中,至此识卡完成
<code>int mmc_add_card(struct mmc_card *card)
{
int ret;
const char *type;
const char *uhs_bus_speed_mode = "";
static const char *const uhs_speeds[] = {
[UHS_SDR12_BUS_SPEED] = "SDR12 ",
[UHS_SDR25_BUS_SPEED] = "SDR25 ",
[UHS_SDR50_BUS_SPEED] = "SDR50 ",
[UHS_SDR104_BUS_SPEED] = "SDR104 ",
[UHS_DDR50_BUS_SPEED] = "DDR50 ",
};
// 设置卡名:host+4位rca
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
switch (card->type) {
case MMC_TYPE_MMC:
type = "MMC";
break;
case MMC_TYPE_SD:
type = "SD";
if (mmc_card_blockaddr(card)) {
if (mmc_card_ext_capacity(card))
type = "SDXC";
else
type = "SDHC";
}
break;
case MMC_TYPE_SDIO:
type = "SDIO";
break;
case MMC_TYPE_SD_COMBO:
type = "SD-combo";
if (mmc_card_blockaddr(card))
type = "SDHC-combo";
break;
default:
type = "?";
break;
}
// 识别是否是uhs卡和卡速度
if (mmc_card_uhs(card) &&
(card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))
uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];
// 打印卡的一些信息,如本设备的打印如下:
// mmc2: new ultra high speed SDR12 MMC card at address 0001
if (mmc_host_is_spi(card->host)) {
pr_info("%s: new %s%s%s card on SPI\n",
mmc_hostname(card->host),
mmc_card_hs(card) ? "high speed " : "",
mmc_card_ddr52(card) ? "DDR " : "",
type);
} else {
pr_info("%s: new %s%s%s%s%s%s card at address %04x\n",
mmc_hostname(card->host),
mmc_card_uhs(card) ? "ultra high speed " :
(mmc_card_hs(card) ? "high speed " : ""),
mmc_card_hs400(card) ? "HS400 " :
(mmc_card_hs200(card) ? "HS200 " : ""),
mmc_card_hs400es(card) ? "Enhanced strobe " : "",
mmc_card_ddr52(card) ? "DDR " : "",
uhs_bus_speed_mode, type, card->rca);
}
#ifdef CONFIG_DEBUG_FS
// 加入到debugfs,见下图,可以查看一些卡属性
mmc_add_card_debugfs(card);
#endif
card->dev.of_node = mmc_of_find_child_device(card->host, 0);
device_enable_async_suspend(&card->dev);
// 将该卡注册,添加到dev中
ret = device_add(&card->dev);
if (ret)
return ret;
// 置卡在线标志
mmc_card_set_present(card);
return 0;
}
I. Debugfs下可以查看的一些属性
可以查看对应host的caps、卡的ext_csd(emmc)、卡的status(通过cmd13获取,详情见协议手册中6.13 设备状态)和卡的state(见下图)
5.6 e•MMC状态图(设备识别模式)
5.7 e•MMC状态图(数据传输模式)
6. 通信(命令)
本章节分析mmc子系统的通信流程,以上面emmc扫卡识别中,使用的mmc_go_idle(命令发送)为例进行分析。
6.1 mmc_go_idle
源码如下,有部分删减,可以看出实际就是填充cmd结构,然后调用mmc_wait_for_cmd
<code>int mmc_go_idle(struct mmc_host *host)
{
int err;
struct mmc_command cmd = { };
/* 删 */
cmd.opcode = MMC_GO_IDLE_STATE; // cmd0
cmd.arg = 0;
// 这里置标志,在后面详细说明
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
err = mmc_wait_for_cmd(host, &cmd, 0);
/* 删 */
return err;
}
6.2 mmc_wait_for_cmd
该函数是命令发送的核心
/**
* mmc_wait_for_cmd - start a command and wait for completion
* @host: MMC host to start command
* @cmd: MMC command to start
* @retries: maximum number of retries
*
* Start a new MMC command for a host, and wait for the command
* to complete. Return any error that occurred while the command
* was executing. Do not attempt to parse the response.
*/
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
{
struct mmc_request mrq = { };
WARN_ON(!host->claimed);
// 命令回复清空
memset(cmd->resp, 0, sizeof(cmd->resp));
// 赋值最大重试次数
cmd->retries = retries;
mrq.cmd = cmd;
cmd->data = NULL;
mmc_wait_for_req(host, &mrq);
return cmd->error;
}
6.3 mmc_wait_for_req
实际上该函数主要执行两步:1.开启命令请求;2.等待请求完成,其中cap_cmd_during_tfr标志的作用为:在此进行数据传输或忙碌等待期间允许其他命令(在代码中没发现哪里会置ture,因此后续先不管这个变量了)
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
__mmc_start_req(host, mrq);
if (!mrq->cap_cmd_during_tfr)
mmc_wait_for_req_done(host, mrq);
}
6.4 __mmc_start_req
<code>static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
int err;
// 这个只有cap_cmd_during_tfr生效时才起作用,不分析
mmc_wait_ongoing_tfr_cmd(host);
// 初始化完成量
init_completion(&mrq->completion);
// 实际上就是complete(&mrq->completion);
mrq->done = mmc_wait_done;
// 开始请求,下面详细分析
err = mmc_start_request(host, mrq);
if (err) {
// 出错处理
mrq->cmd->error = err;
// 这个函数也是只有cap_cmd_during_tfr生效时才起作用,不分析
mmc_complete_cmd(mrq);
complete(&mrq->completion);
}
return err;
}
6.5 mmc_start_request
<code>int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
int err;
// 初始化cmd_xxx完成量,cap_cmd_during_tfr置位时生效,不细究
init_completion(&mrq->cmd_completion);
// 执行后,未释放时是不允许mmc进行调速操作的
mmc_retune_hold(host);
// 卡state处于移除状态,则直接放回错误
if (mmc_card_removed(host->card))
return -ENOMEDIUM;
// debug打印
mmc_mrq_pr_debug(host, mrq, false);
// 如果host没被占有,则警告
WARN_ON(!host->claimed);
// 对mrq数据结构进行部分初始化和部分数据校验,
// 主要是判断数据大小不能超过host一次写入的大小
err = mmc_mrq_prep(host, mrq);
if (err)
return err;
// 灯,不管
led_trigger_event(host->led, LED_FULL);
// 正式开启数据请求,实际上就是执行mmc_host->ops->request(host, mrq);
// 也就是执行 sdhci_request
__mmc_start_request(host, mrq);
return 0;
}
6.6 sdhci_request
这里涉及到控制器层面操作了,主要是通过配置host寄存器实现数据收发
<code>void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host = mmc_priv(mmc);
struct mmc_command *cmd;
unsigned long flags;
bool present;
/* Firstly check card presence */
// 获取卡的在线状态
present = mmc->ops->get_cd(mmc);
// 加锁,禁中断
spin_lock_irqsave(&host->lock, flags);
// 灯不管
sdhci_led_activate(host);
// 如果卡不在了,直接退出就完事了,并赋值错误状态
if (sdhci_present_error(host, mrq->cmd, present))
goto out_finish;
cmd = sdhci_manual_cmd23(host, mrq) ? mrq->sbc : mrq->cmd;
// 这里主要涉及控制器层面的一些操作了
// 这里面涉及非常复杂的操作,各种定时器,工作队列和控制器中断等等
// 实际上主要执行sdhci_send_command,中间包含部分休眠等待多次重试的操作
if (!sdhci_send_command_retry(host, cmd, flags))
goto out_finish;
spin_unlock_irqrestore(&host->lock, flags);
return;
out_finish:
// 成功发送数据,完成请求,后面详细分析
sdhci_finish_mrq(host, mrq);
spin_unlock_irqrestore(&host->lock, flags);
}
I. sdhci_send_command
执行cmd发送,主要是对控制器的一些操作,配置各个寄存器
<code>static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
{
int flags;
u32 mask;
unsigned long timeout;
u16 mode = 0;
WARN_ON(host->cmd);
/* Initially, a command has no error */
cmd->error = 0;
if ((host->quirks2 & SDHCI_QUIRK2_STOP_WITH_TC) &&
cmd->opcode == MMC_STOP_TRANSMISSION)
cmd->flags |= MMC_RSP_BUSY;
// 掩码置位,命令发送一定要检测,见主机控制器24H寄存器描述
mask = SDHCI_CMD_INHIBIT;
// 判断是否要发送数据,或者cmd->flags & MMC_RSP_BUSY
if (sdhci_data_line_cmd(cmd))
mask |= SDHCI_DATA_INHIBIT; //如果发送数据,掩码置位数据bit
/* We shouldn't wait for data inihibit for stop commands, even
though they might use busy signaling */
// 意思是如果需要发停止,就不用等数据线ok了
if (cmd->mrq->data && (cmd == cmd->mrq->data->stop))
mask &= ~SDHCI_DATA_INHIBIT;
// 读24H寄存器,如果与掩码与上之后有某个bit是1,说明cmd或者data
// 有一个还在忙,不能发送命令或数据
if (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)
return false;
// 命令给到主机控制器
host->cmd = cmd;
host->data_timeout = 0;
if (sdhci_data_line_cmd(cmd)) {
WARN_ON(host->data_cmd);
host->data_cmd = cmd;
// 计算一个合适的超时时间(设置2EH寄存器)
// 并使能数据超时中断
sdhci_set_timeout(host, cmd);
}
if (cmd->data) {
/* 删除dma相关 */
// 如果存在数据,则进行一些校验,初始化等操作,这里不详细分析
sdhci_prepare_data(host, cmd);
}
// 写命令参数,到主机控制器08H
sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
// 设置发送模式,写0CH寄存器
mode = sdhci_set_transfer_mode(host, cmd);
if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
WARN_ONCE(1, "Unsupported response type!\n");
/*
* This does not happen in practice because 136-bit response
* commands never have busy waiting, so rather than complicate
* the error path, just remove busy waiting and continue.
*/
cmd->flags &= ~MMC_RSP_BUSY;
}
// 一些标志位设置
if (!(cmd->flags & MMC_RSP_PRESENT))
flags = SDHCI_CMD_RESP_NONE;
else if (cmd->flags & MMC_RSP_136)
flags = SDHCI_CMD_RESP_LONG;
else if (cmd->flags & MMC_RSP_BUSY)
flags = SDHCI_CMD_RESP_SHORT_BUSY;
else
flags = SDHCI_CMD_RESP_SHORT;
if (cmd->flags & MMC_RSP_CRC)
flags |= SDHCI_CMD_CRC;
if (cmd->flags & MMC_RSP_OPCODE)
flags |= SDHCI_CMD_INDEX;
/* CMD19 is special in that the Data Present Select should be set */
if (cmd->data || cmd->opcode == MMC_SEND_TUNING_BLOCK ||
cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)
flags |= SDHCI_CMD_DATA;
timeout = jiffies;
if (host->data_timeout)
timeout += nsecs_to_jiffies(host->data_timeout);
else if (!cmd->data && cmd->busy_timeout > 9000)
timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
else
timeout += 10 * HZ;
// 如果涉及数据发送,则mod_timer(&host->data_timer, timeout);
// 如果只是cmd,则mod_timer(&host->timer, timeout);
sdhci_mod_timer(host, cmd->mrq, timeout);
/* dma删除 */
// 发送命令,写0E寄存器
sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
return true;
}
a. sdhci主机控制器24H寄存器
其中D00的含义如下,即当该bit为1时不能发送命令
D01的含义如下,同D00,该bit为1时不能发送数据
b. sdhci主机控制器08H寄存器
c. sdhci主机控制器0CH寄存器
其中D03-D02的含义如下
其中D04的含义如下
d. sdhci主机控制器0EH寄存器
其中D013-D08的含义如下
其中D07-D06的含义如下
其中D05的含义如下
其中D01-D00的含义如下
II. sdhci_cmd_irq
控制器在初始化阶段已经配置了默认的中断,中断配置如下,当cmd发送完成后,如果正常将触发host中断,并进入sdhci_irq然后通过掩码判断进入sdhci_cmd_irq
<code>static void sdhci_set_default_irqs(struct sdhci_host *host)
{
host->ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT |
SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC |
SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END |
SDHCI_INT_RESPONSE;
if (host->tuning_mode == SDHCI_TUNING_MODE_2 ||
host->tuning_mode == SDHCI_TUNING_MODE_3)
host->ier |= SDHCI_INT_RETUNE;
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); //34H寄存器
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); //38H寄存器
}
sdhci_cmd_irq源码如下,其中initmask通过读取30H寄存器(读取的32位,因此连带中断错误状态[32H]一起都读了)
static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
{
/* Handle auto-CMD12 error */
// 判断一下自动命令是否有报错,报的什么错
if (intmask & SDHCI_INT_AUTO_CMD_ERR && host->data_cmd) {
struct mmc_request *mrq = host->data_cmd->mrq;
u16 auto_cmd_status = sdhci_readw(host, SDHCI_AUTO_CMD_STATUS);
int data_err_bit = (auto_cmd_status & SDHCI_AUTO_CMD_TIMEOUT) ?
SDHCI_INT_DATA_TIMEOUT :
SDHCI_INT_DATA_CRC;
/* Treat auto-CMD12 error the same as data error */
if (!mrq->sbc && (host->flags & SDHCI_AUTO_CMD12)) {
*intmask_p |= data_err_bit;
return;
}
}
// 本身是命令的中断回调,结果命令还是空的,肯定有问题
if (!host->cmd) {
/*
* SDHCI recovers from errors by resetting the cmd and data
* circuits. Until that is done, there very well might be more
* interrupts, so ignore them in that case.
*/
if (host->pending_reset)
return;
pr_err("%s: Got command interrupt 0x%08x even though no command operation was in progress.\n",
mmc_hostname(host->mmc), (unsigned)intmask);
sdhci_dumpregs(host);
return;
}
// 存在下述4种错误,对应32H的D3-D0,做一些处理
if (intmask & (SDHCI_INT_TIMEOUT | SDHCI_INT_CRC |
SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) {
if (intmask & SDHCI_INT_TIMEOUT)
host->cmd->error = -ETIMEDOUT;
else
host->cmd->error = -EILSEQ;
/* Treat data command CRC error the same as data CRC error */
if (host->cmd->data &&
(intmask & (SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)) ==
SDHCI_INT_CRC) {
host->cmd = NULL;
*intmask_p |= SDHCI_INT_DATA_CRC;
return;
}
__sdhci_finish_mrq(host, host->cmd->mrq);
return;
}
/* Handle auto-CMD23 error */
if (intmask & SDHCI_INT_AUTO_CMD_ERR) {
struct mmc_request *mrq = host->cmd->mrq;
u16 auto_cmd_status = sdhci_readw(host, SDHCI_AUTO_CMD_STATUS);
int err = (auto_cmd_status & SDHCI_AUTO_CMD_TIMEOUT) ?
-ETIMEDOUT :
-EILSEQ;
if (mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
mrq->sbc->error = err;
__sdhci_finish_mrq(host, mrq);
return;
}
}
// 上面都是错误处理,到这里才是真的的命令发送完成中断
// 对应30H的D0
if (intmask & SDHCI_INT_RESPONSE)
sdhci_finish_command(host);
}
a. sdhci主机控制器34H寄存器
b. sdhci主机控制器38H寄存器
c. sdhci主机控制器30H寄存器(中断状态)
根据描述可知,sdhci主机控制器的中断状态想要获取到的话,34H和38H都要使能才行
d. sdhci主机控制器32H寄存器(中断错误状态)
III. sdhci_finish_command
到这里,命令发送完成,执行相关处理,在此之前,我们来看下cmd->flags,这个flags的标记都在执行命令时候进行设置,如mmc_go_idle中就对cmd->flags置了MMC_RSP_NONE,相当于不需要任何回复。
<code>/*
* These are the native response types, and correspond to valid bit
* patterns of the above flags. One additional valid pattern
* is all zeros, which means we don't expect a response.
*/
#define MMC_RSP_NONE (0)
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
#define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC)
#define MMC_RSP_R3 (MMC_RSP_PRESENT)
#define MMC_RSP_R4 (MMC_RSP_PRESENT)
#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
static void sdhci_finish_command(struct sdhci_host *host)
{
struct mmc_command *cmd = host->cmd;
host->cmd = NULL;
// 如果对于需要回复的
if (cmd->flags & MMC_RSP_PRESENT) {
// RSP_136实际对应R2这种长数据应答,读10H寄存器
if (cmd->flags & MMC_RSP_136) {
sdhci_read_rsp_136(host, cmd);
} else {
// 正常应答,也是读10H寄存器,具体见下图
cmd->resp[0] = sdhci_readl(host, SDHCI_RESPONSE);
}
}
/* 删除部分 */
/* Processed actual command. */
// 正常只有置位MMC_RSP_BUSY了的情况,会触发这个条件
if (host->data && host->data_early)
sdhci_finish_data(host); //后面再分析
// 确定不是一个数据命令
if (!cmd->data)
// 调用完成
__sdhci_finish_mrq(host, cmd->mrq);
}
a. sdhci主机控制器10H寄存器
其中Response Field对应协议中应答的具体字段,Response Register是对应寄存器的那些值来进行表示。
IV. sdhci_timeout_timer
如果数据传输出现超时,在sdhci_send_command中经sdhci_mod_timer,到时后唤起对应定时器
<code>static void sdhci_timeout_timer(struct timer_list *t)
{
struct sdhci_host *host;
unsigned long flags;
host = from_timer(host, t, timer);
spin_lock_irqsave(&host->lock, flags);
// cmd非空,并且没有数据要发送
if (host->cmd && !sdhci_data_line_cmd(host->cmd)) {
// 报错,超时了
pr_err("%s: Timeout waiting for hardware cmd interrupt.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
host->cmd->error = -ETIMEDOUT;
// 后面分析
sdhci_finish_mrq(host, host->cmd->mrq);
}
spin_unlock_irqrestore(&host->lock, flags);
}
6.7 sdhci_finish_mrq
实际上主体还是唤醒工作队列执行sdhci_request_done
<code>static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
{
__sdhci_finish_mrq(host, mrq);
// 唤醒工作队列,执行sdhci_complete_work,即执行sdhci_request_done
queue_work(host->complete_wq, &host->complete_work);
}
I. __sdhci_finish_mrq
只做了3件事:置空,host->mrqs_done = mrq(request_done用),删除超时定时器
static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq)
{
// 各种置空,该发的到这里都发完了
if (host->cmd && host->cmd->mrq == mrq)
host->cmd = NULL;
if (host->data_cmd && host->data_cmd->mrq == mrq)
host->data_cmd = NULL;
if (host->deferred_cmd && host->deferred_cmd->mrq == mrq)
host->deferred_cmd = NULL;
if (host->data && host->data->mrq == mrq)
host->data = NULL;
if (sdhci_needs_reset(host, mrq))
host->pending_reset = true;
// 设置要完成的请求是哪一个,实际上就是host->mrqs_done = mrq
sdhci_set_mrq_done(host, mrq);
// 超时用的定时器删掉
sdhci_del_timer(host, mrq);
// 灯,不管
if (!sdhci_has_requests(host))
sdhci_led_deactivate(host);
}
II. sdhci_request_done
执行完该函数,才意味着完成了一次通信
static bool sdhci_request_done(struct sdhci_host *host)
{
unsigned long flags;
struct mmc_request *mrq;
int i;
spin_lock_irqsave(&host->lock, flags);
// 获取要完成的请求
for (i = 0; i < SDHCI_MAX_MRQS; i++) {
mrq = host->mrqs_done[i];
if (mrq)
break;
}
// 如果要完成的请求是空的,直接返回就行了
if (!mrq) {
spin_unlock_irqrestore(&host->lock, flags);
return true;
}
/* 删除如果出错或因为其他原因重启host的代码 */
/*
* Always unmap the data buffers if they were mapped by
* sdhci_prepare_data() whenever we finish with a request.
* This avoids leaking DMA mappings on error.
*/
/* 删除了一些dma相关的操作 */
// mrqs_done一共有两个,循环利用,这里这个到这里可以置空了
host->mrqs_done[i] = NULL;
spin_unlock_irqrestore(&host->lock, flags);
if (host->ops->request_done)
host->ops->request_done(host, mrq);
else
// 调用mmc层的函数,实际上就是执行mrq->done = mmc_wait_done;
// 该函数中间有一些调试打印,无关紧要
// 而done执行:complete(&mrq->completion);
// 而该完成量在mmc_wait_for_req_done进行等待
mmc_request_done(host->mmc, mrq);
return false;
}
6.8 mmc_wait_for_req_done
<code>void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq)
{
struct mmc_command *cmd;
while (1) {
// 等待完成量
wait_for_completion(&mrq->completion);
cmd = mrq->cmd;
// 没有报错,说明这次通信成功了,没有重试次数了
// 卡拔出了,都退出
if (!cmd->error || !cmd->retries ||
mmc_card_removed(host->card))
break;
mmc_retune_recheck(host);
pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
mmc_hostname(host), cmd->opcode, cmd->error);
// 非上述3种情况,重试次数自减,错误清0,然后继续执行通信
cmd->retries--;
cmd->error = 0;
__mmc_start_request(host, mrq);
}
mmc_retune_release(host);
}
7. 通信(数据)
本章节分析mmc子系统的通信流程,以上面emmc扫卡识别中,使用的mmc_read_ext_csd ->mmc_get_ext_csd ->mmc_send_cxd_data (扩展寄存器读)为例进行分析。实际上对比命令通信,数据通信多了设置数据通信量(写04H/06H)并执行数据通信操作,中断和超时处理略有差异
7.1 mmc_send_cxd_data
在上一层级,调用参数为:mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD, ext_csd, 512);其中MMC_SEND_EXT_CSD的值为8,即使用cmd8进行通信
<code>static int
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
u32 opcode, void *buf, unsigned len)
{
struct mmc_request mrq = { };
struct mmc_command cmd = { };
struct mmc_data data = { };
struct scatterlist sg;
mrq.cmd = &cmd;
mrq.data = &data;
cmd.opcode = opcode;
cmd.arg = 0;
/* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
* rely on callers to never use this with "native" calls for reading
* CSD or CID. Native versions of those commands use the R2 type,
* not R1 plus a data block.
*/
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
// 这里填充数据,块大小512,块个数1个,进行块读
data.blksz = len;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, buf, len);
if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
/*
* The spec states that CSR and CID accesses have a timeout
* of 64 clock cycles.
*/
data.timeout_ns = 0;
data.timeout_clks = 64;
} else
// 计算确定data->timeout_ns和data.timeout_clks
mmc_set_data_timeout(&data, card);
// 回到了熟悉的环节,这里我们来分析一下和data通信相关,
// 在之前未分析的函数
mmc_wait_for_req(host, &mrq);
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return 0;
}
7.2 sdhci_prepare_data
在执行sdhci_send_command时,如果cmd->data不空,则需要对data进行“前期准备”的操作
static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
{
struct mmc_data *data = cmd->data;
// 执行 host->data = data; 并对块大小校验
// BUG_ON(data->blksz * data->blocks > 524288); // 512K
// BUG_ON(data->blksz > host->mmc->max_blk_size);
// BUG_ON(data->blocks > 65535);
sdhci_initialize_data(host, data);
/* 删除大段dma相关设置,DMA相关的不分析 */
sdhci_config_dma(host);
/* 删除大段dma相关设置,DMA相关的不分析 */
// 使能数据中断
sdhci_set_transfer_irqs(host);
// 见下
sdhci_set_block_info(host, data);
}
7.3 sdhci_set_block_info
static inline void sdhci_set_block_info(struct sdhci_host *host,
struct mmc_data *data)
{
/* Set the DMA boundary value and block size */
// 配置04H寄存器,设置读写的块大小
sdhci_writew(host,
SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz),
SDHCI_BLOCK_SIZE);
/* 删除一种(SDHCI_QUIRK2_USE_32BIT_BLK_CNT)的特殊情况 */
// 配置06H寄存器,设置写入的块数量
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
// 一次读写入的块大小*读写入的块数量 = 总计读写的数据量
}
I. sdhci主机控制器04H寄存器
II. sdhci主机控制器06H寄存器
7.4 sdhci_data_irq
<code>static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
{
u32 command;
/* CMD19 generates _only_ Buffer Read Ready interrupt */
// 命令19和命令21都是用来调速的测试命令,到这里就算tuning完成
if (intmask & SDHCI_INT_DATA_AVAIL) {
command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND));
if (command == MMC_SEND_TUNING_BLOCK ||
command == MMC_SEND_TUNING_BLOCK_HS200) {
host->tuning_done = 1;
wake_up(&host->buf_ready_int);
return;
}
}
// 没有数据的数据中断,即在发送命令时,是MMC_RSP_BUSY的状态
if (!host->data) {
struct mmc_command *data_cmd = host->data_cmd;
/*
* The "data complete" interrupt is also used to
* indicate that a busy state has ended. See comment
* above in sdhci_cmd_irq().
*/
if (data_cmd && (data_cmd->flags & MMC_RSP_BUSY)) {
if (intmask & SDHCI_INT_DATA_TIMEOUT) {
host->data_cmd = NULL;
data_cmd->error = -ETIMEDOUT;
// 这个cmd也发送超时了
__sdhci_finish_mrq(host, data_cmd->mrq);
return;
}
if (intmask & SDHCI_INT_DATA_END) {
host->data_cmd = NULL;
/*
* 有些卡在命令完成之前就处理了忙碌结束中断,
* 因此请确保我们按照正确的顺序进行操作。
*/
if (host->cmd == data_cmd)
return;
__sdhci_finish_mrq(host, data_cmd->mrq);
return;
}
}
/*
* SDHCI通过重置命令和数据电路从错误中恢复。
* 在那之前,很可能还会有更多的中断,所以在这种情况下忽略它们。
*/
if (host->pending_reset)
return;
pr_err("%s: Got data interrupt 0x%08x even though no data operation was in progress.\n",
mmc_hostname(host->mmc), (unsigned)intmask);
sdhci_dumpregs(host);
return;
}
// 根据中断状态标记错误
if (intmask & SDHCI_INT_DATA_TIMEOUT)
host->data->error = -ETIMEDOUT;
else if (intmask & SDHCI_INT_DATA_END_BIT)
host->data->error = -EILSEQ;
else if ((intmask & SDHCI_INT_DATA_CRC) &&
SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
!= MMC_BUS_TEST_R)
host->data->error = -EILSEQ;
else if (intmask & SDHCI_INT_ADMA_ERROR) {
pr_err("%s: ADMA error: 0x%08x\n", mmc_hostname(host->mmc),
intmask);
sdhci_adma_show_error(host);
host->data->error = -EIO;
if (host->ops->adma_workaround)
host->ops->adma_workaround(host, intmask);
}
// 如果发生了错误,执行finish
if (host->data->error)
sdhci_finish_data(host);
else {
// 中断状态为:buffer可读可写
if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL))
// 下面详细分析
sdhci_transfer_pio(host);
/* DMA相关删除 */
if (intmask & SDHCI_INT_DATA_END) {
if (host->cmd == host->data_cmd) {
/*
* Data managed to finish before the
* command completed. Make sure we do
* things in the proper order.
*/
// 数据比命令完成的还快,需要做特殊处理
// 在sdhci_cmd_irq->sdhci_finish_command,
// 会判断该标志,最后执行sdhci_finish_data
host->data_early = 1;
} else {
// 下面详细分析
sdhci_finish_data(host);
}
}
}
}
7.5 sdhci_transfer_pio
在这里完成真正的数据读写
static void sdhci_transfer_pio(struct sdhci_host *host)
{
u32 mask;
if (host->blocks == 0)
return;
// 判断是要读还是要写,置位掩码
if (host->data->flags & MMC_DATA_READ)
mask = SDHCI_DATA_AVAILABLE;
else
mask = SDHCI_SPACE_AVAILABLE;
/* 删除部分特性的特殊处理 */
// 读状态寄存器24H,判断D10/D9
while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) {
if (host->quirks & SDHCI_QUIRK_PIO_NEEDS_DELAY)
udelay(100);
// 实际就是将数据读/写入20H寄存器
if (host->data->flags & MMC_DATA_READ)
sdhci_read_block_pio(host);
else
sdhci_write_block_pio(host);
// 直到读写完全部块
host->blocks--;
if (host->blocks == 0)
break;
}
DBG("PIO transfer complete.\n");
}
I. sdhci主机控制器20H寄存器
7.6 sdhci_finish_data
<code>static void sdhci_finish_data(struct sdhci_host *host)
{
__sdhci_finish_data(host, false);
}
static void __sdhci_finish_data(struct sdhci_host *host, bool sw_data_timeout)
{
struct mmc_command *data_cmd = host->data_cmd;
struct mmc_data *data = host->data;
// 数据都清掉
host->data = NULL;
host->data_cmd = NULL;
/*
* The controller needs a reset of internal state machines upon error
* conditions.
*/
if (data->error) {
// 如果有报错,尝试复位
if (!host->cmd || host->cmd == data_cmd)
sdhci_do_reset(host, SDHCI_RESET_CMD);
sdhci_do_reset(host, SDHCI_RESET_DATA);
}
/* DMA删 */
/*
* 规范指出必须更新块计数寄存器,
* 但并未具体说明在数据流的哪个点进行更新。
* 这使得寄存器读取回来完全没有用处,
* 因此我们必须假设在发生错误时没有任何数据被传送到卡中。
*/
if (data->error)
data->bytes_xfered = 0;
else
data->bytes_xfered = data->blksz * data->blocks;
/*
* Need to send CMD12 if -
* a) open-ended multiblock transfer not using auto CMD12 (no CMD23)
* b) error in multiblock transfer
*/
// 如果不支持自动cmd12,并且还需要发stop,或者数据通信有报错
// 通过sdhci_send_command发stop
if (data->stop &&
((!data->mrq->sbc && !sdhci_auto_cmd12(host, data->mrq)) ||
data->error)) {
/*
* 'cap_cmd_during_tfr' request must not use the command line
* after mmc_command_done() has been called. It is upper layer's
* responsibility to send the stop command if required.
*/
if (data->mrq->cap_cmd_during_tfr) {
__sdhci_finish_mrq(host, data->mrq);
} else {
/* Avoid triggering warning in sdhci_send_command() */
host->cmd = NULL;
if (!sdhci_send_command(host, data->stop)) {
if (sw_data_timeout) {
/*
* This is anyway a sw data timeout, so
* give up now.
*/
// 如果是因为超时进来的,然后发stop的cmd也失败了
// 说明硬件可能有问题,置io错误
data->stop->error = -EIO;
__sdhci_finish_mrq(host, data->mrq);
} else {
WARN_ON(host->deferred_cmd);
// 如果不是因为超时进来,但是命令还发送失败了
// 将命令推迟执行,在下次中断执行时候,会执行
// sdhci_thread_irq,这个后面分析
host->deferred_cmd = data->stop;
}
}
}
} else {
// 执行完成
__sdhci_finish_mrq(host, data->mrq);
}
}
7.7 sdhci_timeout_data_timer
<code>static void sdhci_timeout_data_timer(struct timer_list *t)
{
struct sdhci_host *host;
unsigned long flags;
host = from_timer(host, t, data_timer);
spin_lock_irqsave(&host->lock, flags);
// 有数据,或者需要使用数据线发送
if (host->data || host->data_cmd ||
(host->cmd && sdhci_data_line_cmd(host->cmd))) {
pr_err("%s: Timeout waiting for hardware interrupt.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
// 置超时错误
if (host->data) {
host->data->error = -ETIMEDOUT;
__sdhci_finish_data(host, true);
// 唤醒sdhci_complete_work,执行sdhci_request_done
queue_work(host->complete_wq, &host->complete_work);
} else if (host->data_cmd) {
host->data_cmd->error = -ETIMEDOUT;
sdhci_finish_mrq(host, host->data_cmd->mrq);
} else {
host->cmd->error = -ETIMEDOUT;
sdhci_finish_mrq(host, host->cmd->mrq);
}
}
spin_unlock_irqrestore(&host->lock, flags);
}
8. 中断
在前文已经初步分析过了中断,数据通信过程中的sdhci_data_irq和sdhci_cmd_irq,实际上中断还要处理其他事件,比如sd卡的热插拔等,直接分析源码。
8.1 sdhci_irq
主机控制器的中断在注册阶段通过request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq, IRQF_SHARED , mmc_hostname ( host->mmc ), host); 进行绑定。主机控制器中断中处理包括:数据,命令,热插拔等事件,最终确保非空的mrq_done一定能够唤醒request_done
static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
struct mmc_request *mrqs_done[SDHCI_MAX_MRQS] = { 0};
irqreturn_t result = IRQ_NONE;
struct sdhci_host *host = dev_id;
u32 intmask, mask, unexpected = 0;
int max_loops = 16;
int i;
spin_lock(&host->lock);
// 运行过程中被挂起,中断不处理了
if (host->runtime_suspended) {
spin_unlock(&host->lock);
return IRQ_NONE;
}
// 读取30H+32H中断状态和中断错误状态寄存器
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
if (!intmask || intmask == 0xffffffff) {
result = IRQ_NONE;
goto out;
}
do {
// 根据中断状态循环进行处理
DBG("IRQ status 0x%08x\n", intmask);
// 如果控制器底层还有额外的处理流程,就执行
if (host->ops->irq) {
intmask = host->ops->irq(host, intmask);
if (!intmask)
goto cont;
}
/* Clear selected interrupts. */
mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
SDHCI_INT_BUS_POWER);
// 清除cmd,data和SD BUS POWER错误
sdhci_writel(host, mask, SDHCI_INT_STATUS);
if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
// 卡插入或拔出,获取卡状态
u32 present = (sdhci_readl(host, SDHCI_PRESENT_STATE) &
SDHCI_CARD_PRESENT);
/*
* There is a observation on i.mx esdhc. INSERT
* bit will be immediately set again when it gets
* cleared, if a card is inserted. We have to mask
* the irq to prevent interrupt storm which will
* freeze the system. And the REMOVE gets the
* same situation.
*
* More testing are needed here to ensure it works
* for other platforms though.
*/
// 重新使能卡插入或拔出中断
host->ier &= ~(SDHCI_INT_CARD_INSERT |
SDHCI_INT_CARD_REMOVE);
host->ier |= present ? SDHCI_INT_CARD_REMOVE :
SDHCI_INT_CARD_INSERT;
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
// 清除中断标志
sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |
SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);
// 中断线程中标记是卡插拔
host->thread_isr |= intmask & (SDHCI_INT_CARD_INSERT |
SDHCI_INT_CARD_REMOVE);
// 只有返回IRQ_WAKE_THREAD,后续才会调用中断线程
result = IRQ_WAKE_THREAD;
}
// 命令处理
if (intmask & SDHCI_INT_CMD_MASK)
sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK, &intmask);
// 数据处理
if (intmask & SDHCI_INT_DATA_MASK)
sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
/* 删部分 */
// 将已经处理的中断都清掉
intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE |
SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
SDHCI_INT_ERROR | SDHCI_INT_BUS_POWER |
SDHCI_INT_RETUNE | SDHCI_INT_CARD_INT);
// 如果还是非0,说明有中断处理不了,是异常的
if (intmask) {
unexpected |= intmask;
// 吧能处理的中断清掉
sdhci_writel(host, intmask, SDHCI_INT_STATUS);
}
cont:
if (result == IRQ_NONE)
result = IRQ_HANDLED;
// 在读中断状态
intmask = sdhci_readl(host, SDHCI_INT_STATUS);
} while (intmask && --max_loops);
/* Determine if mrqs can be completed immediately */
for (i = 0; i < SDHCI_MAX_MRQS; i++) {
struct mmc_request *mrq = host->mrqs_done[i];
if (!mrq)
continue;
// 由于各种原因请求完成需要被推迟
if (sdhci_defer_done(host, mrq)) {
result = IRQ_WAKE_THREAD;
} else {
// 到这里,只要非空的请求,要确保最后一定能完成
mrqs_done[i] = mrq;
host->mrqs_done[i] = NULL;
}
}
out:
// 如果有被推迟的命令,唤醒中断线程
if (host->deferred_cmd)
result = IRQ_WAKE_THREAD;
spin_unlock(&host->lock);
/* Process mrqs ready for immediate completion */
for (i = 0; i < SDHCI_MAX_MRQS; i++) {
if (!mrqs_done[i])
continue;
// 上面获取到的需要完成的mrq,这里唤醒完成请求
if (host->ops->request_done)
host->ops->request_done(host, mrqs_done[i]);
else
mmc_request_done(host->mmc, mrqs_done[i]);
}
// 有异常,抛出相关打印
if (unexpected) {
pr_err("%s: Unexpected interrupt 0x%08x.\n",
mmc_hostname(host->mmc), unexpected);
sdhci_dumpregs(host);
}
return result;
}
8.2 sdhci_thread_irq
中断线程(下半部),处理被推迟的命令或可以被完成的请求,处理卡插拔,并重新触发扫卡逻辑
static irqreturn_t sdhci_thread_irq(int irq, void *dev_id)
{
struct sdhci_host *host = dev_id;
struct mmc_command *cmd;
unsigned long flags;
u32 isr;
// 被推迟的mrq_done在这里进行处理
while (!sdhci_request_done(host))
;
spin_lock_irqsave(&host->lock, flags);
// 插拔卡的标记获取
isr = host->thread_isr;
host->thread_isr = 0;
// 被推迟的命令获取
cmd = host->deferred_cmd;
// 如果命令非空,并且重试了还执行不成功
if (cmd && !sdhci_send_command_retry(host, cmd, flags))
// 强制完成拉倒
sdhci_finish_mrq(host, cmd->mrq);
spin_unlock_irqrestore(&host->lock, flags);
if (isr & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
struct mmc_host *mmc = host->mmc;
// 处理卡插拔,然后重新执行扫卡
mmc->ops->card_event(mmc);
mmc_detect_change(mmc, msecs_to_jiffies(200));
}
return IRQ_HANDLED;
}
9. 块设备
emmc或sd最终要注册成一个块设备供上层使用的。其实在注册章节,我们有一步是没有分析的,就是如何注册成一个块设备。以及作为一个块设备,是怎样被上层调用并进行数据通信的。
9.1 作为块设备的注册
如下图为实际作为块设备的注册流程,在mmc/core/block.c中会先进行mmc_blk_init,并进行驱动注册(注册mmc_driver,见下图),随后mmc_add_card执行时,经由device_add与实际驱动进行匹配,最终注册成一个mmcblk设备
I. mmc_blk_probe
<code>static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
char cap_str[10];
/*
* Check that the card supports the command class(es) we need.
*/
// 需要保证这个块设备可以读取
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
return -ENODEV;
// 申请一个工作队列
card->complete_wq = alloc_workqueue("mmc_complete",
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
if (unlikely(!card->complete_wq)) {
pr_err("Failed to create mmc completion workqueue");
return -ENOMEM;
}
// 获取扇区大小后执行mmc_blk_alloc_req
// 对于emmc设备来说,其容量是从ext_csd寄存器的sectors域获取
// 对于sd card来说,其容量是从csd的capacity域获取的
// 计算方法memory capacity = (C_SIZE+1) * 512K byte
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
// 获取卡容量
string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2,
cap_str, sizeof(cap_str));
// 打印关键信息,块设备号,mmc号+RCA地址,卡名,和容量
pr_info("%s: %s %s %s %s\n",
md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
cap_str, md->read_only ? "(ro)" : "");
// 为物理分区(例如rpmb分区)分配和设置mmc_blk_data下面分析
if (mmc_blk_alloc_parts(card, md))
goto out;
// dev->driver_data = md;
dev_set_drvdata(&card->dev, md);
// 将mmc_blk构造的gendisk注册到系统中,识别分区,生成对应的块设备
// 然后在将其加入到sysfs中,具体就不解析了
if (mmc_add_disk(md))
goto out;
list_for_each_entry(part_md, &md->part, part) {
// 列出这个设备的所有分区,并add_disk
if (mmc_add_disk(part_md))
goto out;
}
/* Add two debugfs entries */
// debug_fs
mmc_blk_add_debugfs(card, md);
/* 电源管理删 */
return 0;
out:
mmc_blk_remove_parts(card, md);
mmc_blk_remove_req(md);
return 0;
}
II. mmc_blk_alloc_req
static struct 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)
{
struct mmc_blk_data *md;
int devidx, ret;
// 分配一个mmcblk的从设备号
devidx = ida_simple_get(&mmc_blk_ida, 0, max_devices, GFP_KERNEL);
if (devidx < 0) {
if (devidx == -ENOSPC)
dev_err(mmc_dev(card->host),
"no more device IDs available\n");
return ERR_PTR(devidx);
}
// 分配struct mmc_blk_data的空间
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
if (!md) {
ret = -ENOMEM;
goto out;
}
md->area_type = area_type;
/*
* Set the read-only status based on the supported commands
* and the write protect switch.
*/
// 是否只读?
md->read_only = mmc_blk_readonly(card);
// 调用alloc_disk分配一个gendisk结构体
md->disk = alloc_disk(perdev_minors);
if (md->disk == NULL) {
ret = -ENOMEM;
goto err_kfree;
}
// 初始化挂载其他物理分区的链表和挂载rpmbs物理分区的链表
INIT_LIST_HEAD(&md->part);
INIT_LIST_HEAD(&md->rpmbs);
// 使用计数设置为1
md->usage = 1;
// 初始化队列,该函数也及其复杂,涉及块设备和队列,以后分析块设备层在分析
// 主要是设置mmc_mq_ops,见下图
// 通过blk_mq_init_queue申请md->queue
// blk_queue_rq_timeout(mq->queue, 60 * HZ);超时时间60HZ
// 执行mmc_setup_queue,初始化一些锁和工作队列,具体不分析
ret = mmc_init_queue(&md->queue, card);
if (ret)
goto err_putdisk;
md->queue.blkdata = md;
/*
* Keep an extra reference to the queue so that we can shutdown the
* queue (i.e. call blk_cleanup_queue()) while there are still
* references to the 'md'. The corresponding blk_put_queue() is in
* mmc_blk_put().
*/
if (!blk_get_queue(md->queue.queue)) {
mmc_cleanup_queue(&md->queue);
ret = -ENODEV;
goto err_putdisk;
}
md->disk->major = MMC_BLOCK_MAJOR;
// 每个设备的子设备号,其中
// perdev_minors = CONFIG_MMC_BLOCK_MINORS(32)
// 可以见我手里的设备,分区和子设备号,是符合的
md->disk->first_minor = devidx * perdev_minors;
md->disk->fops = &mmc_bdops;
md->disk->private_data = md;
// 关联gendisk和request_queue
md->disk->queue = md->queue.queue;
md->parent = parent;
// 设置gendisk的只读属性
set_disk_ro(md->disk, md->read_only || default_ro);
md->disk->flags = GENHD_FL_EXT_DEVT;
if (area_type & (MMC_BLK_DATA_AREA_RPMB | MMC_BLK_DATA_AREA_BOOT))
md->disk->flags |= GENHD_FL_NO_PART_SCAN
| GENHD_FL_SUPPRESS_PARTITION_INFO;
// 设置gendisk的容量,size是以扇区为单位
set_capacity(md->disk, size);
/* 删除部分 */
return md;
err_putdisk:
put_disk(md->disk);
err_kfree:
kfree(md);
out:
ida_simple_remove(&mmc_blk_ida, devidx);
return ERR_PTR(ret);
}
III. mmc_blk_alloc_parts
对于emmc设备在执行mmc_decode_ext_csd过程中,会根据扩展csd寄存器判断boot0/1,rpmb的大小是否能够正常访问,以及是否存在gp分区(可配的)。一个emmc的标准分区如下。在解码扩展csd时并通过mmc_part_add将识别到的分区进行添加(顺带添加了分区标识)
<code>static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md)
{
int idx, ret;
if (!mmc_card_mmc(card))
return 0;
for (idx = 0; idx < card->nr_parts; idx++) {
if (card->part[idx].area_type & MMC_BLK_DATA_AREA_RPMB) {
/*
* RPMB partitions does not provide block access, they
* are only accessed using ioctl():s. Thus create
* special RPMB block devices that do not have a
* backing block queue for these.
*/
ret = mmc_blk_alloc_rpmb_part(card, md,
card->part[idx].part_cfg,
card->part[idx].size >> 9,
card->part[idx].name);
if (ret)
return ret;
} else if (card->part[idx].size) {
ret = mmc_blk_alloc_part(card, md,
card->part[idx].part_cfg,
card->part[idx].size >> 9,
card->part[idx].force_ro,
card->part[idx].name,
card->part[idx].area_type);
if (ret)
return ret;
}
}
return 0;
}
以普通分区添加为例:
static int mmc_blk_alloc_part(struct mmc_card *card,
struct mmc_blk_data *md,
unsigned int part_type,
sector_t size,
bool default_ro,
const char *subname,
int area_type)
{
char cap_str[10];
struct mmc_blk_data *part_md;
// 依旧是调用mmc_blk_alloc_req,申请子分区的结构
part_md = mmc_blk_alloc_req(card, disk_to_dev(md->disk), size, default_ro, subname, area_type);
if (IS_ERR(part_md))
return PTR_ERR(part_md);
part_md->part_type = part_type;
// 加入到链表中
list_add(&part_md->part, &md->part);
// 设置容量
string_get_size((u64)get_capacity(part_md->disk), 512, STRING_UNITS_2, cap_str, sizeof(cap_str));
// 打印见下图
pr_info("%s: %s %s partition %u %s\n",
part_md->disk->disk_name, mmc_card_id(card),
mmc_card_name(card), part_md->part_type, cap_str);
return 0;
}
实际设备识别后的分区,当然这里没包括用户分区
IV. 用户分区识别
执行mmc_add_disk时会进行disk注册,这期间会执行块设备的分区扫描,具体执行函数路径为如下图所示
然后check_partition根据check_part支持的分区划分进行匹配,比如我手里的设备,emmc使用的cmdline分区
在具体的这里就不在分析了,后续有空分析一下block层在具体分析。
9.2 block层的调用
如下图,使用ftrace跟踪的一次sync后,数据写入emmc的函数调用情况
执行一次数据写入,跟踪如下
具体的这里就不在详细分析了,我们只来简单看一下mmc_blk_mq_issue_rq函数
I. mmc_blk_mq_issue_rq
emmc作为块设备,经过一系列块层调用(工作队列),最后一定会执行到该函数,进行读写或者刷cache的请求,比如sync,最终调用到mmc_blk_issue_flush;数据写入,最终调用到mmc_blk_mq_issue_rw_rq。然后在执行部分数据处理,最终又调用到了mmc_start_request,完成数据读写。至此,mmc子系统和block层也就彻底连起来了
<code>enum mmc_issued mmc_blk_mq_issue_rq(struct mmc_queue *mq, struct request *req)
{
struct mmc_blk_data *md = mq->blkdata;
struct mmc_card *card = md->queue.card;
struct mmc_host *host = card->host;
int ret;
ret = mmc_blk_part_switch(card, md->part_type);
if (ret)
return MMC_REQ_FAILED_TO_START;
switch (mmc_issue_type(mq, req)) {
case MMC_ISSUE_SYNC:
ret = mmc_blk_wait_for_idle(mq, host);
if (ret)
return MMC_REQ_BUSY;
switch (req_op(req)) {
case REQ_OP_DRV_IN:
case REQ_OP_DRV_OUT:
mmc_blk_issue_drv_op(mq, req);
break;
case REQ_OP_DISCARD:
mmc_blk_issue_discard_rq(mq, req);
break;
case REQ_OP_SECURE_ERASE:
mmc_blk_issue_secdiscard_rq(mq, req);
break;
case REQ_OP_FLUSH:
mmc_blk_issue_flush(mq, req);
break;
default:
WARN_ON_ONCE(1);
return MMC_REQ_FAILED_TO_START;
}
return MMC_REQ_FINISHED;
case MMC_ISSUE_DCMD:
case MMC_ISSUE_ASYNC:
switch (req_op(req)) {
case REQ_OP_FLUSH:
if (!mmc_cache_enabled(host)) {
blk_mq_end_request(req, BLK_STS_OK);
return MMC_REQ_FINISHED;
}
ret = mmc_blk_cqe_issue_flush(mq, req);
break;
case REQ_OP_READ:
case REQ_OP_WRITE:
if (mq->use_cqe)
ret = mmc_blk_cqe_issue_rw_rq(mq, req);
else
ret = mmc_blk_mq_issue_rw_rq(mq, req);
break;
default:
WARN_ON_ONCE(1);
ret = -EINVAL;
}
if (!ret)
return MMC_REQ_STARTED;
return ret == -EBUSY ? MMC_REQ_BUSY : MMC_REQ_FAILED_TO_START;
default:
WARN_ON_ONCE(1);
return MMC_REQ_FAILED_TO_START;
}
}
10. 参考文档
emmc5.1协议+4.51中文版SD3.0协议PartA2_SD Host_Controller_Simplified_Specification_Ver4.20https://blog.csdn.net/lickylin/article/details/104717742http://www.wowotech.net/basic_tech/mmc_sd_sdio_intro.htmlSD 卡 和 microSD 卡速度等级指南- 金士顿科技 (kingston.com.cn)https://blog.csdn.net/u013606261/article/details/112567922https://blog.csdn.net/swanghn/article/details/112643632https://www.cnblogs.com/cslunatic/p/3678045.htmlhttps://www.cnblogs.com/linhaostudy/p/10790115.htmlhttps://www.cnblogs.com/linhaostudy/p/10813200.html#_label1_0https://blog.csdn.net/wzm_c1386666/article/details/120618363 (对__mmc_claim_host讲解)
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。