
SD 卡最初是从 MMC 卡演化而来的。你可能最熟悉的还是那个指甲盖大小的 micro SD 卡(也叫 TF 卡)。别被它小巧的外形迷惑,它的引脚和总线模式直接决定了初始化代码该怎么写。
标准 micro SD 卡(不含 UHS-II)有 8 个物理引脚,通过卡槽触点引出后,功能和标准大 SD 卡完全兼容。引脚排列如下:


注:卡检测DAT3(CD)通常由卡槽的独立机械开关或专用 GPIO 实现,这 8 个引脚本身不直接提供卡检测功能。
初始化时只用 1-bit 总线,这一点至关重要。无论你插的是标准大卡还是 micro SD 卡,所有命令和响应都走 CMD 线,数据交互最初只使用 DAT0 这一根数据线。而且,上电后 CMD 线的工作模式是开漏输出,多条设备可以共享,这也是为什么 SD 卡协议里有一大堆“广播命令”(Broadcast Commands),不需要地址就能发给所有卡。在完成卡的地址分配(CMD3/RCA)并选中某个卡(CMD7)之前,CMD 线无法进入推挽高速模式。
SD 卡的总线宽度是可以动态切换的:初始化完成后,可以通过 ACMD6 命令从 1-bit 切换到 4-bit 模式(DAT0~DAT3 全用上)。至于 UHS-II 这种更高速的模式,它使用了额外的差分信号线,为简化篇幅,本文仅聚焦传统 SD 以及 UHS-I 模式下的初始化,这已经覆盖了绝大多数嵌入式和消费级场景。


struct mmc_command {u32 opcode;u32 arg;u32 resp[4];unsigned int flags; /* expected response type */unsigned int retries; /* max number of retries */int error; /* command error */unsigned int busy_timeout; /* busy detect timeout in ms */struct mmc_data *data; /* data segment associated with cmd */struct mmc_request *mrq; /* associated request */struct uhs2_command *uhs2_cmd; /* UHS2 command *//* for SDUC */bool has_ext_addr;u8 ext_addr;};
用户态 /dev/mmcblk0───────────────────────────────────────块设备层 (block layer) drivers/mmc/core/block.cmmc_blk_probe → 创建 gendisk───────────────────────────────────────MMC核心层 (mmc core) drivers/mmc/core/│ ├─ core.c (host注册、rescan)│ ├─ sd.c (SD卡协议实现)│ ├─ mmc.c (MMC通用命令封装)│ ├─ sd_ops.c (SD专用命令)│ └─ queue.c (请求队列)───────────────────────────────────────HOST控制器驱动 (host) drivers/mmc/host/│ ├─ sdhci.c (标准SD Host Controller Interface)│ └─ 具体SoC驱动 (如 sdhci-msm.c, dw_mmc.c)───────────────────────────────────────物理层 SD卡硬件、总线



注:图中的
autonumber编号对应清单中的阶段①~⑧。注意第⑤步是一个“浓缩包”,内部包含了从 Idle 到 Transfer 的全部状态跳转命令,这是第3章的重头戏。
卡插入 → GPIO中断 → mmc_gpio_cd_irqt()→ mmc_detect_change(host, msecs_to_jiffies(200))→ _mmc_detect_change(host, delay)→ 调度 host->detect 工作 → mmc_rescan(host)
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };void mmc_rescan(struct work_struct *work){struct mmc_host *host =container_of(work, struct mmc_host, detect.work);/* 若 host 正在被移除,则直接返回 */if (host->rescan_disable)return;/* 如果当前有卡存在,先尝试发送CMD0复位,再重新识别 */if (host->bus_ops && host->bus_ops->detect && !host->bus_ops->detect(host))goto out; // 卡仍在,不重新初始化mmc_bus_get(host);/* 关键循环:尝试不同的频率和协议 */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;}if (!mmc_rescan_try_freq(host, max(freq, host->f_min)))// f_init 通常为400kHzbreak;if (freqs[i] <= host->f_min)break;}mmc_bus_put(host);out:if (host->caps & MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(&host->detect, HZ);}
// 在 mmc_rescan_try_freq 内部:if (!mmc_attach_sd(host))return; // SD卡初始化成功// 否则继续尝试 mmc_attach_mmc...
intmmc_attach_sd(struct mmc_host *host){int err;u32 ocr, rocr;struct mmc_card *card;// 1) 发送ACMD41循环,获取OCRerr = mmc_send_app_op_cond(host, 0, &ocr);if (err)return err;/* 设置总线操作为 SD 协议族 */mmc_attach_bus(host, &mmc_sd_ops);if (host->ocr_avail_sd)host->ocr_avail = host->ocr_avail_sd;/* 屏蔽不规范的电压位(bit7 及保留位) */ocr &= ~0x7FFF;// 如果host强制1.8V,但卡不支持,则失败if (host->ocr_avail_sd)rocr = mmc_select_voltage(host, ocr); // 从host支持中选最佳电压// 3) 核心状态机初始化card = mmc_sd_init_card(host, rocr, NULL);if (IS_ERR(card)) {err = PTR_ERR(card);goto err;}// 4) 性能提升(总线宽度、UHS等)err = mmc_sd_setup_card(host, card, false);if (err)goto remove_card;// 注册卡err = mmc_add_card(host, card);if (err)goto remove_card;return 0;remove_card:mmc_remove_card(card);err:// 清理,可能继续尝试MMCreturn err;}

// 让所有卡进入Idle状态err = mmc_go_idle(host);if (err)return ERR_PTR(err);
/* 发送CMD8,参数:VHS=0x1 (2.7-3.6V),check pattern=0xAA */err = mmc_send_if_cond(host, host->ocr_avail);if (!err) {/* 卡支持SD 2.0及以上,且电压兼容 */ocr |= SD_OCR_CCS; // 预设CCS,后面ACMD41会确认}/* 若CMD8无响应且不是超时,可能是SD 1.x卡,走降级路径 */
/** 循环发送ACMD41,直到卡就绪(OCR busy位为1)* arg: 包含HCS(Host Capacity Support)位,告知卡主机支持高容量*/err = mmc_send_app_op_cond(host, ocr | SD_OCR_HCS, rocr);if (err)return ERR_PTR(err);/* ocr 现在包含了卡真实支持的电压、CCS等信息 */card->ocr = ocr;
mmc_send_app_op_cond 内部实现精要
/*** mmc_send_app_op_cond - 发送 ACMD41 循环,等待卡上电完成* @host: 主机控制器* @ocr: 期望的 OCR 值(含 HCS 等标志)* @rocr: 输出参数,存放卡返回的 OCR 寄存器值(非 SPI 模式下有效)** 功能:循环发送 CMD55+ACMD41,直到卡拉高 busy 信号(OCR bit31),* 表示卡已完成上电初始化,准备好进入 Ready 状态。* 超时:单次轮询间隔 SD_APP_OP_COND_PERIOD_US,总超时 SD_APP_OP_COND_TIMEOUT_MS。*/int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr){struct mmc_command cmd = {};struct sd_app_op_cond_busy_data cb_data = {.host = host,.ocr = ocr, // 传入的期望 OCR(含 HCS 位).cmd = &cmd // 指向本地的命令结构,供回调使用};int err;/* 命令号:41 (ACMD41) */cmd.opcode = SD_APP_OP_COND;/* SPI 模式下仅识别 bit30(CCS),普通模式传递完整 OCR */if (mmc_host_is_spi(host))cmd.arg = ocr & (1 << 30);elsecmd.arg = ocr;/* 响应类型:R1(SPI 模式) / R3(SD 模式),广播命令无数据 */cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;/** 核心:轮询直到卡就绪或超时。* 内部会不断发送 CMD55 + CMD41,每次检查响应 R3 的 bit31(busy)。* 若 busy 未置位,等待 SD_APP_OP_COND_PERIOD_US 后重试,* 总超时 SD_APP_OP_COND_TIMEOUT_MS 后会返回 -ETIMEDOUT。*/err = __mmc_poll_for_busy(host, SD_APP_OP_COND_PERIOD_US,SD_APP_OP_COND_TIMEOUT_MS,&sd_app_op_cond_cb, &cb_data);if (err)return err;/* 非 SPI 模式下,把卡返回的 OCR 存入 *rocr */if (rocr && !mmc_host_is_spi(host))*rocr = cmd.resp[0];return 0;}
err = mmc_all_send_cid(host, card->raw_cid);if (err)return ERR_PTR(err);/* 解析CID,填充 card->cid 结构 */memcpy(card->raw_cid, resp, sizeof(card->raw_cid));mmc_decode_cid(card);
err = mmc_send_relative_addr(host, &card->rca);if (err)return ERR_PTR(err);
err = mmc_sd_get_csd(card, is_sduc);if (err)goto free_card;/* 解析CSD,得到容量、速度信息 */mmc_decode_csd(card);
/*** mmc_decode_csd - 解析 CSD 寄存器(128位),填充 mmc_csd 结构* @card: 所属的卡对象(其 raw_csd 已存有原始数据)* @is_sduc: 是否为 SDUC 卡(容量超过 2TB)** CSD 有两个主流版本:v1.0(csd_struct=0)和 v2.0(csd_struct=1/2)。* v1.0 用于标准容量 SDSC 卡,v2.0 用于高容量 SDHC/SDXC/SDUC 卡。*/staticintmmc_decode_csd(struct mmc_card *card, bool is_sduc){struct mmc_csd *csd = &card->csd;unsigned int e, m, csd_struct;u32 *resp = card->raw_csd;/* 提取 CSD 版本(最高两位) */csd_struct = unstuff_bits(resp, 126, 2);switch (csd_struct) {case 2: /* CSD 版本 2.0 —— 高容量 / 扩展容量卡 */mmc_card_set_blockaddr(card); // 标记为块寻址(每块 512 字节)csd->taac_ns = 0; /* 未使用 */csd->taac_clks = 0; /* 未使用 */m = unstuff_bits(resp, 99, 4);e = unstuff_bits(resp, 96, 3);csd->max_dtr = tran_exp[e] * tran_mant[m];csd->cmdclass = unstuff_bits(resp, 84, 12);/* 容量字段:v2.0 为 22 位,SDUC 为 28 位 */if (csd_struct == 1)m = unstuff_bits(resp, 48, 22);elsem = unstuff_bits(resp, 48, 28);csd->c_size = m;/* 根据容量标志设置卡类型 (SDUC/Ultra) */if (csd->c_size >= 0x400000 && is_sduc)mmc_card_set_ult_capacity(card);else if (csd->c_size >= 0xFFFF)mmc_card_set_ext_capacity(card);/* 容量 = (1 + c_size) * 512 KB = (1+m) << 10 个扇区 (每个扇区512B) */csd->capacity = (1 + (typeof(sector_t))m) << 10;csd->read_blkbits = 9; /* 固定 512 字节块 */csd->erase_size = 1; /* 单位 1 个扇区 */if (unstuff_bits(resp, 13, 1))mmc_card_set_readonly(card);break;card->erase_size = csd->erase_size;return 0;}
err = mmc_select_card(card);if (err)return ERR_PTR(err);
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card){struct mmc_command cmd = {};cmd.opcode = MMC_SELECT_CARD;if (card) {cmd.arg = card->rca << 16;cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;} else {cmd.arg = 0;cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;}return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);}// 等待卡释放DAT0
mmc_sd_init_card 完成(卡处于 Transfer 状态)↓mmc_sd_setup_card 入口↓┌── reinit=false? ──→ YES ──→ ① 读取 SCR (ACMD51)│ ② 解析 SCR (版本、1.8V支持、总线宽度)│ ③ 读取 SSR (ACMD13)│ ④ 初始化擦除参数│ ⑤ 检查写保护开关└─────────────────────────────↓⑥ 读取 CMD6 切换能力表(速度模式、驱动强度、电流限制)↓⑦ SPI 模式下使能 CRC(如需要)↓返回 0
/*** mmc_sd_setup_card - SD 卡性能提升与特性发现* @host: 主机控制器* @card: 已完成基础状态机初始化的卡* @reinit: 是否为重新初始化(系统恢复时跳过部分硬件读取)** 此函数在 mmc_sd_init_card 成功后调用。* 它不负责切换总线宽度/UHS 模式(那些在 sd_init_card 末尾完成),* 而是负责 **读取卡的能力寄存器** 和 **执行特性发现**:* - 读取 SCR 寄存器(SD 配置寄存器)* - 读取 SSR 寄存器(SD 状态寄存器)* - 初始化擦除参数* - 读取 CMD6 切换能力表(速度模式、驱动强度等)* - 检查物理写保护开关** 这些信息为后续的总线宽度切换、UHS 模式协商、时钟提升* 以及块设备层的擦除优化提供决策依据。*/int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,bool reinit){int err;/* ---------- 仅全新初始化时执行 ---------- */if (!reinit) {/* ① 发送 ACMD51,获取 SCR 寄存器(64 位) */err = mmc_app_send_scr(card);if (err)return err;/* ② 解析 SCR:SD 版本、1.8V 支持、总线宽度能力等 */err = mmc_decode_scr(card);if (err)return err;/* ③ 发送 ACMD13,获取 SSR 寄存器(512 位) */err = mmc_read_ssr(card);if (err)return err;/* ④ 根据 CSD 和 SSR 初始化擦除参数 */mmc_init_erase(card);}/** ⑤ 发送 CMD6,查询卡的功能切换能力。* 返回的 switch_caps 结构包含:* - 功能组 1:支持的速度模式(普通高速 / SDR25 / SDR50 / SDR104 / DDR50)* - 功能组 2:驱动强度(A 类 / B 类等)* - 功能组 3:电流限制* 注意:sd3_bus_mode 可能因电压切换结果而改变,因此总是读取。*/err = mmc_read_switch(card);if (err)return err;/** ⑥ SPI 模式:根据卡的能力使能 CRC 校验。* 必须在读取完寄存器之后执行,因为部分 SDHC 卡* 在非 512 字节块传输时 CRC 不合法。*/if (mmc_host_is_spi(host)) {err = mmc_spi_set_crc(host, use_spi_crc);if (err)return err;}/* ---------- 仅全新初始化时执行 ---------- */if (!reinit) {/* ⑦ 检查物理写保护开关(卡侧面的 Lock 滑块) */int ro = mmc_sd_get_ro(host);if (ro < 0) {pr_warn("%s: host does not support reading read-only switch, ""assuming write-enable\n", mmc_hostname(host));} else if (ro > 0) {mmc_card_set_readonly(card);}}return 0;}
/*** mmc_sd_init_uhs_card - UHS-I 超高速模式初始化流程* @card: 已完成基础识别和 SCR/CID/CSD 读取的 SD 卡** 此函数在 mmc_sd_init_card 识别到卡支持 UHS-I 后调用,* 执行 SD 物理层规范定义的 UHS-I 初始化序列:* 1. 切换到 4-bit 总线宽度* 2. 选择卡与主机都支持的最高速度模式(SDR50/104/DDR50 等)* 3. 设置驱动强度(A 类 / B 类)* 4. 设置电流限制* 5. 执行模式切换(发送 CMD6)* 6. 若模式需要调谐(SDR104 / SDR50),则执行 tuning** 若卡不支持 CMD6 切换命令(命令类 CCC_SWITCH 未置位),则直接返回。*/staticintmmc_sd_init_uhs_card(struct mmc_card *card){int err;u8 *status;/* 若卡不支持切换命令(CMD6),直接退出,不进入 UHS 模式 */if (!(card->csd.cmdclass & CCC_SWITCH))return 0;status = kmalloc(64, GFP_KERNEL);if (!status)return -ENOMEM;/* ① 切换到 4-bit 总线宽度 (ACMD6) */err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);if (err)goto out;mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);/* ② 根据主机能力和卡能力选择最佳速度模式 */sd_update_bus_speed_mode(card);/* ③ 设置驱动强度(功能组 2) */err = sd_select_driver_type(card, status);if (err)goto out;/* ④ 设置电流限制(功能组 3) */err = sd_set_current_limit(card, status);if (err)goto out;/* ⑤ 执行速度模式切换(CMD6 功能组 1) */err = sd_set_bus_speed_mode(card, status);if (err)goto out;/* ⑥ 若所选模式需要 tuning,则执行 CMD19 tuning */if (mmc_sd_use_tuning(card)) {err = mmc_execute_tuning(card);/** SD 物理层规范 v3.01 允许 DDR50 模式也可执行 tuning,* 但部分卡不支持,若 tuning 失败仅警告,不视为致命错误。*/if (err && card->host->ios.timing == MMC_TIMING_UHS_DDR50) {pr_warn("%s: ddr50 tuning failed\n",mmc_hostname(card->host));err = 0;}}out:kfree(status);return err;}
/*** mmc_sd_use_tuning - 判断当前卡是否需要执行 CMD19 调谐* @card: 已完成模式切换的 SD 卡** 返回值: true 表示需要 tuning,false 表示不需要。** 调谐仅适用于 UHS-I 总线速度模式下的 SDR50、SDR104 和 DDR50。* - SDR50 / SDR104:必须 tuning 以保证高速数据采样窗口正确。* - DDR50:tuning 是可选的,部分卡可能不支持或不需 tuning(通过 quirk 标记跳过)。* - SPI 模式不支持 CMD19,因此永远不需要 tuning。*/staticboolmmc_sd_use_tuning(struct mmc_card *card){/* SPI 模式不定义 CMD19,直接返回 false */if (mmc_host_is_spi(card->host))return false;switch (card->host->ios.timing) {case MMC_TIMING_UHS_SDR50:case MMC_TIMING_UHS_SDR104:/* SDR50 和 SDR104 模式强制需要 tuning */return true;case MMC_TIMING_UHS_DDR50:/* DDR50 模式下,若卡有跳过 tuning 的 quirk 则不需要,否则建议 tuning */return !mmc_card_no_uhs_ddr50_tuning(card);}/* 其他模式(如普通高速、SDR25 等)不需要 tuning */return false;}

/*** mmc_add_card - 将已初始化的 MMC/SD 卡注册到 Linux 设备模型* @card: 已完成初始化、填充了所有关键信息的卡对象** 这是初始化流程的倒数第二步(阶段⑦)。* 功能:把抽象的 struct mmc_card 变成内核设备模型中的一个 device,* 触发总线匹配和驱动 probe,最终生成用户可用的块设备节点。** 内部会打印一条著名的内核日志,类似:* "mmc0: new ultra high speed SDR104 SDHC card at address 0001"*/intmmc_add_card(struct mmc_card *card){int ret;const char *type;const char *speed_mode = "";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 ",};/* ① 设置设备名称:格式为 "mmc0:0001"(主机名:RCA) */dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);/* ② 标记设备是否可移除(SD 卡为 removable,eMMC 为 fixed) */dev_set_removable(&card->dev,mmc_card_is_removable(card->host) ?DEVICE_REMOVABLE : DEVICE_FIXED);/* ③ 根据卡类型生成可读字符串 */switch (card->type) {case MMC_TYPE_MMC:type = "MMC";break;case MMC_TYPE_SD:type = "SD"; // 默认 SDSCif (mmc_card_blockaddr(card)) { // 高容量卡if (mmc_card_ult_capacity(card))type = "SDUC"; // 超大容量(>2TB)else if (mmc_card_ext_capacity(card))type = "SDXC"; // 扩展容量(32GB~2TB)elsetype = "SDHC"; // 高容量(2GB~32GB)}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;}/* ④ 确定速度等级字符串 */if (mmc_card_hs(card))speed_mode = "high speed ";else if (mmc_card_uhs(card))speed_mode = "ultra high speed "; // UHS-Ielse if (mmc_card_uhs2(card->host))speed_mode = "UHS-II speed ";else if (mmc_card_ddr52(card))speed_mode = "high speed DDR ";else if (mmc_card_hs200(card))speed_mode = "HS200 ";else if (mmc_card_hs400es(card))speed_mode = "HS400 Enhanced strobe ";else if (mmc_card_hs400(card))speed_mode = "HS400 ";/* ⑤ 确定 UHS 具体模式字符串(SDR50/SDR104 等) */if (mmc_card_uhs(card) &&(card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];/* ⑥ 打印那条著名的内核日志 */if (mmc_host_is_spi(card->host))pr_info("%s: new %s%s card on SPI\n",mmc_hostname(card->host), speed_mode, type);elsepr_info("%s: new %s%s%s card at address %04x\n",mmc_hostname(card->host), speed_mode,uhs_bus_speed_mode, type, card->rca);/* ⑦ 创建 debugfs 调试目录 */mmc_add_card_debugfs(card);/* ⑧ 关联设备树节点 */card->dev.of_node = mmc_of_find_child_device(card->host, 0);/* ⑨ 允许异步挂起(优化系统休眠速度) */device_enable_async_suspend(&card->dev);/* ⑩ 注册设备到内核设备模型——触发总线 match 和驱动 probe */ret = device_add(&card->dev);if (ret)return ret;/* ⑪ 标记卡为 present(已就位) */mmc_card_set_present(card);/** ⑫ 若为 MMC 卡且支持断电通知,则注册电压不足回调,* 以便在电源异常时能紧急关闭卡。*/if (mmc_card_mmc(card) &&card->ext_csd.power_off_notification == EXT_CSD_POWER_ON)mmc_regulator_register_undervoltage_notifier(card->host);return 0;}
staticintmmc_blk_probe(struct mmc_card *card){struct mmc_blk_data *md;int ret = 0;/* 检查卡是否支持块读取命令类 */if (!(card->csd.cmdclass & CCC_BLOCK_READ))return -ENODEV;/* 应用设备特殊修正(quirks) */mmc_fixup_device(card, mmc_blk_fixups);/* 创建高优先级、可内存回收的完成工作队列 */card->complete_wq = alloc_workqueue("mmc_complete",WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);if (!card->complete_wq) {pr_err("Failed to create mmc completion workqueue");return -ENOMEM;}/* 分配主块设备数据结构(gendisk、请求队列) */md = mmc_blk_alloc(card);if (IS_ERR(md)) {ret = PTR_ERR(md);goto out_free;}/* 为硬件分区(如RPMB、boot分区)创建对应设备 */ret = mmc_blk_alloc_parts(card, md);if (ret)goto out;/* 添加 debugfs 调试接口 */mmc_blk_add_debugfs(card, md);/* 配置运行时电源管理:空闲 3 秒后自动挂起 */pm_runtime_set_autosuspend_delay(&card->dev, 3000);pm_runtime_use_autosuspend(&card->dev);/* SD-combo卡由SDIO侧管理电源,此处仅对纯存储卡启用runtime PM */if (!mmc_card_sd_combo(card)) {pm_runtime_set_active(&card->dev);pm_runtime_enable(&card->dev);}/* 注册RPMB(重放保护内存块)设备 */mmc_blk_rpmb_add(card);return 0;out:mmc_blk_remove_parts(card, md);mmc_blk_remove_req(md);out_free:destroy_workqueue(card->complete_wq);return ret;}



struct mmc_request {struct mmc_command *sbc; /* 多块读写前的 SET_BLOCK_COUNT 命令 */struct mmc_command *cmd; /* 主命令(必须) */struct mmc_data *data; /* 关联的数据传输(可选) */struct mmc_command *stop; /* 停止传输命令(如 CMD12,可选) */struct completion completion; /* 整体请求完成信号量 */struct completion cmd_completion; /* 仅命令阶段完成信号量 */void (*done)(struct mmc_request *); /* 完成回调函数 */void (*recovery_notifier)(struct mmc_request *); /* 错误恢复通知(CQE使用) */struct mmc_host *host; /* 所属host控制器 */bool cap_cmd_during_tfr; /* 允许传输期间发送其他命令 */int tag; /* 请求标签(用于blk-mq队列) */#ifdefCONFIG_MMC_CRYPTOconst struct bio_crypt_ctx *crypto_ctx; /* 内联加密上下文 */int crypto_key_slot; /* 密钥槽位 */#endifstruct uhs2_command uhs2_cmd; /* UHS-II 扩展命令描述 */};
static const struct mmc_bus_ops mmc_sd_ops = {.remove = mmc_sd_remove, /* 卡移除时调用,注销设备、释放资源 */.detect = mmc_sd_detect, /* 热插拔检测:返回true表示卡仍在槽 */.runtime_suspend = mmc_sd_runtime_suspend, /* 运行时挂起:空闲时关时钟省电 */.runtime_resume = mmc_sd_runtime_resume, /* 运行时恢复:重新使能时钟与总线 */.suspend = mmc_sd_suspend, /* 系统挂起:通知卡进入休眠状态 */.resume = mmc_sd_resume, /* 系统唤醒:重新初始化卡回到传输态 */.alive = mmc_sd_alive, /* 心跳检测:发送CMD13检查卡是否响应 */.shutdown = mmc_sd_suspend, /* 系统关机:复用挂起逻辑,优雅关闭卡 */.hw_reset = mmc_sd_hw_reset, /* 硬件复位:拉低RST引脚或发送CMD0复位 */.cache_enabled = sd_cache_enabled, /* 查询卡是否开启了内部写缓存 */.flush_cache = sd_flush_cache, /* 刷写卡内部缓存(CMD32/CMD33序列) */};

一张小小的 SD 卡,从插入卡槽到 mount 可见,内核背后执行了十余步精密的协商与配置。这些步骤看似繁琐,实则每一环都是在为“跑得更快、更稳”铺路——用低频 1-bit 起步保安全,用电压协商和 tuning 冲刺极限性能。
希望这篇文章能帮你建立起 MMC 子系统的全景视图。下次再看到 mmc0: new ultra high speed SDR104 SDHC card at address 0001 这行日志时,你可以会心一笑:“这背后的一切,我都知道。”
目前文章知识介绍了底层驱动的初始化与识别,你在系统重要看到还需要上层的挂载,在Android中等底层初始化完成之后会触发事件通知到上层的vold,最后的挂载操作由vold完成!这部分后续会继续补充!
代码永远是最好的老师。去读源码吧。