LINUX之MMC子系统分析

目录

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

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函数分析

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

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插入状态的检测
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等
/**
 *  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了。

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的方式有如下几种:

  1. mmc card是不可移除的(如emmc),则在mmc host初始化时设置mmc host为nonremovable(仅在mmc_add_host时,调用mmc_detect_change完成一次mmc rescan,此后不再执行mmc rescan操作);
  2. mmc host支持mmc card detect功能(通过提供mmc detect中断,进行mmc card detect),此种情况在mmc card detect中断对应的中断处理接口中,调用mmc_detect_change接口,对延迟工作队列进行调度,从而调用接口mmc_rescan,完成一次mmc card的rescan;
  3. mmc host不支持mmc card detect功能,针对此情形,可以设置mmc host为poll模式。针对此种模式,在mmc_add_host执行一次mmc rescan时,在mmc rescan的最后会执行延迟1s调度该延迟工作队列,从而完成每秒执行一次mmc rescan操作。
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为例进行分析

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

该函数巨长,因此源码部分删减,该函数主要是协议的收发并配置卡,主要操作如下:

  1. mmc_go_idle,发送cmd0,置emmc进idle状态
  2. mmc_send_op_cond,发送cmd1,获取ocr寄存器值(包括bit30,访问模式)
  3. mmc_send_cid,发送cmd2,获取cid寄存器值
  4. 申请mmc_card结构空间,并部分初始化
  5. 发送cmd3,将rca赋值给emmc,此时emmc进入standby状态,并将信号从开漏转为上推拉模式
  6. 发送cmd9,获取csd寄存器,并解码cid和csd
  7. 如果卡支持DSR寄存器,host需要发送cmd4对dsr进行配置,
  8. 发送cmd7,card由stand-by切换到transfer-mode
  9. 发送cmd8 读取扩展CSD,并解析。注:此时设备已经处于发送模式
  10. 发送cmd6设置扩展csd的175寄存器,设置值为0,设置擦除组定义大小,这里使用默认大小
  11. 发送cmd6设置扩展csd的179寄存器,访问权限设置,设置成默认(只访问用户分区)
  12. 发送cmd6设置扩展csd的34寄存器,设置值为1,设置主机下电通知设备
  13. 开始调速HS400ES/HS200/HS,以及位宽选择(这个过程不详细分析了,也是各种发命令进行设置)
  14. 发送cmd6设置扩展csd的187寄存器,根据位宽选择一个合适的功耗等级
  15. 发送cmd6配置扩展csd的161寄存器,使能HPI (High Priority Interrupt高优先级中断,该机制可以中断一些还没有完成的优先级比较低的操作,来满足对高优先级操作的需求)
  16. 发送cmd6配置扩展csd的15寄存器,使能CQE
  17. mmc_host->card 赋值,识别卡到这里基本就成功一半了
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中,至此识卡完成

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

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

在这里插入图片描述

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

在这里插入图片描述

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寄存器实现数据收发
在这里插入图片描述

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发送,主要是对控制器的一些操作,配置各个寄存器
在这里插入图片描述

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
在这里插入图片描述

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,相当于不需要任何回复。

/*
 * 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,到时后唤起对应定时器
在这里插入图片描述

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
在这里插入图片描述

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

在这里插入图片描述

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进行通信

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

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将识别到的分区进行添加(顺带添加了分区标识)
在这里插入图片描述

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层也就彻底连起来了

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. 参考文档

  1. emmc5.1协议+4.51中文版
  2. SD3.0协议
  3. PartA2_SD Host_Controller_Simplified_Specification_Ver4.20
  4. https://blog.csdn.net/lickylin/article/details/104717742
  5. http://www.wowotech.net/basic_tech/mmc_sd_sdio_intro.html
  6. SD 卡 和 microSD 卡速度等级指南- 金士顿科技 (kingston.com.cn)
  7. https://blog.csdn.net/u013606261/article/details/112567922
  8. https://blog.csdn.net/swanghn/article/details/112643632
  9. https://www.cnblogs.com/cslunatic/p/3678045.html
  10. https://www.cnblogs.com/linhaostudy/p/10790115.html
  11. https://www.cnblogs.com/linhaostudy/p/10813200.html#_label1_0
  12. https://blog.csdn.net/wzm_c1386666/article/details/120618363 (对__mmc_claim_host讲解)
  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜暝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值