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

<code>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讲解)



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。