当前位置: 首页 > news >正文

LINUX之MMC子系统分析

目录

  • 1. 概念
    • 1.1 MMC卡
    • 1.2 SD卡
    • 1.3 SDIO
  • 2. 总线协议
    • 2.1 协议
    • 2.2 一般协议
    • 2.3 写数据
    • 2.4 读数据
    • 2.5 卡模式
      • 2.5.1 SD卡模式
      • 2.5.2 eMMC模式
    • 2.6 命令
      • 2.6.1 命令类
      • 2.6.2 详细命令
    • 2.7 应答
    • 2.8 寄存器
      • 2.8.1 OCR
      • 2.8.2 CID
      • 2.8.3 CSD
      • 2.8.4 RCA
      • 2.8.5 扩展CSD
  • 3. 关键结构
    • 3.1 struct sdhci_host
    • 3.2 struct sdhci_ops
    • 3.3 struct mmc_host
    • 3.4 struct mmc_host_ops
    • 3.5 struct mmc_card
    • 3.6 struct mmc_request
    • 3.7 struct mmc_command
    • 3.8 struct mmc_data
  • 4. 注册
    • 4.1 mmc_host层
      • I. sdhci-xxxx
      • II. sdhci_pltfm
      • III. sdhci
        • a. sdhci_alloc_host
        • b. sdhci_add_host
    • 4.2 mmc_core层
      • I. host
        • a. mmc_alloc_host
        • b. mmc_add_host
      • II. core
        • a. mmc_init
        • b. mmc_start_host
        • c. mmc_claim/release_host
        • d. _mmc_detect_change
  • 5. 扫卡(识别)
    • 5.1 mmc_rescan
    • 5.2 mmc_rescan_try_freq
    • 5.3 mmc_attach_xx(主要以emmc进行分析)
    • 5.4 mmc_init_card
      • 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
      • I. Debugfs下可以查看的一些属性
    • 5.6 e•MMC状态图(设备识别模式)
    • 5.7 e•MMC状态图(数据传输模式)
  • 6. 通信(命令)
    • 6.1 mmc_go_idle
    • 6.2 mmc_wait_for_cmd
    • 6.3 mmc_wait_for_req
    • 6.4 __mmc_start_req
    • 6.5 mmc_start_request
    • 6.6 sdhci_request
      • I. sdhci_send_command
        • a. sdhci主机控制器24H寄存器
        • b. sdhci主机控制器08H寄存器
        • c. sdhci主机控制器0CH寄存器
        • d. sdhci主机控制器0EH寄存器
      • II. sdhci_cmd_irq
        • a. sdhci主机控制器34H寄存器
        • b. sdhci主机控制器38H寄存器
        • c. sdhci主机控制器30H寄存器(中断状态)
        • d. sdhci主机控制器32H寄存器(中断错误状态)
      • III. sdhci_finish_command
        • a. sdhci主机控制器10H寄存器
      • IV. sdhci_timeout_timer
    • 6.7 sdhci_finish_mrq
      • I. __sdhci_finish_mrq
      • II. sdhci_request_done
    • 6.8 mmc_wait_for_req_done
  • 7. 通信(数据)
    • 7.1 mmc_send_cxd_data
    • 7.2 sdhci_prepare_data
    • 7.3 sdhci_set_block_info
      • I. sdhci主机控制器04H寄存器
      • II. sdhci主机控制器06H寄存器
    • 7.4 sdhci_data_irq
    • 7.5 sdhci_transfer_pio
      • I. sdhci主机控制器20H寄存器
    • 7.6 sdhci_finish_data
    • 7.7 sdhci_timeout_data_timer
  • 8. 中断
    • 8.1 sdhci_irq
    • 8.2 sdhci_thread_irq
  • 9. 块设备
    • 9.1 作为块设备的注册
      • I. mmc_blk_probe
      • II. mmc_blk_alloc_req
      • III. mmc_blk_alloc_parts
      • IV. 用户分区识别
    • 9.2 block层的调用
      • I. mmc_blk_mq_issue_rq
  • 10. 参考文档

1. 概念

MMC(Multi-Media Card)子系统是Linux内核中的一个模块,主要用于管理SD卡和eMMC等可移动存储设备。使用MMC子系统可以使得SD卡等存储设备在Linux内核中被识别为一个块设备,并可以使用标准的块设备驱动程序进行管理。同时,MMC子系统也为SDIO卡提供了标准的接口,便于开发各种不同类型的SDIO卡设备驱动。

对与MMC,主要包括几个部分:MMC控制器、MMC总线、card

对于卡而言,包括MMC卡(7pin,支持MMC和spi两种通信模式)、SD卡(9pin,支持sd和spi两种通信模式)、TF卡(8pin,支持sd和spi两种通信模式),这些卡其总线规范类似,都是从MMC总线规范演化过来的

基于MMC这种通信方式,又演化了SDIO,SDIO强调的是IO一种总线,可以链接任何支持SDIO的外设(包括蓝牙设备、wifi设备等)。

CPU、MMC controller、存储设备之间的关联如下图所示,主要包括了MMC controller、总线、存储卡等内容的连接,针对控制器与设备的总线连接,主要包括时钟、数据、命令三种类型的引脚,而这些引脚中的cd引脚主要用于卡的在位检测,当mmc controller检测到该位的变化后,则会进行mmc card的注册或注销操作
在这里插入图片描述
MMC从本质上看,是一种用于固态非易失性存储的内存卡(memory card)规范,定义了诸如卡的形态、尺寸、容量、电气信号、和主机之间的通信协议等方方面面的内容。

从1997年MMC规范发布至今,基于不同的考量(物理尺寸、电压范围、管脚数量、最大容量、数据位宽、clock频率、安全特性、是否支持SPI mode、是否支持DDR[DDR: Dual data rate] mode、等等),进化出了MMC、SD、microSD、SDIO、eMMC等不同的规范。

在这里插入图片描述
下面是不同接口规范的封装引脚和大小对比图,当SD接口要和MMC兼容时,8和9脚留空即可
在这里插入图片描述
MMC、SD、SDIO的技术本质是一样的(使用相同的总线规范,等等),都是从MMC规范演化而来;

MMC强调的是多媒体存储(MM,MultiMedia);
SD强调的是安全和数据保护(S,Secure);
SDIO是从SD演化出来的,强调的是接口(IO,Input/Output),不再关注另一端的具体形态(可以是WIFI设备、Bluetooth设备、GPS等等)。

1.1 MMC卡

MMC(Multimedia Card)卡是一种较早的记忆卡标准,目前已经被SD标准所取代,目前基本上仅存eMMC。卡的管脚有VDD、GND、RST、CLK、CMD和DATA等,VDD和GND提供power,RST用于复位,CLK、CMD和DATA为MMC总线协议的物理通道
在这里插入图片描述

CLK有一条,提供同步时钟,可以在CLK的上升沿(或者下降沿,或者上升沿和下降沿)采集数据;
CMD有一条,用于传输双向的命令。
DATA用于传输双向的数据,根据MMC的类型,可以有一条(1-bit)、四条(4-bit)或者八条(8-bit)。

电压范围为1.65V和3.6V,根据工作电压的不同,MMC卡可以分为两类:
High Voltage MultiMediaCard,工作电压为2.7V~3.6V。
Dual Voltage MultiMediaCard,工作电压有两种,1.70V1.95V和2.7V3.6V,CPU可以根据需要切换。

CLK的频率范围,包括0-20MHz、0-26MHz、0-52MHz等几种,结合数据线宽度,基本决定了MMC的访问速度
在这里插入图片描述
在这里插入图片描述

1.2 SD卡

SD(Secure Digital)是一种flash memory card的标准,是一般常见的SD记忆卡。SD协议支持三种模式:4-wire mode, 1-wire mode, SPI mode。三种模式的信号定义如下:
在这里插入图片描述
在这里插入图片描述
SD卡按供电范围划分,分两种:
High Voltage SD Memory Card: 操作的电压范围在2.7-3.6V
UHS-II SD Memory Card: 操作的电压范围VDD1: 2.7-3.6V, VDD2: 1.70-1.95V

SD卡按总线速度模式来分,有下面几种:
Default Speed mode: 3.3V供电模式,频率上限25MHz,速度上限 12.5MB/sec
High Speed mode: 3.3V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR12: UHS-I卡, 1.8V供电模式,频率上限25MHz,速度上限 12.5MB/sec
SDR25: UHS-I卡, 1.8V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR50: UHS-I卡, 1.8V供电模式,频率上限100MHz,速度上限 50MB/sec
SDR104: UHS-I卡, 1.8V供电模式,频率上限208MHz,速度上限 104MB/sec
DDR50: UHS-I卡, 1.8V供电模式,频率上限50MHz,性能上限 50MB/sec
UHS156: UHS-II RCLK Frequency Range 26MHz - 52MHz, up to 1.56Gbps per lane.

注:UHS(Ultra High Speed)

1.3 SDIO

SDIO(Secure Digital I/O):就是SD的I/O接口(interface)的意思,更具体的说明,SD本来是记忆卡的标准,但是现在也可以把SD拿来插上一些外围接口使用,这样的技术便是SDIO

SDIO总线和USB总线类似,SDIO总线也有两端,其中一端是主机(HOST)端,另一端是设备端(DEVICE),采用HOST- DEVICE这样的设计是为了简化DEVICE的设计,所有的通信都是由HOST端发出命令开始的。在DEVICE端只要能解析HOST的命令,就可以同HOST进行通信了,SDIO的HOST可以连接多个DEVICE

2. 总线协议

2.1 协议

SDIO(EMMC通用,只不过数据线不一样)协议,其中包括“无数据传输的一般命令”,“有数据传输的写命令”,“有数据传输的读命令”。协议包含三个要素:命令Command,应答Response和数据Data。

Command:由HOST发送,DEVICE接收,在CMD信号线上传输。每一个命令Token都由一个起始位(’0’)前导,以一个停止位(’1’)终止。总长度是48比特。每一个Token都用CRC保护,因此可以检测到传输错误,可重复操作。
在这里插入图片描述
Response:由DEVICE发送,HOST接收,在CMD信号线上传输。应答根据不同命令分为4种(R1R3,R6)/EMMC有5种(R1R5),长度有48位或136位。
在这里插入图片描述
Data:数据是双向的传送的。可以设置为1线/4线模式,eMMC可以8线。数据是通过DAT0-DAT3/ DAT7信号线传输的(注:下图是SDR[区别DDR是时钟上下沿都进行通信]通信模式)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 一般协议

一般协议传输无应答(no response)命令和无数据(no data)命令。无应答命令只需要传输CMD,不需要应答,命令即携带了所有信息。无数据命令需要在CMD传输之后,返回相应的应答,但无数据传输。应答格式有多种
在这里插入图片描述

2.3 写数据

写数据的传输是分块进行的。数据块后面总是跟着CRC位。定义了单块和多块操作。多块操作模式更适合更快的写入操作。当CMD线路上出现停止命令时,多块传输终止。块写操作期间通过 Data0 信号线指示Busy 状态。
在这里插入图片描述

2.4 读数据

当无数据传输时,DAT0-DAT7总线上为高电平。传输数据块由各个DAT线上的起始位(低),以及随后连续的数据流所组成。数据流以各条线上的停止位(高)终结。数据传输是以时钟信号同步的
在这里插入图片描述

2.5 卡模式

除通用的协议之外,eMMC和SDIO协议已有较大不同,因此需要分别说明,比如卡模式,SD卡有3种eMMC有5种(3种和SD卡模式相同,另外多出2种),并且有各自不同的卡状态。

2.5.1 SD卡模式

非活动模式:设备工作电压范围或访问模式无效,则设备进入非活动模式。设备也可以通过使用GO_INACTIVE_STATE命令(CMD15)进入非活动模式

设备识别模式:复位后,Host 处于卡识别模式,寻找总线上的新卡。卡将处于这个模式,直到接收到Send_RCA命令(CMD3)

数据传输模式:卡的 RCA 首次发布后,卡进入数据传输模式。Host 识别完毕总线上的所有卡后,进入数据传输模式

在这里插入图片描述

2.5.2 eMMC模式

引导模式:上电周期后,接受参数为0xF0F0F0F0的CMD0或硬件复位信号有效,设备处于引导模式。

设备识别模式:在引导模式结束或主机、设备不支持引导操作模式时,设备处于设备识别模式。设备将在此模式下,直至接收到SET_RCA命令(CMD3)。在设备识别模式下,主机复位设备,验证工作电压范围和访问模式,识别设备并为总线上的设备分配相对设备地址(RCA)在设备识别模式下的所有数据通讯都仅采用命令线CMD

中断模式:主机与设备同时进入中断模式。在中断模式下没有数据传输。唯一允许的消息是来自主机或设备的中断服务请求。e•MMC系统的中断模式使主机能够向从机(设备)授予同时传输的许可。这种模式减少了主机轮询的负担,因而降低了系统功耗,同时保持了主机对设备服务请求的足够的责任。支持e•MMC中断模式是可选的,对主机和设备都是如此

数据传输模式:一旦分配了RCA,设备就进入数据传输模式。主机在识别总线上的设备后即进入数据传输模式。当设备处于stand-by状态,在CMD和DAT线上的通讯都将以推拉模式执行。直到主机知道CSD寄存器内容之前,fPP时钟速率都必须保持在fOD。主机发送SEND_CSD(CMD9)来获取设备专用数据(CSD寄存器),如块长度、设备存储容量、最大时钟速率等等。
当设备处于Stand-by状态时,CMD7被用于通过参数中包含设备的相对地址来选定设备并将其置于Transfer状态。如果设备此前已经被选定并处于Transfer状态,则当CMD7通过参数中不等于该设备自己相对地址的任意地址来取消选定时,它与主机的连接被释放,并返回Stand-by状态。当CMD7以保留的相对设备地址0x0000发送时,该设备返回Stand-by状态。处于Transfer状态的设备接收到有设备自己的相对地址的CMD7时,将忽略该命令,也可能当作非法命令来处理。在设备被分配了一个RCA之后,就不再应答识别命令——CMD1、CMD2和CMD3

非活动模式:如果设备工作电压范围或访问模式无效,则设备进入非活动模式。设备也可以通过使用GO_INACTIVE_STATE命令(CMD15)进入非活动模式。上电周期后,设备将复位到Pre- idle模式。
在这里插入图片描述

2.6 命令

协议定义了4种命令:

  • 无应答广播命令(bc)
  • 有应答广播命令(bcr)
  • 寻址(点对点)命令(ac),无DAT线数据传输
  • 寻址数据传输(点对点)命令(adtc),数据在DAT线传输

所有的命令有固定 48Bit 编码长度,需要传输时间1.92us@25MHz 和0.96us@50MHz
在这里插入图片描述

2.6.1 命令类

在SD协议中Class 0,2,4,5 和8 是强制的,应该被所有的卡支持,在emmc协议中Class 0是必需的,所有的设备均应支持。其他类对特定类型设备可能是必需的,也可能是可选的
在这里插入图片描述

2.6.2 详细命令

下面皆以eMMC为例,CMD0~15为基本命令
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CMD16~18,CMD21为块读命令
在这里插入图片描述
CMD23~27,块写
在这里插入图片描述
CMD35,CMD36,CMD38块擦除
在这里插入图片描述
在这里插入图片描述

2.7 应答

所有的应答均通过命令线CMD发送。编码长度取决于应答类型。应答总是以起始位开始(总是‘0’),紧接表示传输方向的比特(设备= ‘0’)。下面表中标记为 ‘x’ 的值表示一个变量。除R3类型之外,所有应答将被CRC保护。每一个应答编码都以停止位结束(总是‘1’)

R1(正常应答类型):编码长度48 bit。bits 45:40表示应答相对的命令索引数字(0到63)。设备的状态编码用32 bit表示。(R1b额外在数据线 DAT0上发送可选的忙信号,其余与R1相同)
在这里插入图片描述
R2(CID、CSD寄存器):编码长度136 bit。CID寄存器的内容作为对CMD2和CMD10的应答发送。CSD寄存器的内容作为CMD9的应答发送。CID和CSD只有bit [127:1]被发送,保留的bit 0被应答的停止位替代
在这里插入图片描述
R3(OCR寄存器):编码长度48 bit。OCR寄存器的内容作为CMD1的应答发送
在这里插入图片描述
R4(快速I/O):编码长度48 bit。参数域包含被寻址设备的RCA、要读写的寄存器地址及其内容。如果操作成功参数中的结果位被置位。
在这里插入图片描述
R5(中断请求):编码长度48 bit。如果应答是主机生成的,参数中的RCA应为0。
在这里插入图片描述

2.8 寄存器

协议中规定的几个寄存器分别为:OCR、CID、CSD(扩展CSD)、RCA、DSR(可选),和SDIO协议中的SCR。下面皆以eMMC寄存器为例
在这里插入图片描述

2.8.1 OCR

32比特的工作条件寄存器(OCR)寄存着设备的VDD电压概况和访问模式指示。另外,此寄存器包括一个状态信息位。此状态位当设备上电例程结束时置位。所有设备均应实现OCR寄存器。
该寄存器的作用:

  • 存储Vdd电压曲线
  • 存储器件访问模式,从而可以知道器件是否为大容量设备(即设备容量大于2G,只有大容量设备才支持扇区访问模式,小容量设备只支持字节访问模式)
  • 指示设备上电过程(即bit31:如果设备没有完成上电例程,此位设置为低)
    在这里插入图片描述

2.8.2 CID

卡的识别寄存器(CID)是一个128Bit 宽度。其包括了卡的鉴别信息,其用于在卡的鉴别相中。每一个读/写(RW)卡应该有一个唯一的鉴别号。其中MID号由JEDEC管理,分配给eMMC制造商,每个制造商的MID都是独一无二的(同理SD卡的MID由SD-3C控制并分配)。
在这里插入图片描述

2.8.3 CSD

设备专用数据寄存器(CSD)提供设备内容访问方式的信息。CSD定义了数据格式、纠错类型、最长数据访问时间、数据传输速度、DSR寄存器是否可用等等。寄存器可编程的部分(标有W或E的项目,见下)可以用CMD27命令更改。

R: 只读
W: 可一次编程且不可读
R/W: 可一次编程且可读
W/E: 可多次写,其值在掉电、硬件复位和任何CMD0复位后保持,不可读
R/W/E: 可多次写,其值在掉电、硬件复位和任何CMD0复位后保持,可读
R/W/C_P: 在掉电和硬件复位清除值后(值不被CMD0复位清除)可写,可读
R/W/E_P: 可多次写,其值在掉电、硬件复位和任何CMD0复位后复位,可读
W/E_P: 可多次写,其值在掉电、硬件复位和任何CMD0复位后复位,不可读
在这里插入图片描述
在这里插入图片描述

2.8.4 RCA

可写的16-bit 设备相对地址(RCA)寄存器,载有在设备识别期间主机分配的设备地址。此地址用于设备识别例程之后寻址的主机-设备通讯。RCA寄存器缺省值是0x0001。值0x0000是为将所有设备以CMD7置于Stand-by状态而保留的。

2.8.5 扩展CSD

扩展CSD寄存器定义了设备属性和选定的模式。它长512字节。高320字节是属性段,定义了设备能力,不能被主机更改。低192字节是模式段,定义了设备的工作配置。这些模式可以被主机通过SWITCH命令改变。具体寄存器略

3. 关键结构

在这里插入图片描述

3.1 struct sdhci_host

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);// 设置,使能dmaint     (*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的devicestruct 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的标识,禁止搜索cardint         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绑定在一起的cardstruct 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_REGULATORbool            regulator_enabled; /* regulator state */
#endifstruct 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 settingvoid    (*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所属hoststruct 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 */// 该命令关联到哪个requeststruct 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 gpioret = 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;// 解析设备树中寄存器基址并直接ioremapioaddr = 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;elsehost->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->privatehost = 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_countret = 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_doneINIT_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->devhost->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_FSmmc_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/mmcret = mmc_register_bus();if (ret)return ret;// 注册mmc_host classret = mmc_register_host_class();if (ret)goto unregister_bus;// 注册sdio bus,这条bus称之为sdio_bus,节点:/sys/bus/sdioret = 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// 如果上下文相同,或者没有上下文但任务相同,可以申请该hostif (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));elsepm_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++,说明执行到这里该总线已经算被使用了// 引用计数+1mmc_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 hostmmc_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,100Kif (!mmc_rescan_try_freq(host, max(freq, host->f_min)))break;if (freqs[i] <= host->f_min)break;}// 释放该hostmmc_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)mmcif (!(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 ocrerr = 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;// 最后占用住该hostmmc_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_typecard = 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_cidmemcpy(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进行配置,发送cmd4if (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-modeerr = 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位rcadev_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";elsetype = "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 0001if (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);
#endifcard->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; // cmd0cmd.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_BUSYif (sdhci_data_line_cmd(cmd))mask |= SDHCI_DATA_INHIBIT; //如果发送数据,掩码置位数据bit/* We shouldn't wait for data inihibit for stop commands, eventhough 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);}// 写命令参数,到主机控制器08Hsdhci_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;elseflags = 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;elsetimeout += 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;elsehost->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的D0if (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_donequeue_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 = mrqsdhci_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_clksmmc_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);}// 如果发生了错误,执行finishif (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_datahost->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;elsemask = SDHCI_SPACE_AVAILABLE;/* 删除部分特性的特殊处理 */// 读状态寄存器24H,判断D10/D9while (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);elsesdhci_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;elsedata->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发stopif (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_donequeue_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]);elsemmc_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 bytemd = 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_diskif (mmc_add_disk(part_md))goto out;}/* Add two debugfs entries */// debug_fsmmc_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);// 使用计数设置为1md->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_queuemd->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);elseret = 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讲解)

相关文章:

LINUX之MMC子系统分析

目录 1. 概念1.1 MMC卡1.2 SD卡1.3 SDIO 2. 总线协议2.1 协议2.2 一般协议2.3 写数据2.4 读数据2.5 卡模式2.5.1 SD卡模式2.5.2 eMMC模式 2.6 命令2.6.1 命令类2.6.2 详细命令 2.7 应答2.8 寄存器2.8.1 OCR2.8.2 CID2.8.3 CSD2.8.4 RCA2.8.5 扩展CSD 3. 关键结构3.1 struct sdh…...

VulnHub:cengbox1

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…...

MySQL第一阶段:多表查询、事务

继续我的MySQL之旅&#xff0c;继续上篇的DDL、DML、DQL、以及一些约束&#xff0c;该到了多表查询和事务的学习总结&#xff0c;以及相关的案例实现&#xff0c;为未来的复习以及深入的理解做好知识储备。 目录 多表查询 连接查询 内连接 外连接 子查询 事务 事务简介…...

Java的序列化和反序列化

序列化&#xff1a; 将数据结构或对象转换成二进制串的过程 反序列化&#xff1a;将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 至于为什么要序列化和反序列化呢&#xff1f; 因为互联网的产生带来了机器间通讯的需求&#xff0c;而互联通讯的双方需要采用约…...

本地连接远程阿里云K8S

1.首先安装kubectl 1.1验证自己系统 uname -m 1.2 按照步骤安装 在 Linux 系统中安装并设置 kubectl | Kubernetes 1.3 阿里云配置 通过kubectl连接Kubernetes集群_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心 2.验证 阿里云config直接导出&#xff0c;直接扔到.…...

CasaOS设备使用Docker安装SyncThing文件同步神器并实现远程管理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

k210 图像操作详解(一)(直线检测、边缘检测、色块追踪)

1、直线检测 ##################################################################################################### # file main.py # author 正点原子团队(ALIENTEK) # version V1.0 # date 2024-01-17 # brief image图像特征检测实…...

【Java版数据结构】初识泛型

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 br />个人主页&#xff1a;Gu Gu Study专栏&#xff1a;Java版数据结构 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xff1…...

DevExpress WinForms自动表单布局,创建高度可定制用户体验(二)

使用DevExpress WinForms的表单布局组件可以创建高度可定制的应用程序用户体验&#xff0c;从自动安排UI控件到按比例调整大小&#xff0c;DevExpress布局和数据布局控件都可以让您消除与基于像素表单设计相关的麻烦。 P.S&#xff1a;DevExpress WinForms拥有180组件和UI库&a…...

vue中v-if和v-for

vue中v-if和v-for Vue 官方建议不要在同一个元素上同时使用 v-if 和 v-for 指令&#xff0c;主要有以下几个原因&#xff1a; 性能问题&#xff1a; 当 v-if 和 v-for 一起使用时&#xff0c;Vue 在每次渲染时都需要先执行循环&#xff0c;然后再对每个元素进行条件判断。这可能…...

【MySQL】根据binlog日志获取回滚sql的一个开发思路

根据binlog日志获取回滚sql的一个开发思路 需要获取的信息 thread_id 打开 mysql 客户端 开始时间 关闭 mysql 客户端 结束时间 binlog 匹配流程 指定 mysql 客户端 开始时间和结束时间 先匹配 thread_id 相同的 然后匹配 ^BEGIN$行和 ^COMMIT/*!*/;$行之间的数据 当匹…...

Kafka快速入门+SpringBoot简单的秒杀案例

1. 主题相关 1.1 创建主题 kafka-topics.sh --create --bootstrap-server [服务器地址] --replication-factor [副本数] --partitions [分区数] --topic [主题名]liberliber-VMware-Virtual-Platform:/home/zookeeper$ docker-compose exec kafka /bin/bash #进入kafka容器 b…...

Redis哨兵机制

哨兵机制&#xff1a; &#xff08;1&#xff09;监控&#xff1a;有一个哨兵集群&#xff0c;这个哨兵集群检测redis的主从集群。它是每隔1秒钟就向主从集群中的节点发送心跳&#xff0c;如果节点没有回复&#xff0c;则这个哨兵就主观的认为这个节点发生故障&#xff0c;这时…...

OSPF概述

OSPF OSPF属于内部网关路由协议【IGP】 用于单一自治系统【Autonomous System-AS】内决策路由 自治系统【AS】 执行统一路由策略的一组网络设备的组合 OSPF概述 为了适应大型的网络&#xff0c;OSPF在AS内划分多个区域 每个OSPF路由器只维护所在区域的完整的链路状态信息 …...

CSS学习笔记[Web开发]

CSS学习 本文为学习笔记&#xff0c;参考菜鸟和w3c 文章目录 CSS 简介CSS 插入外部 CSS内部 CSS行内 CSS多个样式表层叠顺序 CSS 语法例子解释 CSS 选择器CSS 元素选择器CSS id 选择器实例CSS 类选择器实例CSS 通用选择器实例CSS 分组选择器CSS 后代选择器CSS 子元素选择器CSS …...

Go基础编程 - 11 - 函数(func)

接口&#xff08;interface&#xff09; 函数1. 函数定义1.1. 函数名1.2. 参数列表1.3. 返回值列表 2. 匿名函数3. 闭包、递归3.1 闭包3.1.1 函数、引用环境3.1.2 闭包的延迟绑定3.1.3 goroutine 的延迟绑定 3.2 递归函数 4. 延迟调用&#xff08;defer&#xff09;4.1 defer特…...

Typora入门

标题&#xff08;clrt数字&#xff09; 段落 实现换行 1.在一个行的结尾加上两个空格实现换行 2.在两行之间加上空行实现换行 实现分割线 &#xff08;1.***三个星号实现分割线&#xff09; (2.三个以上的—也可以实现分割线) 强调 斜体&#xff1a;我是斜体 (单下划线…...

PT2262-IR

PT2262是一款很古老的编码芯片&#xff0c;其兼容型号有&#xff1a;SC2262&#xff0c;AD2262&#xff0c;SC2260(需改变匹配电阻)等。 依据其datasheet&#xff0c;PT2262射频模式工作原理: CODE BITS A Code Bit is the basic component of the encoded waveform, and ca…...

JavaScript 迭代器

在JavaScript中&#xff0c;迭代器是一种允许我们遍历集合中元素的对象。迭代器对象具有一个next()方法&#xff0c;该方法返回value和done。value是当前迭代的值&#xff0c;done属性是一个布尔值&#xff0c;表示是否到达了集合的末尾。 迭代器协议 一个迭代器对象必须具备以…...

数据结构之《队列》

在数据结构之《栈》章节中学习了线性表中除了顺序表和链表外的另一种结构——栈&#xff0c;在本篇中我们将继续学习另一种线性表的结构——队列&#xff0c;在通过本篇的学习后&#xff0c;你将会对栈的结构有充足的了解&#xff0c;在了解完结构后我们还将进行栈的实现。一起…...

【NPU 系列专栏 2 -- NVIDIA 的 H100 和 H200 是什么?】

请阅读【嵌入式及芯片开发学必备专栏】 文章目录 NVIDIA H100 和 H200 芯片NVIDIA H100 芯片简介NVIDIA H100 主要特点NVIDIA H100 应用场景NVIDIA H100 使用举例NVIDIA H200 芯片简介NVIDIA H200 主要特点NVIDIA H200 应用场景NVIDIA H200 使用举例Summary NVIDIA H100 和 H20…...

【BUG】已解决:IndexError: positional indexers are out-of-bounds

IndexError: positional indexers are out-of-bounds 目录 IndexError: positional indexers are out-of-bounds 【常见模块错误】 【解决方案】 原因分析 解决方法 示例代码 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博…...

视频汇聚,GB28181,rtsp,rtmp,sip,webrtc,视频点播等多元异构视频融合,视频通话,视频会议交互方案

现在视频汇聚&#xff0c;视频融合和视频互动&#xff0c;是视频技术的应用方向&#xff0c;目前客户一般有很多视频的业务系统&#xff0c;如已有GB28181的监控&#xff08;GB现在是国内主流&#xff0c;大量开源接入和商用方案&#xff09;&#xff0c;rtsp设备&#xff0c;音…...

SpringCloud断路器的使用与原理解析

Spring Cloud断路器是在分布式系统中实现容错的一种方式。它的原理是通过在调用链路上添加断路器,当某个服务的调用出现故障或超时时,断路器会自动迅速地切换到快速失败模式,防止故障扩散,从而保护整个系统的稳定性。 Spring Cloud断路器的使用与原理解析如下: 一、使用断…...

结构型模式-分类

一、结构型设计模式 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式&#xff0c;前者采用继承机制来组织接口和类&#xff0c;后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低&#xff0c;满足“合成…...

【前端】JavaScript入门及实战106-110

文章目录 106 a的索引问题107 使用DOM操作CSS108 读取元素当前的样式109 getStyle()110 其他样式操作的属性滚动条练习 106 a的索引问题 <!DOCTYPE html> <html> <head> <title></title> <meta charset"utf-8"> <script typ…...

git 版本回退-idea

1、选中项目&#xff0c;右键&#xff0c;打开 git历史提交记录 2、选中想要回退的版本&#xff0c;选择 hard&#xff08;不保留版本记录&#xff09; 3、最终选择强制提交&#xff08;必须强制&#xff09; OK&#xff0c;搞定...

[安洵杯 2019]easy_serialize_php

进入界面然后 <?php$function $_GET[f];function filter($img){$filter_arr array(php,flag,php5,php4,fl1g);$filter /.implode(|,$filter_arr)./i;return preg_replace($filter,,$img); } 这就是个正则if($_SESSION){unset($_SESSION); 销毁 }$_SESSION["use…...

2024年软件测试面试题大全【含答案】

一、面试基础题 简述测试流程: 1、阅读相关技术文档&#xff08;如产品PRD、UI设计、产品流程图等&#xff09;。 2、参加需求评审会议。 3、根据最终确定的需求文档编写测试计划。 4、编写测试用例&#xff08;等价类划分法、边界值分析法等&#xff09;。 5、用例评审(…...

返回倒数第 k 个节点 - 力扣(LeetCode)

面试题 02.02. 返回倒数第 k 个节点 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/int kthToLast(struct ListNode* head, int k) {struct ListNode* fastnode head…...

12 前端工程化

组件化 1. 组件化理解 就是将页面的某一部分独立出来&#xff0c;将这一部分的数据层&#xff08;M&#xff09;、视图层&#xff08;V&#xff09;和控制层&#xff08;C&#xff09;用黑盒的形式全部封装到一个组件内&#xff0c;暴露出一些开箱即用的函数和属性供外部调用。…...

跨文档消息传递:WebKit中的Web通信新纪元

跨文档消息传递&#xff1a;WebKit中的Web通信新纪元 在现代Web应用中&#xff0c;跨文档消息传递&#xff08;Cross-document messaging&#xff09;是一种允许不同源的文档进行通信的机制。这种机制对于构建复杂的Web应用&#xff0c;如嵌入式框架&#xff08;iframes&#…...

面试题 33. 二叉搜索树的后序遍历序列

二叉搜索树的后序遍历序列 题目描述示例 题解递归单调栈 题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true&#xff0c;否则返回 false。假设输入的数组的任意两个数字都互不相同。 示例 参考以下这颗二叉搜索树&#…...

Web响应式设计———1、Grid布局

1、网格布局 Grid布局 流动网格布局是响应式设计的基础。它通过使用百分比而不是固定像素来定义网格和元素的宽度。这样&#xff0c;页面上的元素可以根据屏幕宽度自动调整大小&#xff0c;适应不同设备和分辨率。 <!DOCTYPE html> <html lang"en"> &l…...

ESP32开发进阶: 训练神经网络

一、网络设定 我们设定一个简单的前馈神经网络&#xff0c;其结构如下&#xff1a; 输入层&#xff1a;节点数&#xff1a;2&#xff0c;接收输入数据&#xff0c;每个输入样本包含2个特征&#xff0c;例如 {1.0, 0.0}, {0.0, 1.0} 等。 隐藏层&#xff1a;节点数&#xff1a;…...

全国区块链职业技能大赛国赛考题前端功能开发

任务3-1:区块链应用前端功能开发 1.请基于前端系统的开发模板,在登录组件login.js、组件管理文件components.js中添加对应的逻辑代码,实现对前端的角色选择功能,并测试功能完整性,示例页面如下: 具体要求如下: (1)有明确的提示,提示用户选择角色; (2)用户可看…...

直接插入排序算法详解

直接插入排序&#xff08;Straight Insertion Sort&#xff09;是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常采用in-place排…...

sql手动自增id

有时候在运维处理数据的时候&#xff0c;需要给某张表插入新的记录&#xff0c;那么需要知道最新插入数据的id,并在最新id的基础上加上id增长步长获取新的id,这个过程往往需要现将max出来加1,再手动补充到sql语句中&#xff0c;很麻烦&#xff0c;而且数据多的时候容易出错。有…...

10_TypeScript中的泛型

TypeScript中的泛型&#xff09; 一、泛型的定义二、泛型函数三、泛型类&#xff1a;比如有个最小堆算法&#xff0c;需要同时支持返回数字和字符串两种类型。通过类的泛型来实现四、泛型接口五、泛型类 --扩展 把类作为参数类型的泛型类1、实现&#xff1a;定义一个 User 的类…...

Unity3D之TextMeshPro使用

文章目录 1. TextMeshPro简介2. TextMeshPro创建3. TextMeshPro脚本中调用4. TextMeshPro字体设置及中文支持过程中出现的一些问题 1. TextMeshPro简介 【官网文档】https://docs.unity.cn/cn/2020.3/Manual/com.unity.textmeshpro.html TextMeshPro 是 Unity 的最终文本解决…...

K8S 上部署 Prometheus + Grafana

文章目录 一、使用 Helm 安装 Prometheus1. 配置源2. 下载 prometheus 包3. 安装 prometheus4. 卸载 二、使用 Helm 安装 Grafana1. 配置源2. 安装 grafana3. 访问4. 卸载 一、使用 Helm 安装 Prometheus 1. 配置源 地址&#xff1a;https://artifacthub.io/packages/helm/pro…...

雷军的逆天改命与顺势而为

雷军年度演讲前&#xff0c;朋友李翔提了一个问题&#xff1a;雷军造车是属于顺势而为还是逆势而为&#xff1f;评论互动区有一个总结&#xff0c;很有意思&#xff0c;叫“顺势逆袭”。 大致意思是产业趋势下小米从手机到IOT再切入汽车&#xff0c;是战略的必然&#xff0c;不…...

Leetcode 11. 盛最多水的容器

Leetcode 11. 盛最多水的容器 Leetcode 11. 盛最多水的容器 一、题目描述二、我的想法 一、题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成…...

Java笔试分享

1、设计模式&#xff08;写>3种常用的设计模式&#xff09; 设计模式是在软件工程中解决常见问题的经验性解决方案。以下是一些常用的设计模式&#xff1a; 单例模式&#xff08;Singleton&#xff09;&#xff1a; 意图&#xff1a;确保一个类只有一个实例&#xff0c;并…...

LeetCode:对称的二叉树(C语言)

1、问题概述&#xff1a;给一个二叉树&#xff0c;看是否按轴对称 2、示例 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 3、分析 &#xff08;1&a…...

Postman中的API Schema验证:确保响应精准无误

Postman中的API Schema验证&#xff1a;确保响应精准无误 在API开发和测试过程中&#xff0c;验证响应数据的准确性和一致性是至关重要的。Postman提供了一个强大的功能——API Schema验证&#xff0c;它允许开发者根据预定义的JSON Schema来检查API响应。本文将详细介绍如何在…...

深入浅出WebRTC—GCC

GoogCcNetworkController 是 GCC 的控制中心&#xff0c;它由 RtpTransportControllerSend 通过定时器和 TransportFeedback 来驱动。GoogCcNetworkController 不断更新内部各个组件的状态&#xff0c;并协调组件之间相互配合&#xff0c;向外输出目标码率等重要参数&#xff0…...

leetcode日记(49)旋转链表

其实不难&#xff0c;就是根据kk%len判断需要旋转的位置&#xff0c;再将后半段接在前半段前面就行。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : …...

InteliJ IDEA最新2024版下载安装与快速配置激活使用教程+jdk下载配置

第一步&#xff1a;下载ideaIC-2024.1.4 方法1&#xff1a;在线链接 IntelliJ IDEA – the Leading Java and Kotlin IDE (jetbrains.com) 选择社区版进行下载 方法2&#xff1a;百度网盘 链接&#xff1a;https://pan.baidu.com/s/1ydS6krUX6eE_AdW4uGV_6w?pwdsbfm 提取…...

【23】Android高级知识之Window(四) - ThreadedRenderer

一、概述 在上一篇文章中已经讲了setView整个流程中&#xff0c;最开始的addToDisplay和WMS跨进程通信的整个过程做了什么。继文章Android基础知识之Window(二)&#xff0c;这算是另外一个分支了&#xff0c;接着讲分析在performTraversals的三个操作中&#xff0c;最后触发pe…...