赞
踩
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等等)。
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的访问速度
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)
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
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是时钟上下沿都进行通信]通信模式)。
一般协议传输无应答(no response)命令和无数据(no data)命令。无应答命令只需要传输CMD,不需要应答,命令即携带了所有信息。无数据命令需要在CMD传输之后,返回相应的应答,但无数据传输。应答格式有多种
写数据的传输是分块进行的。数据块后面总是跟着CRC位。定义了单块和多块操作。多块操作模式更适合更快的写入操作。当CMD线路上出现停止命令时,多块传输终止。块写操作期间通过 Data0 信号线指示Busy 状态。
当无数据传输时,DAT0-DAT7总线上为高电平。传输数据块由各个DAT线上的起始位(低),以及随后连续的数据流所组成。数据流以各条线上的停止位(高)终结。数据传输是以时钟信号同步的
除通用的协议之外,eMMC和SDIO协议已有较大不同,因此需要分别说明,比如卡模式,SD卡有3种eMMC有5种(3种和SD卡模式相同,另外多出2种),并且有各自不同的卡状态。
非活动模式:设备工作电压范围或访问模式无效,则设备进入非活动模式。设备也可以通过使用GO_INACTIVE_STATE命令(CMD15)进入非活动模式
设备识别模式:复位后,Host 处于卡识别模式,寻找总线上的新卡。卡将处于这个模式,直到接收到Send_RCA命令(CMD3)
数据传输模式:卡的 RCA 首次发布后,卡进入数据传输模式。Host 识别完毕总线上的所有卡后,进入数据传输模式
引导模式:上电周期后,接受参数为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模式。
协议定义了4种命令:
所有的命令有固定 48Bit 编码长度,需要传输时间1.92us@25MHz 和0.96us@50MHz
在SD协议中Class 0,2,4,5 和8 是强制的,应该被所有的卡支持,在emmc协议中Class 0是必需的,所有的设备均应支持。其他类对特定类型设备可能是必需的,也可能是可选的
下面皆以eMMC为例,CMD0~15为基本命令
CMD16~18,CMD21为块读命令
CMD23~27,块写
CMD35,CMD36,CMD38块擦除
所有的应答均通过命令线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。
协议中规定的几个寄存器分别为:OCR、CID、CSD(扩展CSD)、RCA、DSR(可选),和SDIO协议中的SCR。下面皆以eMMC寄存器为例
32比特的工作条件寄存器(OCR)寄存着设备的VDD电压概况和访问模式指示。另外,此寄存器包括一个状态信息位。此状态位当设备上电例程结束时置位。所有设备均应实现OCR寄存器。
该寄存器的作用:
卡的识别寄存器(CID)是一个128Bit 宽度。其包括了卡的鉴别信息,其用于在卡的鉴别相中。每一个读/写(RW)卡应该有一个唯一的鉴别号。其中MID号由JEDEC管理,分配给eMMC制造商,每个制造商的MID都是独一无二的(同理SD卡的MID由SD-3C控制并分配)。
设备专用数据寄存器(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复位后复位,不可读
可写的16-bit 设备相对地址(RCA)寄存器,载有在设备识别期间主机分配的设备地址。此地址用于设备识别例程之后寻址的主机-设备通讯。RCA寄存器缺省值是0x0001。值0x0000是为将所有设备以CMD7置于Stand-by状态而保留的。
扩展CSD寄存器定义了设备属性和选定的模式。它长512字节。高320字节是属性段,定义了设备能力,不能被主机更改。低192字节是模式段,定义了设备的工作配置。这些模式可以被主机通过SWITCH命令改变。具体寄存器略
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; };
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); };
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; };
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); };
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 */ };
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; };
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 */ };
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 */ };
注册流程按照mmc_host和mmc_core两层分别分析,从mmc_host层的主机控制器的probe开始进行分析。mmc控制器的注册流程如下图所示:
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要做的事情如下:
再次说明:应实际的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的开发)
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; }
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); }
sdhci_host:对于sdhci类host(也就是符合sdhci标准)的host来说,直接通过sdhci core来实现host controller的使用。而sdhci core会为对应的host抽象出对应的struct sdhci_host结构体进行管理。对应代码:drivers/mmc/host/sdhci.c
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; }
在add_host之前,已经完成的底层解析,并传上来的sdhci_host中包含以下信息:
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; }
完成控制器注册和初始化
mmc core通过struct mmc_host来管理host。不管是什么类型的host,最终都是要实现出对应的mmc_host并注册到mmc core中交由mmc子系统进行管理。对应代码drivers/mmc/core/host.c。为底层host controller driver实现mmc host的申请以及注册的API等等,以及host相关属性的实现。其中和注册相关的两个接口如下:
底层host controller驱动调用,用来分配一个struct mmc_host结构体,将其于mmc_host_class关联,并且做部分初始化操作
主要工作:
/** * 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; }
底层host controller驱动调用,注册mmc_host到设备驱动中,添加到sys类下面,并设置相应的debug目录。然后启动mmc_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; }
mmc core主模块是mmc core的实现核心,对应代码位置drivers/mmc/core/core.c。其中mmc core初始化包括注册mmc bus、mm host class等等
负责初始化整个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中,初始化这里不详细分析
当底层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); }
这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)); } }
到这里触发扫卡工作流程
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);
}
当_mmc_detect_change执行后,触发了扫卡的工作队列,并且使能了卡检测,则正式进入扫卡识别阶段,通过唤醒工作队列,执行mmc_rescan,如下图为实际设备识别emmc的流程(注:dump_stack加到mmc_add_card函数的最后)
用于检测host的卡槽状态,并对状态变化做相应的操作。有card插入时,重新扫描mmc card,mmc card 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); }
以一定频率搜索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; }
这里以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; }
该函数巨长,因此源码部分删减,该函数主要是协议的收发并配置卡,主要操作如下:
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; }
将卡正式进行注册,打印一些卡信息,并注册到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; }
可以查看对应host的caps、卡的ext_csd(emmc)、卡的status(通过cmd13获取,详情见协议手册中6.13 设备状态)和卡的state(见下图)
本章节分析mmc子系统的通信流程,以上面emmc扫卡识别中,使用的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; }
该函数是命令发送的核心
/** * 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; }
实际上该函数主要执行两步: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);
}
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; }
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; }
这里涉及到控制器层面操作了,主要是通过配置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); }
执行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; }
其中D00的含义如下,即当该bit为1时不能发送命令
D01的含义如下,同D00,该bit为1时不能发送数据
其中D03-D02的含义如下
其中D04的含义如下
其中D013-D08的含义如下
其中D07-D06的含义如下
其中D05的含义如下
其中D01-D00的含义如下
控制器在初始化阶段已经配置了默认的中断,中断配置如下,当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); }
根据描述可知,sdhci主机控制器的中断状态想要获取到的话,34H和38H都要使能才行
到这里,命令发送完成,执行相关处理,在此之前,我们来看下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); }
其中Response Field对应协议中应答的具体字段,Response Register是对应寄存器的那些值来进行表示。
如果数据传输出现超时,在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); }
实际上主体还是唤醒工作队列执行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);
}
只做了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); }
执行完该函数,才意味着完成了一次通信
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; }
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); }
本章节分析mmc子系统的通信流程,以上面emmc扫卡识别中,使用的mmc_read_ext_csd ->mmc_get_ext_csd ->mmc_send_cxd_data (扩展寄存器读)为例进行分析。实际上对比命令通信,数据通信多了设置数据通信量(写04H/06H)并执行数据通信操作,中断和超时处理略有差异
在上一层级,调用参数为: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; }
在执行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); }
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);
// 一次读写入的块大小*读写入的块数量 = 总计读写的数据量
}
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); } } } }
在这里完成真正的数据读写
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"); }
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); } }
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); }
在前文已经初步分析过了中断,数据通信过程中的sdhci_data_irq和sdhci_cmd_irq,实际上中断还要处理其他事件,比如sd卡的热插拔等,直接分析源码。
主机控制器的中断在注册阶段通过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; }
中断线程(下半部),处理被推迟的命令或可以被完成的请求,处理卡插拔,并重新触发扫卡逻辑
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; }
emmc或sd最终要注册成一个块设备供上层使用的。其实在注册章节,我们有一步是没有分析的,就是如何注册成一个块设备。以及作为一个块设备,是怎样被上层调用并进行数据通信的。
如下图为实际作为块设备的注册流程,在mmc/core/block.c中会先进行mmc_blk_init,并进行驱动注册(注册mmc_driver,见下图),随后mmc_add_card执行时,经由device_add与实际驱动进行匹配,最终注册成一个mmcblk设备
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; }
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); }
对于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; }
实际设备识别后的分区,当然这里没包括用户分区
执行mmc_add_disk时会进行disk注册,这期间会执行块设备的分区扫描,具体执行函数路径为如下图所示
然后check_partition根据check_part支持的分区划分进行匹配,比如我手里的设备,emmc使用的cmdline分区
在具体的这里就不在分析了,后续有空分析一下block层在具体分析。
如下图,使用ftrace跟踪的一次sync后,数据写入emmc的函数调用情况
执行一次数据写入,跟踪如下
具体的这里就不在详细分析了,我们只来简单看一下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; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。