5.18 SDIO/MMC设备
5.18 SDIO/MMC设备
📚 本节导读
学习时长: 约 50 分钟
难度级别: ⭐⭐⭐⭐⭐
前置知识: C 语言基础、SD/MMC 协议基础、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS SDIO/MMC 协议栈的分层架构
- 掌握 Host 控制器的抽象与注册
- 理解 SD/MMC/SDIO 卡的检测与初始化流程
- 掌握 Block 设备接口的读写操作
- 了解热插拔检测机制
一、概述
OneOS SDIO/MMC 驱动位于 drivers/sdio/ 目录下,提供完整的 SD/MMC/SDIO 协议栈支持。该驱动实现了从 Host 控制器到卡协议层的完整抽象,支持 SD 卡、MMC 卡、SDIO 设备(如 WiFi 模块)的识别与操作。
协议栈分层架构:
┌──────────────────────────────────────┐
│ Block Device Interface │ ← 块设备读写接口
├──────────────────────────────────────┤
│ SD Protocol │ MMC Protocol │ ← 卡协议层(sd.c / mmc.c)
│ │ │
├──────────────────────────────────────┤
│ mmcsd_core.c │ ← 核心框架层
│ (命令发送、响应解析、数据传输) │
├──────────────────────────────────────┤
│ mmcsd_host │ ← Host 控制器抽象
│ (Host ops 函数表) │
├──────────────────────────────────────┤
│ HAL 层 Host 驱动实现 │ ← 具体硬件实现
│ (STM32/ESP32/... SDIO 外设) │
└──────────────────────────────────────┘SDIO 驱动文件组成:
| 模块 | 文件 | 说明 |
|---|---|---|
| 核心框架 | mmcsd_core.h / mmcsd_core.c | 命令发送/接收、数据传输、卡检测线程 |
| Host 抽象 | mmcsd_host.h | Host 控制器结构体和操作函数表 |
| 命令定义 | mmcsd_cmd.h | SD/MMC 命令宏定义 |
| 卡信息 | mmcsd_card.h | 卡信息结构体(CID/CSD/SCR 等) |
| SD 协议 | sd.h / sd.c | SD 卡初始化、识别、配置 |
| MMC 协议 | mmc.h / mmc.c | MMC/eMMC 卡初始化、识别、配置 |
| SDIO 协议 | sdio.h / sdio.c | SDIO 设备(WiFi/BT 等)初始化与读写 |
| Block 设备 | block_dev.c | 将 SD/MMC 卡封装为 Block 设备 |
1.1 使用场景
一句话核心:SDIO/MMC 框架把 SD/MMC 存储卡和 SDIO 功能设备(WiFi/BT)统一抽象——存储卡初始化后封装成 Block 设备(挂文件系统),SDIO 功能设备通过 CMD52/CMD53 IO_RW 命令通信。HAL 层只需实现 Host 操作函数,就能让任意带 SDIO 外设的 MCU 用上这套协议栈。
与"裸操作 SDIO 寄存器"对比:
| 维度 | 用 OneOS SDIO 框架 | 裸操作寄存器 |
|---|---|---|
| 协议栈 | 内置完整 SD/MMC 初始化流程(CMD0~ACMD51) | 需手写整个握手 |
| 卡识别 | 自动识别 SD/SDHC/SDXC/MMC/SDIO | 手动判断 |
| 存储访问 | 自动封装 Block 设备,可挂文件系统 | 手动搬数据 |
| 热插拔 | 检测线程 + 邮箱通知 | 需自己轮询 |
典型应用场景:
| 场景 | 谁在用 | 实际工作情况 |
|---|---|---|
| 数据存储(SD 卡) | 文件系统(LFS/FSL) | SD 卡初始化为 sd0 Block 设备,挂文件系统做日志/配置/媒体存储 |
| 板载 eMMC | 文件系统 | eMMC 容量大、速度快,作为主存储 |
| WiFi/BT 模块 | SDIO 功能设备驱动 | SDIO 卡的 Function 1 被 WiFi 驱动通过 CMD52/CMD53 读写控制/数据 |
| 掉电数据保存 | SD 卡专用分区 | 写 SD 卡特定扇区保存参数 |
选型边界表:
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| MCU 无 SDIO 外设 | SPI Flash + 文件系统 | SDIO 是硬件外设,没有就只能用 SPI Flash |
| 简单小容量存储 | SPI Flash | SD 卡协议栈开销大,小存储用 Flash 更简单 |
| 高速大数据 | eMMC/UFS | SD 卡速率受限于 SDIO 外设和卡规格 |
结合源码的使用注意点:
- 识别频率必须低:初始化用
freq_min(≤400KHz),初始化后才能切高速,否则卡识别失败; valid_ocr电压要匹配:Host 支持的电压范围必须覆盖卡的工作电压;- 卡检测:
mmcsd_change(host)触发检测线程识别卡,卡识别成功才创建 Block 设备; - 热插拔:读写中不能直接拔卡,应先卸载文件系统。
二、核心数据结构
2.1 Host 控制器结构体 struct os_mmcsd_host
来源:drivers/sdio/mmcsd_host.h
struct os_mmcsd_host
{
struct os_mmcsd_card *card;
const struct os_mmcsd_host_ops *ops;
uint32_t freq_min;
uint32_t freq_max;
struct os_mmcsd_io_cfg io_cfg;
uint32_t valid_ocr; /* current valid OCR */
uint32_t flags; /* define device capabilities */
uint32_t max_seg_size; /* maximum size of one dma segment */
uint32_t max_dma_segs; /* maximum number of dma segments in one request */
uint32_t max_blk_size; /* maximum block size */
uint32_t max_blk_count; /* maximum block count */
uint32_t spi_use_crc;
os_mutex_id bus_lock;
os_semaphore_id sem_ack;
uint32_t sdio_irq_num;
os_semaphore_id sdio_irq_sem;
os_task_id sdio_irq_thread;
void *private_data;
};字段详解(源码无注释或英文注释,下表补充说明,不属于源码):
| 字段 | 类型 | 含义 |
|---|---|---|
card | os_mmcsd_card * | 当前插入的卡(NULL 表示无卡) |
ops | const os_mmcsd_host_ops * | Host 操作函数表(request/set_iocfg/get_card_status/enable_sdio_irq,见 2.2) |
freq_min/freq_max | uint32_t | 支持的最小时钟/最大时钟频率(Hz),识别时用 freq_min(≤400KHz) |
io_cfg | os_mmcsd_io_cfg | IO 配置(时钟/电压/总线模式/电源/总线宽度) |
valid_ocr | uint32_t | 有效工作电压范围(位掩码,见下方 VDD 宏) |
flags | uint32_t | Host 能力标志(见下方 flags 宏) |
max_seg_size | uint32_t | 单个 DMA 段的最大字节数 |
max_dma_segs | uint32_t | 一次请求的 DMA 最大段数 |
max_blk_size | uint32_t | 最大块大小 |
max_blk_count | uint32_t | 最大块数 |
spi_use_crc | uint32_t | SPI 模式是否用 CRC |
bus_lock | os_mutex_id | 总线互斥量(mmcsd_host_lock/unlock 用它保护整条 SD 总线) |
sem_ack | os_semaphore_id | 命令完成信号量(mmcsd_send_request 等待、mmcsd_req_complete 释放) |
sdio_irq_num/sem/thread | —— | SDIO 中断处理相关(中断号/信号量/处理线程) |
private_data | void * | 厂商私有数据 |
电压范围宏(valid_ocr 位掩码)(drivers/sdio/mmcsd_host.h,与项目逐字一致):
#define VDD_165_195 (1 << 7) /* VDD voltage 1.65 - 1.95 */
#define VDD_20_21 (1 << 8) /* VDD voltage 2.0 ~ 2.1 */
#define VDD_21_22 (1 << 9) /* VDD voltage 2.1 ~ 2.2 */
#define VDD_22_23 (1 << 10) /* VDD voltage 2.2 ~ 2.3 */
#define VDD_23_24 (1 << 11) /* VDD voltage 2.3 ~ 2.4 */
#define VDD_24_25 (1 << 12) /* VDD voltage 2.4 ~ 2.5 */
#define VDD_25_26 (1 << 13) /* VDD voltage 2.5 ~ 2.6 */
#define VDD_26_27 (1 << 14) /* VDD voltage 2.6 ~ 2.7 */
#define VDD_27_28 (1 << 15) /* VDD voltage 2.7 ~ 2.8 */
#define VDD_28_29 (1 << 16) /* VDD voltage 2.8 ~ 2.9 */
#define VDD_29_30 (1 << 17) /* VDD voltage 2.9 ~ 3.0 */
#define VDD_30_31 (1 << 18) /* VDD voltage 3.0 ~ 3.1 */
#define VDD_31_32 (1 << 19) /* VDD voltage 3.1 ~ 3.2 */
#define VDD_32_33 (1 << 20) /* VDD voltage 3.2 ~ 3.3 */
#define VDD_33_34 (1 << 21) /* VDD voltage 3.3 ~ 3.4 */
#define VDD_34_35 (1 << 22) /* VDD voltage 3.4 ~ 3.5 */
#define VDD_35_36 (1 << 23) /* VDD voltage 3.5 ~ 3.6 */数值演算 ——
valid_ocr常用值:若 Host 支持 2.7V~3.6V,则valid_ocr = VDD_27_28 | VDD_28_29 | VDD_29_30 | VDD_30_31 | VDD_31_32 | VDD_32_33 | VDD_33_34 | VDD_34_35 | VDD_35_36,即0x00FF8000(bit15~bit23 全 1)。
Host 能力标志(flags)(drivers/sdio/mmcsd_host.h,与项目逐字一致):
#define MMCSD_BUSWIDTH_4 (1 << 0)
#define MMCSD_BUSWIDTH_8 (1 << 1)
#define MMCSD_MUTBLKWRITE (1 << 2)
#define MMCSD_HOST_IS_SPI (1 << 3)
#define MMCSD_SUP_SDIO_IRQ (1 << 4) /* support signal pending SDIO IRQs */
#define MMCSD_SUP_HIGHSPEED (1 << 5) /* support high speed */| 宏定义 | 位 | 说明 |
|---|---|---|
MMCSD_BUSWIDTH_4 | bit0 | 支持 4-bit 总线宽度 |
MMCSD_BUSWIDTH_8 | bit1 | 支持 8-bit 总线宽度(eMMC) |
MMCSD_MUTBLKWRITE | bit2 | 支持多块写 |
MMCSD_HOST_IS_SPI | bit3 | Host 是 SPI 模式 |
MMCSD_SUP_SDIO_IRQ | bit4 | 支持 SDIO 中断 |
MMCSD_SUP_HIGHSPEED | bit5 | 支持高速模式 |
注意:本框架没有
MMCSD_BUSWIDTH_1(1-bit 是默认),也没有MMCSD_SUPP_*拼写——正确拼写是MMCSD_SUP_SDIO_IRQ/MMCSD_SUP_HIGHSPEED(SUP不是SUPP)。总线宽度宏在io_cfg里是MMCSD_BUS_WIDTH_1=0/_4=2/_8=3(见 2.2 说明),与flags的MMCSD_BUSWIDTH_4/8是两套不同定义。
2.2 Host 操作函数表 struct os_mmcsd_host_ops
来源:drivers/sdio/mmcsd_host.h
struct os_mmcsd_host_ops
{
void (*request)(struct os_mmcsd_host *host, struct os_mmcsd_req *req);
void (*set_iocfg)(struct os_mmcsd_host *host, struct os_mmcsd_io_cfg *io_cfg);
int32_t (*get_card_status)(struct os_mmcsd_host *host);
void (*enable_sdio_irq)(struct os_mmcsd_host *host, int32_t en);
};函数指针详解(源码无注释,下表补充说明,不属于源码):
| 函数指针 | 签名要点 | 说明 |
|---|---|---|
request | (host, req) | 发送一个命令请求(含可能的数据传输),HAL 层完成命令后调 mmcsd_req_complete 通知框架(见 5.1) |
set_iocfg | (host, io_cfg) | 设置 IO 配置(时钟/电压/总线模式/电源/总线宽度,由 mmcsd_set_clock 等内部调用) |
get_card_status | (host) | 获取卡检测状态(MMCSD_HOST_PLUGED=0 已插入 / MMCSD_HOST_UNPLUGED=1 未插入) |
enable_sdio_irq | (host, en) | 启用/禁用 SDIO 中断(SDIO 设备用) |
注意:本框架的
os_mmcsd_host_ops只有上述 4 个回调,没有config_dma。DMA 传输能力通过host的max_dma_segs/max_seg_size字段描述,HAL 层在request内部自行配置 DMA。文档早期版本若提到config_dma是错误的,请以本节为准。
set_iocfg 用到的 IO 配置结构 os_mmcsd_io_cfg(drivers/sdio/mmcsd_host.h,与项目逐字一致):
struct os_mmcsd_io_cfg
{
uint32_t clock; /* clock rate */
uint16_t vdd;
/* vdd stores the bit number of the selected voltage range from below. */
uint8_t bus_mode; /* command output mode */
#define MMCSD_BUSMODE_OPENDRAIN 1
#define MMCSD_BUSMODE_PUSHPULL 2
uint8_t chip_select; /* SPI chip select */
#define MMCSD_CS_IGNORE 0
#define MMCSD_CS_HIGH 1
#define MMCSD_CS_LOW 2
uint8_t power_mode; /* power supply mode */
#define MMCSD_POWER_OFF 0
#define MMCSD_POWER_UP 1
#define MMCSD_POWER_ON 2
uint8_t bus_width; /* data bus width */
#define MMCSD_BUS_WIDTH_1 0
#define MMCSD_BUS_WIDTH_4 2
#define MMCSD_BUS_WIDTH_8 3
};| 字段/宏 | 含义 |
|---|---|
clock | 时钟频率(Hz) |
vdd | 电压档位编号(bit 号) |
bus_mode | 命令输出模式:MMCSD_BUSMODE_OPENDRAIN=1 / MMCSD_BUSMODE_PUSHPULL=2 |
chip_select | SPI 片选:IGNORE=0 / HIGH=1 / LOW=2 |
power_mode | 电源模式:OFF=0 / UP=1 / ON=2 |
bus_width | 数据总线宽度:MMCSD_BUS_WIDTH_1=0 / _4=2 / _8=3 |
注意区分两套总线宽度宏:
io_cfg.bus_width用的是MMCSD_BUS_WIDTH_1/4/8(值 0/2/3,配置给硬件的);host.flags用的是MMCSD_BUSWIDTH_4/8(位 0/1,声明 Host 能力)。前者是"设置当前总线宽",后者是"声明支持哪些宽"。
2.3 请求/命令/数据结构
来源:drivers/sdio/mmcsd_core.h
struct os_mmcsd_data
{
uint32_t blksize;
uint32_t blks;
uint8_t *buf;
int32_t err;
uint32_t flags;
#define DATA_DIR_WRITE (1 << 0)
#define DATA_DIR_READ (1 << 1)
#define DATA_STREAM (1 << 2)
unsigned int bytes_xfered;
struct os_mmcsd_cmd *stop; /* stop command */
struct os_mmcsd_req *mrq; /* associated request */
uint32_t timeout_ns;
uint32_t timeout_clks;
};
struct os_mmcsd_cmd
{
uint32_t cmd_code;
uint32_t arg;
uint32_t resp[4];
uint32_t flags;
#define RESP_MASK (0xF)
#define RESP_NONE (0)
#define RESP_R1 (1 << 0)
#define RESP_R1B (2 << 0)
#define RESP_R2 (3 << 0)
#define RESP_R3 (4 << 0)
#define RESP_R4 (5 << 0)
#define RESP_R6 (6 << 0)
#define RESP_R7 (7 << 0)
#define RESP_R5 (8 << 0) /*SDIO command response type*/
#define CMD_MASK (3 << 4) /* command type */
#define CMD_AC (0 << 4)
#define CMD_ADTC (1 << 4)
#define CMD_BC (2 << 4)
#define CMD_BCR (3 << 4)
#define resp_type(cmd) ((cmd)->flags & RESP_MASK)
int32_t retries; /* max number of retries */
int32_t err;
struct os_mmcsd_data *data;
struct os_mmcsd_req *mrq; /* associated request */
};
struct os_mmcsd_req
{
struct os_mmcsd_data *data;
struct os_mmcsd_cmd *cmd;
struct os_mmcsd_cmd *stop;
};os_mmcsd_data 字段详解(源码无注释,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
blksize/blks | 块大小(字节)/块数量 |
buf | 数据缓冲区(读/写) |
err | 本次数据传输的错误码 |
flags | 方向标志:DATA_DIR_WRITE(1<<0) / DATA_DIR_READ(1<<1) / DATA_STREAM(1<<2) |
bytes_xfered | 已传输字节数 |
stop | 停止传输命令指针(多块传输的 CMD12) |
mrq | 关联的请求(指向所属 os_mmcsd_req) |
timeout_ns/timeout_clks | 超时(纳秒/时钟周期) |
os_mmcsd_cmd 字段详解:
| 字段 | 含义 |
|---|---|
cmd_code | 命令码(如 GO_IDLE_STATE=0、SEND_CSD=9) |
arg | 命令参数(32 位) |
resp[4] | 响应寄存器(最多 128 bit,R2 用 4 个、其余用 1 个) |
flags | 响应类型 + 命令类型(低 4 bit 响应类型、bit4~5 命令类型) |
retries | 最大重试次数 |
err | 命令错误码 |
data/mrq | 关联的数据/请求 |
响应类型表(resp_type(cmd) = cmd->flags & RESP_MASK):
| 宏 | 值 | 说明 | 响应长度 |
|---|---|---|---|
RESP_NONE | 0 | 无响应 | 0 |
RESP_R1 | 1<<0 | 常规响应 | 48 bit |
RESP_R1B | 2<<0 | 带 Busy 的 R1 | 48 bit |
RESP_R2 | 3<<0 | CID/CSD 响应 | 136 bit |
RESP_R3 | 4<<0 | OCR 响应 | 48 bit |
RESP_R4 | 5<<0 | SDIO 响应 | 48 bit |
RESP_R6 | 6<<0 | 发布 RCA 响应 | 48 bit |
RESP_R7 | 7<<0 | 接口条件响应 | 48 bit |
RESP_R5 | 8<<0 | SDIO 命令响应 | 48 bit |
命令类型表(cmd_type(cmd) = cmd->flags & CMD_MASK,bit4~5):
| 宏 | 值 | 说明 |
|---|---|---|
CMD_AC | 0<<4 | 寻址命令(ac),无数据 |
CMD_ADTC | 1<<4 | 寻址数据传输命令(adtc),带数据传输 |
CMD_BC | 2<<4 | 广播命令(bc),无响应 |
CMD_BCR | 3<<4 | 广播命令带响应(bcr) |
数值演算 ——
flags位域打包:例如读 CSD 的 CMD9,命令类型CMD_AC(0<<4)、响应类型RESP_R2(3<<0),则flags = CMD_AC | RESP_R2 = (0<<4) | (3<<0) = 0x03。resp_type/cmd_type宏分别用& RESP_MASK(0xF)和& CMD_MASK(0x30)取出对应字段。
2.4 卡信息结构体 struct os_mmcsd_card
struct os_mmcsd_card
{
struct os_mmcsd_host *host;
uint32_t rca; /* card addr */
uint32_t resp_cid[4]; /* card CID register */
uint32_t resp_csd[4]; /* card CSD register */
uint32_t resp_scr[2]; /* card SCR register */
uint16_t tacc_clks; /* data access time by ns */
uint32_t tacc_ns; /* data access time by clk cycles */
uint32_t max_data_rate; /* max data transfer rate */
uint32_t card_capacity; /* card capacity, unit:KB */
uint32_t card_blksize; /* card block size */
uint32_t erase_size; /* erase size in sectors */
uint16_t card_type;
#define CARD_TYPE_MMC 0 /* MMC card */
#define CARD_TYPE_SD 1 /* SD card */
#define CARD_TYPE_SDIO 2 /* SDIO card */
#define CARD_TYPE_SDIO_COMBO 3 /* SD combo (IO+mem) card */
uint16_t flags;
#define CARD_FLAG_HIGHSPEED (1 << 0) /* SDIO bus speed 50MHz */
#define CARD_FLAG_SDHC (1 << 1) /* SDHC card */
#define CARD_FLAG_SDXC (1 << 2) /* SDXC card */
struct os_sd_scr scr;
struct os_mmcsd_csd csd;
uint32_t hs_max_data_rate; /* max data transfer rate in high speed mode */
uint8_t sdio_function_num; /* totol number of SDIO functions */
struct os_sdio_cccr cccr; /* common card info */
struct os_sdio_cis cis; /* common tuple info */
struct os_sdio_function *sdio_function[SDIO_MAX_FUNCTIONS + 1]; /* SDIO functions (devices) */
};字段详解(源码英文注释或原样,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
host | 所属 Host 控制器 |
rca | 卡相对地址(CMD3 发布) |
resp_cid[4]/resp_csd[4]/resp_scr[2] | CID/CSD/SCR 寄存器原始值 |
tacc_clks/tacc_ns | 数据访问时间(时钟周期/纳秒) |
max_data_rate | 最大数据传输速率 |
card_capacity | 卡容量(单位 KB) |
card_blksize | 卡块大小(字节) |
erase_size | 擦除大小(扇区数) |
card_type | 卡类型(见下方) |
flags | 卡标志:HIGHSPEED/SDHC/SDXC(见下方) |
scr/csd | 解析后的 SCR/CSD 结构 |
hs_max_data_rate | 高速模式最大速率 |
sdio_function_num/cccr/cis/sdio_function[] | SDIO 功能数/CCCR/CIS/功能设备数组 |
卡类型(card_type)与容量标志(flags)(drivers/sdio/mmcsd_card.h,与项目逐字一致):
| 宏 | 值 | 说明 |
|---|---|---|
CARD_TYPE_MMC | 0 | MMC 卡 |
CARD_TYPE_SD | 1 | SD 卡 |
CARD_TYPE_SDIO | 2 | SDIO 卡 |
CARD_TYPE_SDIO_COMBO | 3 | SD 组合(IO+mem)卡 |
CARD_FLAG_HIGHSPEED | 1<<0 | SDIO 总线速度 50MHz |
CARD_FLAG_SDHC | 1<<1 | SDHC 高容量卡 |
CARD_FLAG_SDXC | 1<<2 | SDXC 扩展容量卡 |
注意:本框架没有
CARD_TYPE_SDHC/CARD_TYPE_SDXC——SDHC/SDXC 是用card.flags里的CARD_FLAG_SDHC/CARD_FLAG_SDXC标志区分的,card_type只区分 SD/MMC/SDIO/SDIO_COMBO 四类。判断容量类型应检查flags & CARD_FLAG_SDHC/& CARD_FLAG_SDXC,不要用card_type。
三、关键 API
3.1 Host 控制器分配与触发检测
说明:本版本框架使用
mmcsd_alloc_host分配 Host,HAL 层填充字段后通过mmcsd_change(发邮箱)触发卡检测线程。源码见 10.2 节。
/* 分配并初始化一个 Host 控制器 */
struct os_mmcsd_host *mmcsd_alloc_host(void);
/* 释放 Host 控制器 */
void mmcsd_free_host(struct os_mmcsd_host *host);
/* 触发卡检测(把 host 发到检测邮箱) */
void mmcsd_change(struct os_mmcsd_host *host);HAL 层需要填充 struct os_mmcsd_host 和 struct os_mmcsd_host_ops,然后调用 mmcsd_change(host) 让框架的 mmcsd_detect 线程去检测卡。
3.2 核心框架 API
/* Host 总线锁(互斥量) */
void mmcsd_host_lock(struct os_mmcsd_host *host);
void mmcsd_host_unlock(struct os_mmcsd_host *host);
/* 发送命令 */
void mmcsd_send_request(struct os_mmcsd_host *host, struct os_mmcsd_req *req);
/* 等待卡状态变化(返回 PLUGED/UNPLUGED/TIMEOUT) */
int32_t mmcsd_wait_cd_changed(int32_t timeout);
/* 热插拔事件(发邮箱通知检测线程) */
void mmcsd_change(struct os_mmcsd_host *host);3.3 卡状态检查
检查卡是否插入没有独立 API,而是通过 Host 的回调 host->ops->get_card_status(host) 实现(见 2.2),返回 MMCSD_HOST_PLUGED(0,已插入)或 MMCSD_HOST_UNPLUGED(1,未插入):
/* 检查卡是否插入(通过 ops 回调,返回值见 mmcsd_core.h) */
int32_t status = host->ops->get_card_status(host);
if (status == MMCSD_HOST_PLUGED)
{
/* 卡已插入 */
}注意:本版本没有
mmcsd_card_status这个函数。卡状态通过host->ops->get_card_status回调获取,且 HAL 层必须正确实现该回调(插入返回MMCSD_HOST_PLUGED=0,拔出返回MMCSD_HOST_UNPLUGED=1)。
四、使用示例
4.1 HAL 层 Host 注册
#include <sdio/mmcsd_host.h>
#include <sdio/mmcsd_core.h>
/* HAL 层实现 Host 操作函数 */
static void stm32_mmcsd_request(struct os_mmcsd_host *host,
struct os_mmcsd_req *req)
{
/* 发送命令,等待响应,处理数据传输 */
/* 发送完成后调用 req->cmd->err 和 req->data->err 设置结果 */
}
static void stm32_mmcsd_set_iocfg(struct os_mmcsd_host *host,
struct os_mmcsd_io_cfg *io_cfg)
{
/* 按 io_cfg 配置时钟/电压/总线宽度(HAL 层实现) */
}
static int32_t stm32_mmcsd_get_card_status(struct os_mmcsd_host *host)
{
/* 读取卡检测引脚:插入返回 MMCSD_HOST_PLUGED(0),拔出返回 MMCSD_HOST_UNPLUGED(1) */
return (GPIO_ReadPin(SD_DETECT_GPIO) == 0) ? MMCSD_HOST_PLUGED : MMCSD_HOST_UNPLUGED;
}
static struct os_mmcsd_host_ops stm32_sdio_ops =
{
.request = stm32_mmcsd_request,
.set_iocfg = stm32_mmcsd_set_iocfg,
.get_card_status = stm32_mmcsd_get_card_status,
.enable_sdio_irq = OS_NULL, /* 非 SDIO 设备可留空 */
};
/* 分配并注册 Host:用 mmcsd_alloc_host 分配,填充字段,触发检测 */
void sdio_host_init(void)
{
struct os_mmcsd_host *host;
host = mmcsd_alloc_host();
if (host == OS_NULL)
{
os_kprintf("alloc host failed\r\n");
return;
}
host->ops = &stm32_sdio_ops;
host->freq_min = 400000; /* 400KHz 识别模式 */
host->freq_max = 24000000; /* 24MHz 高速模式 */
host->valid_ocr = 0x00FF8000;/* 2.7V-3.6V */
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_SUP_HIGHSPEED;
host->private = OS_NULL;
/* 触发卡检测线程去识别卡 */
mmcsd_change(host);
}4.2 卡检测处理
卡检测由框架在 os_mmcsd_core_init 中创建的 mmcsd_detect 线程统一处理(见 5.3 节源码)。HAL 驱动或卡检测中断只需调用 mmcsd_change(host) 把 host 发到检测邮箱,框架就会在 mmcsd_detect 线程里完成上电、发命令、识别卡并创建 Block 设备。
4.3 通过 Block 设备读写
SD/MMC 卡初始化后会被封装为 Block 设备,可通过标准 Block 设备接口访问:
#include <device.h>
#include <block/block_device.h>
void sd_card_read_write_example(void)
{
os_device_t *blk_dev;
uint8_t buf[512];
int ret;
/* 打开 Block 设备(SD 卡通常命名为 sd0) */
blk_dev = os_device_open("sd0");
if (blk_dev == OS_NULL)
{
os_kprintf("SD card not found!\r\n");
return;
}
/* 读取第 0 扇区(MBR) */
ret = os_device_read_block(blk_dev, 0, buf, 1);
if (ret == 1)
{
os_kprintf("Read sector 0 success, first byte: 0x%02X\r\n", buf[0]);
}
/* 写入第 100 扇区 */
memset(buf, 0xAB, 512);
ret = os_device_write_block(blk_dev, 100, buf, 1);
if (ret == 1)
{
os_kprintf("Write sector 100 success\r\n");
}
}五、卡初始化流程
SD/MMC 卡初始化是一个复杂的协议握手过程,由 mmcsd_core.c 中的 mmcsd_card_detect() 统一管理:
mmcsd_card_detect(host)
│
├── 1. 复位 Host 控制器
│
├── 2. 发送 CMD0(GO_IDLE_STATE)→ 复位所有卡
│
├── 3. 发送 CMD8(SEND_IF_COND)→ 检查接口条件
│ ├── 有响应 → SD 2.0+ 卡
│ └── 无响应 → SD 1.x 或 MMC 卡
│
├── 4. 发送 ACMD41(SD_SEND_OP_COND)→ SD 卡初始化
│ ├── 有响应 → SD 卡 → sd_init()
│ │ ├── CMD2(ALL_SEND_CID)→ 获取 CID
│ │ ├── CMD3(SEND_RELATIVE_ADDR)→ 获取 RCA
│ │ ├── CMD9(SEND_CSD)→ 获取 CSD
│ │ ├── CMD7(SELECT_CARD)→ 选中卡
│ │ ├── ACMD51(SEND_SCR)→ 获取 SCR
│ │ ├── ACMD6(SET_BUS_WIDTH)→ 设置 4-bit 总线
│ │ └── CMD6(SWITCH_FUNC)→ 切换到高速模式
│ │
│ └── 无响应 → 尝试 MMC 初始化 → mmc_init()
│ ├── CMD1(SEND_OP_COND)→ MMC 初始化
│ ├── CMD2(ALL_SEND_CID)→ 获取 CID
│ ├── CMD3(SET_RELATIVE_ADDR)→ 设置 RCA
│ ├── CMD9(SEND_CSD)→ 获取 CSD
│ ├── CMD7(SELECT_CARD)→ 选中卡
│ └── CMD8(SEND_EXT_CSD)→ 获取扩展 CSD(eMMC)
│
├── 5. 尝试 SDIO 初始化 → sdio_init()
│ └── CMD5(IO_SEND_OP_COND)→ SDIO 设备初始化
│
├── 6. 计算卡容量
│ ├── SDSC: capacity ≤ 2GB
│ ├── SDHC: 2GB < capacity ≤ 32GB
│ └── SDXC: capacity > 32GB
│
└── 7. 注册 Block 设备
└── mmcsd_block_device_init(card) → 创建 sd0 设备六、热插拔检测
SDIO 框架支持两种卡检测方式:
6.1 GPIO 中断检测
/* GPIO 中断回调中调用:把 host 发到检测邮箱,由 mmcsd_detect 线程处理 */
void sd_cd_gpio_isr(void *arg)
{
struct os_mmcsd_host *host = (struct os_mmcsd_host *)arg;
/* 触发卡检测(发邮箱通知 mmcsd_detect 线程) */
mmcsd_change(host);
}6.2 轮询检测
框架内部定期检查卡状态,检测到变化后通过邮箱通知 mmcsd_detect_thread 处理。
七、SDIO 的适配(接入新 MCU 的 SDIO 外设)
7.1 先认识适配层:SDIO 框架是怎么"长"到芯片上的
drivers/sdio/ 下是跨平台统一的框架层(mmcsd_core.c 命令执行、卡检测线程、sd.c/mmc.c/sdio.c 协议、block_dev.c 封装),不碰具体寄存器。真正操作 SDIO 外设的是各厂商 HAL 驱动,例如 ST 的 drivers/hal/st/drivers/drv_sdio.c。换 MCU 就是换这份 drv_sdio.c。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层(文件系统读写 sd0 / SDIO 设备驱动) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ SDIO 框架层(drivers/sdio/) │
│ mmcsd_core.c 命令执行/卡检测 / sd.c mmc.c sdio.c 协议 │
│ block_dev.c 封装为 Block 设备(不改) │
└───────────────┬──────────────────────────┬────────────────┘
│ ①实现 host_ops 4 回调 │ ②中断里 mmcsd_req_complete
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 厂商驱动适配层(drv_sdio.c)★需实现★ │
│ request / set_iocfg / get_card_status / enable_sdio_irq │
│ 中断 → mmcsd_req_complete(host) │
└───────────────┬──────────────────────────┬────────────────┘
│ HAL 库(HAL_SD_*) │ 中断服务
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(MCU 的 SDIO / SDMMC 外设) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新 SDIO 外设要做两件事:① 实现 struct os_mmcsd_host_ops 四个回调(request/set_iocfg/get_card_status/enable_sdio_irq);② 用 mmcsd_alloc_host 分配 Host、填充能力字段、mmcsd_change 触发检测。框架层和卡协议都不用改。
7.2 以 ST 为例,看一整套适配代码
以 drivers/hal/st/drivers/drv_sdio.c 为例(与项目逐字一致,去除无关部分展示关键结构):
① 实现 os_mmcsd_host_ops 四个回调:
static void stm32_sdio_request(struct os_mmcsd_host *host, struct os_mmcsd_req *req)
{
/* ① 解析命令:req->cmd->cmd_code / arg / resp_type(cmd) */
/* ② 若有数据传输:req->data->buf / blksize / blks / 方向 */
/* ③ 用 HAL_SD_* 发送命令 + 启动数据收发 */
/* ④ 完成(成功/超时)后设置 req->cmd->err / req->data->err */
/* ⑤ 通知框架已处理完毕 */
mmcsd_req_complete(host);
}
static void stm32_sdio_iocfg(struct os_mmcsd_host *host, struct os_mmcsd_io_cfg *io_cfg)
{
/* 按 io_cfg 设置时钟频率 / 电源模式 / 总线宽度 */
}
static int32_t stm32_sdio_detect(struct os_mmcsd_host *host)
{
/* 读卡检测引脚,返回 MMCSD_HOST_PLUGED / MMCSD_HOST_UNPLUGED */
}
static void stm32_sdio_irq(struct os_mmcsd_host *host, int32_t en)
{
/* 启用/禁用 SDIO 中断 */
}
static const struct os_mmcsd_host_ops ops = {
stm32_sdio_request,
stm32_sdio_iocfg,
stm32_sdio_detect,
stm32_sdio_irq,
};要点:
request是核心——它要解析req->cmd(命令码/参数/响应类型)和req->data(读/写缓冲),用 HAL 发送,完成后必须调mmcsd_req_complete(host)释放框架等待的sem_ack信号量(见 5.1)。
② 分配 Host、填充能力字段、触发检测:
static int stm32_sdio_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{
struct os_mmcsd_host *host;
host = mmcsd_alloc_host();
if (host == OS_NULL)
{
os_kprintf("alloc sdio host failed\r\n");
return -1;
}
host->ops = &ops;
host->freq_min = 400000; /* 识别模式 ≤400KHz */
host->freq_max = 24000000; /* 高速 24MHz */
host->valid_ocr = 0x00FF8000; /* 2.7V~3.6V(VDD_27_28~VDD_35_36) */
host->flags = MMCSD_BUSWIDTH_4 | MMCSD_SUP_HIGHSPEED;
/* 保存厂商句柄到 private_data,request 里取出来用 */
host->private_data = (void *)dev->info; /* 例:指向 HAL 的 SD_HandleTypeDef */
mmcsd_change(host); /* 触发卡检测线程识别卡 */
return 0;
}
OS_DRIVER_INFO stm32_sdio_driver = {
.name = "SD_HandleTypeDef", /* 匹配板级配置表里的 HAL 句柄 */
.probe = stm32_sdio_probe,
};
OS_DRIVER_DEFINE(stm32_sdio_driver, OS_INIT_LEVEL_DEVICE, OS_INIT_SUBLEVEL_LOW);要点:
mmcsd_alloc_host已创建bus_lock/sem_ack,HAL 只需填ops/freq_min/freq_max/valid_ocr/flags,存厂商句柄到private_data,最后mmcsd_change(host)把 host 发到检测邮箱。mmcsd_detect线程会执行完整卡识别流程。
7.3 接口契约:接入 SDIO 需要实现什么
| 层 | 接口/回调 | 职责 | 对应 7.2 ST 代码 |
|---|---|---|---|
| 操作回调 | host_ops.request | 发送命令 + 传输数据,完成后 mmcsd_req_complete | stm32_sdio_request |
| 操作回调 | host_ops.set_iocfg | 设置时钟/电源/总线宽 | stm32_sdio_iocfg |
| 操作回调 | host_ops.get_card_status | 卡插入状态 | stm32_sdio_detect |
| 操作回调 | host_ops.enable_sdio_irq | 启停 SDIO 中断 | stm32_sdio_irq |
| 分配 | mmcsd_alloc_host | 分配 Host + 建锁/信号量 | probe 里调用 |
| 触发 | mmcsd_change | 触发检测线程 | probe 末尾调用 |
7.4 适配新 MCU 的标准流程
第 1 步:确认 SDIO 外设能力——查手册确认支持的频率范围、总线宽度(1/4/8-bit)、是否支持高速。
第 2 步:新建厂商 drv_sdio.c——在厂商 HAL 目录新建,实现 os_mmcsd_host_ops 四个回调。
第 3 步:实现 request——这是最复杂部分:解析 req->cmd(命令码/参数/响应类型)、req->data(读写缓冲),用 HAL 发送,完成后 mmcsd_req_complete。
第 4 步:写 probe 并注册——mmcsd_alloc_host 分配、填能力字段、存厂商句柄到 private_data、mmcsd_change 触发检测,配 OS_DRIVER_INFO/OS_DRIVER_DEFINE。
第 5 步:板级配置——在 board/peripherals.c 用 OS_HAL_DEVICE_DEFINE("SD_HandleTypeDef", "sd0", hsd) 之类把 HAL 句柄实例化。
第 6 步:测试——用 ls/挂载文件系统或 os_device_read_block 读写验证;用 mmcsd_change 测热插拔。
7.5 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
| 四个回调都实现? | 漏 request 则命令发不出,漏 get_card_status 则检测不到卡 |
request 完成后调 mmcsd_req_complete? | 否则框架 sem_ack 永远等不到,命令挂死 |
识别频率用 freq_min(≤400KHz)? | 识别时频率太高卡不响应 |
valid_ocr 与卡电压匹配? | 不匹配则 ACMD41 一直 busy,卡上不了电 |
mmcsd_alloc_host 后再填字段? | 直接在未分配内存上填会崩溃 |
板级 OS_HAL_DEVICE_DEFINE 名匹配 OS_DRIVER_INFO.name? | 不匹配则 probe 不触发 |
八、注意事项
- 识别频率: 卡初始化阶段使用低速时钟(≤400KHz),初始化完成后切换到高速时钟
- 总线宽度: 默认为 1-bit 模式,初始化后通过 ACMD6 切换到 4-bit 或 8-bit 模式
- 电压范围: Host 的
valid_ocr需与卡支持的电压范围匹配 - DMA 传输: 大数据量传输应使用 DMA 模式,DMA 能力通过
host的max_dma_segs/max_seg_size字段声明,HAL 层在request内部配置 DMA(本框架os_mmcsd_host_ops没有config_dma回调) - SDIO 设备: WiFi/BT 等 SDIO 设备初始化后不创建 Block 设备,而是通过
sdio.c中的 IO_RW 命令直接通信 - 热插拔: 不要在卡读写过程中拔卡,应先
umount文件系统再操作
九、驱动文件位置
| 文件 | 路径 | 说明 |
|---|---|---|
mmcsd_core.h / mmcsd_core.c | drivers/sdio/ | 核心框架:命令执行、卡检测、热插拔 |
mmcsd_host.h | drivers/sdio/ | Host 控制器抽象接口 |
mmcsd_cmd.h | drivers/sdio/ | SD/MMC 命令码定义 |
mmcsd_card.h | drivers/sdio/ | 卡信息结构体定义 |
sd.h / sd.c | drivers/sdio/ | SD 卡协议实现 |
mmc.h / mmc.c | drivers/sdio/ | MMC/eMMC 协议实现 |
sdio.h / sdio.c | drivers/sdio/ | SDIO 设备协议实现 |
block_dev.c | drivers/sdio/ | Block 设备封装 |
drv_sdio.c(示例) | drivers/hal/st/drivers/drv_sdio.c | ST 厂商 SDIO 适配层,实现 4 回调 |
Kconfig | drivers/sdio/ | 编译配置选项 |
十、核心源码详解
说明:以下源码均取自
drivers/sdio/mmcsd_core.c,与项目源码逐字一致(含原始英文注释)。
10.1 命令请求 mmcsd_send_request
一句话定位:发送一个命令请求(含可能的数据传输),通过信号量等待 HAL 层完成。
完整源码(drivers/sdio/mmcsd_core.c,与项目一致):
void mmcsd_send_request(struct os_mmcsd_host *host, struct os_mmcsd_req *req)
{
do
{
req->cmd->retries--;
req->cmd->err = 0;
req->cmd->mrq = req;
if (req->data)
{
req->cmd->data = req->data;
req->data->err = 0;
req->data->mrq = req;
if (req->stop)
{
req->data->stop = req->stop;
req->stop->err = 0;
req->stop->mrq = req;
}
}
host->ops->request(host, req);
os_semaphore_wait(host->sem_ack, OS_WAIT_FOREVER);
} while (req->cmd->err && (req->cmd->retries > 0));
}分步详解:
第 1 步:准备命令并调用 HAL(无条件执行)
do
{
req->cmd->retries--;
req->cmd->err = 0;
req->cmd->mrq = req;
if (req->data)
{
req->cmd->data = req->data;
req->data->err = 0;
req->data->mrq = req;
if (req->stop)
{
req->data->stop = req->stop;
req->stop->err = 0;
req->stop->mrq = req;
}
}
host->ops->request(host, req);详细解释:每次尝试前把 retries 减一、清空错误。把 req(含命令和数据)关联到 cmd->mrq。若有数据传输,把 req->data 关联到 cmd->data,并把 data 的 mrq 指回 req;若还有停止命令(多块传输的 req->stop,如 CMD12),把 req->stop 关联到 data->stop 并初始化其 err/mrq。然后调用 HAL 层的 host->ops->request(host, req) 真正执行命令。注意:do-while 的重试会重新执行这段准备逻辑,因此每次尝试都会重新关联并清空错误。
第 2 步:等待完成信号量(无条件执行)
os_semaphore_wait(host->sem_ack, OS_WAIT_FOREVER);
} while (req->cmd->err && (req->cmd->retries > 0));
}详细解释:阻塞等待 sem_ack 信号量。HAL 层发送完成后调用 mmcsd_req_complete(os_semaphore_post(host->sem_ack))释放该信号量(见 2.1 节)。关键点:若命令出错(cmd->err)且还有重试次数,do-while 会自动重试。
mmcsd_send_request() 条件走向总结:
mmcsd_send_request() 条件走向总结:
├── do:
│ ├── retries--;cmd->err = 0;cmd->mrq = req
│ ├── 有 data:
│ │ ├── cmd->data = data;data->err = 0;data->mrq = req
│ │ └── 有 stop(多块停止命令):
│ │ ├── data->stop = stop;stop->err = 0;stop->mrq = req
│ ├── host->ops->request(host, req)(HAL 执行)
│ └── os_semaphore_wait(sem_ack)(等 HAL 完成)
├── while (cmd->err && retries > 0) → 出错且有重试则重发10.2 Host 分配 mmcsd_alloc_host
一句话定位:分配一个 Host 对象并初始化默认参数与同步对象(互斥量 + 信号量)。
完整源码(drivers/sdio/mmcsd_core.c,与项目一致):
struct os_mmcsd_host *mmcsd_alloc_host(void)
{
struct os_mmcsd_host *host;
host = (struct os_mmcsd_host *)os_calloc(1, sizeof(struct os_mmcsd_host));
if (!host)
{
LOG_E(DBG_TAG, "alloc host failed");
return OS_NULL;
}
memset(host, 0, sizeof(struct os_mmcsd_host));
host->max_seg_size = 65535;
host->max_dma_segs = 1;
host->max_blk_size = 512;
host->max_blk_count = 4096;
host->bus_lock = os_mutex_create(OS_NULL, "sd_bus_lock", OS_FALSE);
host->sem_ack = os_semaphore_create(OS_NULL, "sd_ack", 0, 1);
return host;
}分步详解:
第 1 步:分配并初始化默认参数(条件 分配失败)
host = (struct os_mmcsd_host *)os_calloc(1, sizeof(struct os_mmcsd_host));
if (!host)
{
LOG_E(DBG_TAG, "alloc host failed");
return OS_NULL;
}
memset(host, 0, sizeof(struct os_mmcsd_host));
host->max_seg_size = 65535;
host->max_dma_segs = 1;
host->max_blk_size = 512;
host->max_blk_count = 4096;详细解释:os_calloc 分配并清零 Host。设置默认传输能力(最大段 65535、DMA 段 1、块 512、块数 4096),HAL 层可在注册前按需覆盖。
第 2 步:创建同步对象(无条件执行)
host->bus_lock = os_mutex_create(OS_NULL, "sd_bus_lock", OS_FALSE);
host->sem_ack = os_semaphore_create(OS_NULL, "sd_ack", 0, 1);
return host;
}详细解释:bus_lock 是总线互斥量(mmcsd_host_lock/unlock 用它保护整条 SD 总线)。sem_ack 是命令完成信号量(初值 0,mmcsd_send_request 等待它,mmcsd_req_complete 释放它)。
10.3 核心初始化 os_mmcsd_core_init
一句话定位:创建两个邮箱(检测/热插拔)和 mmcsd_detect 卡检测线程,再初始化 SDIO。
完整源码(drivers/sdio/mmcsd_core.c,与项目一致):
int os_mmcsd_core_init(void)
{
/* initialize detect SD cart task */
/* initialize mailbox and create detect SD card task */
mmcsd_detect_mb = os_mailbox_create_static(&mmcsd_detect_mb_dummy,
"mmcsdmb",
&mmcsd_detect_mb_pool[0],
sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]));
OS_ASSERT(mmcsd_detect_mb != NULL);
mmcsd_hotpluge_mb = os_mailbox_create_static(&mmcsd_hotpluge_mb_dummy,
"mmcsdhotplugmb",
&mmcsd_hotpluge_mb_pool[0],
sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]));
OS_ASSERT(mmcsd_hotpluge_mb != NULL);
mmcsd_detect_thread = os_task_create(&mmcsd_detect_thread_dummy,
&mmcsd_stack[0],
OS_MMCSD_STACK_SIZE,
"mmcsd_detect",
mmcsd_detect,
OS_NULL,
OS_MMCSD_TASK_PREORITY);
if (mmcsd_detect_thread)
{
os_task_startup(mmcsd_detect_thread);
}
os_sdio_init();
return 0;
}
OS_INIT_CALL(os_mmcsd_core_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_HIGH);分步详解:
第 1 步:创建检测与热插拔邮箱(无条件执行)
mmcsd_detect_mb = os_mailbox_create_static(&mmcsd_detect_mb_dummy,
"mmcsdmb",
&mmcsd_detect_mb_pool[0],
sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]));
OS_ASSERT(mmcsd_detect_mb != NULL);
mmcsd_hotpluge_mb = os_mailbox_create_static(&mmcsd_hotpluge_mb_dummy,
"mmcsdhotplugmb",
&mmcsd_hotpluge_mb_pool[0],
sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]));
OS_ASSERT(mmcsd_hotpluge_mb != NULL);详细解释:创建两个静态邮箱。mmcsd_detect_mb 用于卡检测(mmcsd_change 发 host),mmcsd_hotpluge_mb 用于热插拔通知(mmcsd_wait_cd_changed 等待)。
第 2 步:创建卡检测线程(条件 创建成功)
mmcsd_detect_thread = os_task_create(&mmcsd_detect_thread_dummy,
&mmcsd_stack[0],
OS_MMCSD_STACK_SIZE,
"mmcsd_detect",
mmcsd_detect,
OS_NULL,
OS_MMCSD_TASK_PREORITY);
if (mmcsd_detect_thread)
{
os_task_startup(mmcsd_detect_thread);
}
os_sdio_init();
return 0;
}
OS_INIT_CALL(os_mmcsd_core_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_HIGH);详细解释:创建并启动 mmcsd_detect 线程(函数 mmcsd_detect),它阻塞在 mmcsd_detect_mb 邮箱上等待检测请求。最后 os_sdio_init()。OS_INIT_CALL(os_mmcsd_core_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_HIGH) 让它在设备初始化前自动执行。
10.4 卡检测线程 mmcsd_detect
一句话定位:框架的核心卡检测线程,收到 host 后上电并尝试 SDIO / SD / MMC 初始化。
完整源码(drivers/sdio/mmcsd_core.c,与项目逐字一致):
void mmcsd_detect(void *param)
{
struct os_mmcsd_host *host;
uint32_t ocr;
int32_t err;
while (1)
{
if (os_mailbox_recv(mmcsd_detect_mb, (os_ubase_t *)&host, OS_WAIT_FOREVER) == OS_SUCCESS)
{
if (host->card == OS_NULL)
{
mmcsd_host_lock(host);
mmcsd_power_up(host);
mmcsd_go_idle(host);
mmcsd_send_if_cond(host, host->valid_ocr);
err = sdio_io_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_sdio(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
continue;
}
/*
* detect SD card
*/
err = mmcsd_send_app_op_cond(host, 0, &ocr);
if (!err)
{
if (init_sd(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
continue;
}
/*
* detect mmc card
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_mmc(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
continue;
}
mmcsd_host_unlock(host);
}
else
{
/* card removed */
mmcsd_host_lock(host);
if (host->card->sdio_function_num != 0)
{
LOG_W(DBG_TAG, "unsupport sdio card plug out!");
}
else
{
os_mmcsd_blk_remove(host->card);
os_free(host->card);
host->card = OS_NULL;
}
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
}
}
}
}分步详解:
第 1 步:等待检测请求(无条件执行)
if (os_mailbox_recv(mmcsd_detect_mb, (os_ubase_t *)&host, OS_WAIT_FOREVER) == OS_SUCCESS)
{
if (host->card == OS_NULL)
{
mmcsd_host_lock(host);
mmcsd_power_up(host);
mmcsd_go_idle(host);
mmcsd_send_if_cond(host, host->valid_ocr);详细解释:阻塞等待 mmcsd_detect_mb 邮箱(mmcsd_change 发 host 到这里)。若 host->card == OS_NULL(还没卡),上电、发 CMD0(mmcsd_go_idle)、发 CMD8(mmcsd_send_if_cond 检查接口条件)。
第 2 步:尝试 SDIO 初始化(条件 无错误)
err = sdio_io_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_sdio(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
continue;
}详细解释:发 CMD5(sdio_io_send_op_cond)探测 SDIO 设备。有响应说明是 SDIO 设备,调 init_sdio 初始化(WiFi/BT 等),然后继续等下一张卡。
第 3 步:尝试 SD 卡初始化(条件 无错误)
err = mmcsd_send_app_op_cond(host, 0, &ocr);
if (!err)
{
if (init_sd(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
continue;
}详细解释:SDIO 无响应则发 ACMD41(mmcsd_send_app_op_cond)探测 SD 卡。有响应说明是 SD 卡,调 init_sd 初始化(CMD2/CMD3/CMD9/CMD7/ACMD6 等),成功后发邮箱 mmcsd_hotpluge_mb 通知热插拔(上层可 mmcsd_wait_cd_changed 等待)。
第 4 步:尝试 MMC 卡初始化(条件 无错误)
/*
* detect mmc card
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_mmc(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
continue;
}
mmcsd_host_unlock(host);详细解释:SD 也无响应则发 CMD1(mmc_send_op_cond)探测 MMC/eMMC 卡。有响应说明是 MMC 卡,调 init_mmc 初始化(CMD2/CMD3/CMD9/CMD7 等)。三个探测分支(SDIO→SD→MMC)都不成功后,mmcsd_host_unlock 释放锁,继续循环等待下一次检测请求。
第 5 步:处理拔卡(条件 host->card != OS_NULL)
else
{
/* card removed */
mmcsd_host_lock(host);
if (host->card->sdio_function_num != 0)
{
LOG_W(DBG_TAG, "unsupport sdio card plug out!");
}
else
{
os_mmcsd_blk_remove(host->card);
os_free(host->card);
host->card = OS_NULL;
}
mmcsd_host_unlock(host);
os_mailbox_send(mmcsd_hotpluge_mb, (uint32_t)host, 1000);
}详细解释:若 host->card != OS_NULL(检测请求到来但已有卡),说明卡被拔出。SDIO 卡不支持热拔(sdio_function_num != 0 时只打警告);普通 SD/MMC 卡则 os_mmcsd_blk_remove 卸载 Block 设备、os_free 释放卡结构、置 card = OS_NULL,并发热插拔邮箱通知上层。
mmcsd_detect() 条件走向总结:
mmcsd_detect() 条件走向总结:
├── while (1):os_mailbox_recv(mmcsd_detect_mb, host, FOREVER)
│ ├── host->card == OS_NULL(新卡检测)
│ │ ├── 上电 + CMD0 + CMD8
│ │ ├── CMD5(SDIO 探测)
│ │ │ ├── 有响应 → init_sdio(host) → continue
│ │ ├── ACMD41(SD 探测)
│ │ │ ├── 有响应 → init_sd(host) → 发热插拔邮箱 → continue
│ │ ├── CMD1(MMC 探测)
│ │ │ ├── 有响应 → init_mmc(host) → 发热插拔邮箱 → continue
│ │ └── 三个分支均无响应 → unlock → 继续循环
│ └── host->card != OS_NULL(卡拔出)
│ ├── 是 SDIO 卡(sdio_function_num != 0)→ LOG_W 不支持热拔
│ └── 普通卡 → os_mmcsd_blk_remove + os_free + card=NULL
│ └── 发热插拔邮箱 → continue📝 本节小结
核心要点回顾
本节围绕 SDIO/MMC 设备展开,核心要点如下:
- 分层架构:Block 接口 → SD/MMC/SDIO 协议层 → mmcsd_core 核心框架 → Host 抽象 → HAL 硬件实现。
- Host 分配:HAL 用
mmcsd_alloc_host分配 Host(自动建bus_lock/sem_ack),填充ops/freq/valid_ocr/flags,用mmcsd_change触发检测。 - 命令机制:
mmcsd_send_request通过信号量sem_ack同步等待 HAL 完成,出错自动重试。 - 检测线程:
os_mmcsd_core_init创建mmcsd_detect线程,按"SDIO(CMD5) → SD(ACMD41) → MMC(CMD1)"顺序识别卡。 - 初始化流程:上电 → CMD0 → CMD8 → ACMD41/CMD1 → CMD2/CID → CMD3/RCA → CMD9/CSD → CMD7 → 切总线宽度/高速 → 计算容量 → 建 Block 设备。
- Host 能力字段:
valid_ocr用 VDD 位掩码(如 2.7~3.6V =0x00FF8000)、flags用MMCSD_BUSWIDTH_4/MMCSD_SUP_HIGHSPEED等声明能力,io_cfg.bus_width用另一套MMCSD_BUS_WIDTH_*宏。 - 硬件适配:接入新 SDIO = 实现
os_mmcsd_host_ops四个回调(request/set_iocfg/get_card_status/enable_sdio_irq)+mmcsd_alloc_host+mmcsd_change,框架层不用改。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
用了不存在的 mmcsd_host_register | 本版本用 mmcsd_alloc_host 分配 + mmcsd_change(host) 触发检测,不是 host_register |
用了不存在的 config_dma 回调 | os_mmcsd_host_ops 只有 4 个回调;DMA 能力用 host->max_dma_segs/max_seg_size 声明 |
用了不存在的 mmcsd_card_status | 卡状态用 host->ops->get_card_status(host) 回调,返回 MMCSD_HOST_PLUGED/UNPLUGED |
| flags 宏拼写错误 | 正确是 MMCSD_SUP_SDIO_IRQ/MMCSD_SUP_HIGHSPEED(SUP 非 SUPP);没有 MMCSD_BUSWIDTH_1 |
| 识别频率过高 | 卡初始化阶段用低速(≤400KHz),初始化后才可切高速,否则识别失败 |
忽略 valid_ocr 电压匹配 | Host 的 valid_ocr 必须覆盖卡的工作电压,不匹配卡上不了电 |
| 总线宽度未切换 | 默认 1-bit,初始化后要通过 ACMD6 切 4-bit/8-bit 才有高速 |
| 在读写中热拔插 | 应先在应用层 umount 文件系统再拔卡,避免数据损坏 |
混淆 mmcsd_change 与 mmcsd_send_request | mmcsd_change 发邮箱触发检测;mmcsd_send_request 真正执行命令 |
下一步
掌握了 SDIO/MMC 设备后,接下来学习 Console 控制台设备:
- 5.19 Console设备:了解 OneOS 的 VT100 终端控制台、历史命令与重定向机制。