第 5 课:MMC Core + hc16 Host(WiFi SDIO 如何落到控制器)
接第 1~4 课。
前四课站在 8733 WiFi 驱动 视角;本课下沉到 MMC/SDIO 子系统 + hc16 Host 。
目标:任意一次
sdio_readb/memcpy_toio/ DAT1 中断,都能点名到dw_mci_request/mmc_signal_sdio_irq。正文自包含,不必翻代码。
对应源码(备查):
- MMC:
mmcdriver-*/drivers/mmc/core/core.c(rescan)sdio.c(attach/init)sdio_ops.c/sdio_io.c/sdio_irq.c/sdio_bus.c/sdio_cis.c
- Host:
mmcdriver-*/drivers/mmc/host/hc16/dw_mmc.c、dw_mmc-pltfm-hc.c
- 头文件:
linux/mmc/host.h、sdio.h、sdio_func.h
0. 本课总画面
把整条栈再叠一层:
text
第1~4课:RTL8733BS WiFi 驱动
│ sdio_* API
▼
本课上半:MMC SDIO Core(协议与总线模型)
│ mmc_host_ops.request / enable_sdio_irq / set_ios
▼
本课下半:hc16 DW MMC Host(控制器驱动)
│
▼
SDIO 物理线:CMD / CLK / DAT0-3(DAT1 兼中断)
一句话:
8733 是客户;MMC core 是翻译官;hc16 是干活的硬件管家。
1. 启动顺序:谁先谁后
结合第 1 课,完整顺序应是:
text
1. Host 驱动 probe(hc16)
→ mmc_alloc_host → 填 mmc->ops → mmc_add_host → mmc_start_host
2. MMC core rescan
→ mmc_rescan → mmc_rescan_try_freq
→ 优先 mmc_attach_sdio(WiFi 场景)
3. SDIO 识卡成功
→ 读 CCCR/CIS,创建 sdio_func
→ sdio_register_bus 上的驱动 match
4. 8733 驱动已 register 或稍后 register
→ match 0x024c:0xB733/B73A
→ rtw_drv_init(第 1 课)
关键结论:
- WiFi
probe之前,MMC 往往已经用 CMD5/CMD52 等把卡识别完了 - 所以第 1 课说:
sdio_register_driver时"本驱动"未必读过寄存器,但 MMC 可能已经读过
2. MMC 对象模型(本课必记)
| 对象 | 含义 |
|---|---|
mmc_host |
一个 SDIO/MMC 控制器抽象 |
mmc_host_ops |
Host 回调表:request / set_ios / enable_sdio_irq... |
mmc_card |
已识别的卡 |
sdio_func |
卡上某个 Function(WiFi 通常 Func1) |
mmc_request |
一次完整请求:命令 + 可选数据 |
mmc_command |
一条命令(含 opcode/arg/resp) |
mmc_data |
数据阶段(读/写、block、sg) |
mmc_ios |
时钟、位宽、电压、时序等 IO 设置 |
WiFi 驱动拿到的是 sdio_func *;
它内部挂着 func->card,再挂着 card->host,最终打到 host->ops->request。
text
sdio_func
→ mmc_card
→ mmc_host
→ ops->request(host, mrq) // hc16: dw_mci_request
3. 识卡路径:mmc_attach_sdio(WiFi 能 probe 的前提)
3.1 rescan 入口
text
mmc_start_host
→ detect work
→ mmc_rescan
→ mmc_rescan_try_freq
power_up
sdio_reset / go_idle / CMD8(if_cond)
依次尝试:
mmc_attach_sdio
mmc_attach_sd
mmc_attach_mmc
WiFi 模组走 mmc_attach_sdio。
3.2 mmc_sdio_init_card 做什么(概念顺序)
text
CMD5(IO OCR)------ 询问/设定工作电压
分配 mmc_card,类型 SDIO
(可选)1.8V/UHS 切换 ------ 本树可能被关掉
读 RCA、select card
读 CCCR(能力、速度、总线宽度等)
读 CIS(vendor/device,供驱动 match)
切高速 / 4bit(若双方支持)
为每个 Function 建 sdio_func,读 FBR/CIS
mmc_add_card → sdio_add_func
→ 总线 match → 调用 WiFi probe
3.3 和第 1 课的衔接点
| MMC 已完成 | 8733 probe 再做 |
|---|---|
认出卡、建 sdio_func |
enable_func(确保 Function 开着) |
| 读出 CIS ID | id_table match |
| 可能已设总线宽度/时钟 | set_block_size(512) |
| 尚未挂 WiFi 业务中断 handler | claim_irq(sd_sync_int_hdl) |
4. 命令路径:从 WiFi API 到 dw_mci_request
4.1 CMD52:sdio_readb 等
text
8733: sdio_readb / sdio_writeb / sdio_f0_readb
→ MMC: mmc_io_rw_direct
构造 mmc_command
opcode = SD_IO_RW_DIRECT (CMD52)
arg = 写方向 | fn | addr | data
→ mmc_wait_for_cmd(host, &cmd)
→ host->ops->request(host, mrq) // 实际常包在 wait 路径里
→ hc16: dw_mci_request
→ 控制器发命令,等响应
→ mmc_request_done
CMD52 通常没有 data 阶段,只有命令 + 响应里带 1 字节。
4.2 CMD53:sdio_memcpy_* / readsb/writesb
text
8733: sdio_memcpy_fromio / toio / readsb / writesb
→ MMC: mmc_io_rw_extended
构造 mrq:
cmd.opcode = SD_IO_RW_EXTENDED (CMD53)
cmd.arg 含:写方向、fn、addr、是否递增、byte/block
data: 读或写、blksz、blocks、sg 缓冲区
→ mmc_wait_for_req(host, &mrq)
→ dw_mci_request
→ 命令 + 数据(可用 DMA)
→ mmc_request_done
| WiFi/内核 API | incr_addr |
模式直觉 |
|---|---|---|
memcpy_fromio/toio |
1(递增) | 连续地址窗 |
readsb/writesb |
0(固定) | FIFO 口 |
第 2/4 课提过:8733 主路径常 fixed=0 走 memcpy_*,但 TX/RX 地址是特殊编码。
4.3 mmc_request 长什么样(概念)
text
mrq
├─ cmd : opcode/arg/flags/resp/error
├─ data : blksz/blocks/flags/sg/...(CMD52 常无)
└─ stop : 某些传输才有(SDIO 常见路径未必用)
Host 驱动只认 mrq,不关心上面是 WiFi 还是存储。
5. hc16 Host:dw_mci_ops 与 dw_mci_request
5.1 Host 注册时挂上的 ops(概念)
text
dw_mci_ops:
.request = dw_mci_request ← 所有命令入口
.set_ios = dw_mci_set_ios ← 时钟/位宽/电压时序
.get_cd = ...
.enable_sdio_irq= dw_mci_enable_sdio_irq
.execute_tuning = ...
.start_signal_voltage_switch = ...
...
平台文件 dw_mmc-pltfm-hc.c 负责:
- 从 FDT/DTS 取基址、时钟、pinmux、相位延迟等
- 调用
dw_mci_pltfm_register→dw_mci_probe mmc_alloc_host/mmc_add_host
5.2 dw_mci_request 做什么
text
dw_mci_request(mmc, mrq)
若卡不在 → mrq->cmd->error = -ENOMEDIUM; request_done; 返回
加锁
把 mrq 排入该 slot 队列(或立刻启动)
解锁
(后续在状态机/中断里发 CMD、搬数据、完成后 mmc_request_done)
完成路径:
text
控制器中断:命令完成 / 数据完成 / 错误
→ 填 cmd->error / data->error
→ mmc_request_done(mmc, mrq)
→ 唤醒 mmc_wait_for_req/cmd 的等待者
→ 返回到 sdio_io / 8733 驱动
5.3 set_ios 何时出现
MMC core 在识卡、切高速、改总线宽度、调时钟时调用:
text
mmc_set_clock / mmc_set_bus_width / mmc_set_timing ...
→ host->ops->set_ios
→ dw_mci_set_ios
WiFi 驱动一般不直接调 set_ios;它改的是芯片寄存器。
总线时钟/位宽多在 MMC 识卡或电源管理路径里改。
6. 中断路径:从 DAT1 到第 3 课回调
这是把第 3 课和下层钉死的关键图。
6.1 Host 硬件中断
text
芯片拉 DAT1
→ DW MMC 产生 SDIO 中断状态位(INT_SDIO)
→ dw_mci IRQ handler
→ 清 RINTSTS 对应位
→ mmc_signal_sdio_irq(slot->mmc)
6.2 MMC SDIO IRQ 线程
text
mmc_signal_sdio_irq
→ 唤醒 sdio_irq_thread(ksdioirqd/...)
→ 线程里:
claim_host
process_sdio_pending_irqs
(可读 CCCR INTx,或单功能直调 handler)
调用 func->irq_handler
→ 8733: sd_sync_int_hdl
release_host
→ enable_sdio_irq(1) 再等下一次
sdio_claim_irq(第 1 课)时:
- 写 CCCR 中断使能(IENx)
sdio_card_irq_get→ 创建/引用 IRQ 线程- 保存
func->irq_handler = sd_sync_int_hdl
6.3 为何处理中常 enable_sdio_irq(0/1)
text
收到 DAT1 → 先关 SDIO IRQ → 处理 → 再开
防止处理期间中断风暴;
8733 在 handler 里还会读 HISR、搬 FIFO(第 3 课),这段时间 Host 侧通常保持"已消费本次门铃"的状态。
7. 一张总对照表(8733 ↔ MMC ↔ Host)
| 8733 / 内核 API | MMC Core | hc16 Host |
|---|---|---|
sdio_register_driver |
sdio_bus match/probe |
(卡已由 host+core 建好) |
sdio_enable_func |
写 CCCR IOEx(CMD52) | dw_mci_request |
sdio_set_block_size |
写 FBR/相关(CMD52) | dw_mci_request |
sdio_readb/writeb |
mmc_io_rw_direct CMD52 |
dw_mci_request |
memcpy_* / readsb/writesb |
mmc_io_rw_extended CMD53 |
dw_mci_request + DMA/FIFO |
sdio_claim_irq |
sdio_irq.c + CCCR IENx |
enable_sdio_irq |
| DAT1 来临 | mmc_signal_sdio_irq → handler |
IRQ 里看 INT_SDIO |
| (间接)改时钟位宽 | set_ios 路径 |
dw_mci_set_ios |
8. 三条端到端路径(请背)
8.1 识卡到 WiFi probe
text
hc16 probe → mmc_add_host → mmc_start_host
→ mmc_rescan → mmc_attach_sdio → mmc_sdio_init_card
→ CIS 得到 0x024c:0xB733
→ sdio_add_func → match → rtw_drv_init
8.2 TX 一帧落到线(接第 4 课)
text
write_cmd53 → raw_write → sdio_memcpy_toio/writesb
→ mmc_io_rw_extended(CMD53)
→ mmc_wait_for_req
→ dw_mci_request
→ DMA/FIFO 写出
→ mmc_request_done
→ 返回 8733
8.3 RX 中断落到 8733(接第 3 课)
text
DAT1 → dw_mci IRQ → mmc_signal_sdio_irq
→ sdio_irq_thread → sd_sync_int_hdl
→ sd_int_hdl → read_port → 再次 dw_mci_request(CMD53 读)
注意:一次 RX 至少两段 Host 事务------
先可能有寄存器读(HISR),再有 FIFO CMD53 读;都走 dw_mci_request。
9. 平台相关:hc16 上你还该知道的点
9.1 能力与限制
Host 的 caps / f_max / ocr_avail 决定:
- 能否 4bit
- 最高时钟
- 是否声明支持 SDIO IRQ
- 电压范围
这些来自平台数据 / DTS,在 dw_mmc-pltfm-hc.c 一类文件配置。
9.2 Tuning / 1.8V
高速 SDIO 可能涉及:
execute_tuningstart_signal_voltage_switch
本树 SDIO 1.8V UHS 路径可能被裁掉(#if 0)。
若 WiFi 只能跑在 3.3V HS,排障时先确认 实际 ios.timing / clock,不要假设一定进了 UHS。
9.3 和存储卡共享认知
同一套 dw_mci_request 既服务 SD 存储,也服务 SDIO WiFi。
差别在 core 上层协议(SD 命令集 vs CMD52/53),不在 Host 入口。
10. 排障:从 WiFi 症状定位到哪一层
| 现象 | 优先怀疑 |
|---|---|
| 根本没有 WiFi probe | Host 没扫到卡 / attach_sdio 失败 / CIS ID 不匹配 |
| probe 到但 enable_func 失败 | 电源、F0/CCCR、claim_host 异常 |
| 能读写寄存器,不能收包 | DAT1 / enable_sdio_irq / HIMR / claim_irq |
| CMD53 大数据总超时 | 时钟、相位/tuning、DMA、对齐、信号完整性 |
| TX 很慢但 CMD 成功 | 多半仍在 8733 tx_allowed/聚合(第 4 课),不是 Host 发不出去 |
| 偶发 -ENOMEDIUM | 掉电、复位、接触、surprise remove |
分层口诀:
probe 问题先看 MMC 识卡;中断问题看 DAT1;快慢问题先分清是 page 门禁还是 SDIO 线。
11. 五课总串联图
text
[第1课] register/probe/enable_func/claim_irq
[第2课] CMD52/53 与 raw_read/write
[第3课] DAT1 → HISR → read_port(CMD53)
[第4课] enqueue → tx_allowed → write_port(CMD53)
[第5课] 以上全部经 MMC core 落到 dw_mci_request / SDIO IRQ
text
应用/协议栈
│
▼
RTL8733BS(第1~4课)
│ sdio_*
▼
MMC SDIO Core(本课)
│ mmc_host_ops
▼
hc16 DW MMC(本课)
│
▼
SDIO 物理总线 ↔ 8733 芯片
12. 本课关键名词
| 名词 | 一句话 |
|---|---|
| mmc_host | 控制器抽象 |
| mmc_host_ops | Host 回调表 |
| mmc_request | 一次命令(+数据)请求 |
| mmc_io_rw_direct | CMD52 实现 |
| mmc_io_rw_extended | CMD53 实现 |
| mmc_attach_sdio | SDIO 识卡入口 |
| CCCR/CIS | 公共控制与识别信息 |
| dw_mci_request | hc16 接收 mrq 的入口 |
| set_ios | 配时钟/位宽/时序 |
| mmc_signal_sdio_irq | Host 把 DAT1 事件交给 SDIO 核心 |
| sdio_irq_thread | 调用各 func handler 的线程 |
| INT_SDIO | DW MMC 的 SDIO 中断状态位 |
13. 本课必须记住的 8 句话
- WiFi 驱动是 SDIO 客户,MMC core 先识卡再建 func。
- 所有 CMD52/53 最终变成
mmc_request交给host->ops->request。 - hc16 入口是
dw_mci_request,出口是mmc_request_done。 - CMD52 基本无 data;CMD53 有 data(可 DMA)。
- DAT1 → Host IRQ →
mmc_signal_sdio_irq→sd_sync_int_hdl。 claim_irq同时动 CCCR 与 Hostenable_sdio_irq。set_ios管总线参数,不是 8733 自己的 WiFi 寄存器。- 排障先分层:识卡 / 中断 / 命令超时 / 驱动 page 门禁。
14. 自测题
题 1
为什么说 8733 的 rtw_drv_init 能被调用,通常意味着 MMC 已经成功 attach_sdio?
题 2
sdio_readb 与 sdio_memcpy_toio 分别对应 MMC 里哪两个函数?命令号是什么?
题 3
dw_mci_request 如果不存在或卡住,对 8733 的 TX/RX 会有什么影响?
题 4
画出从 DAT1 到 sd_sync_int_hdl 的 Host/MMC 关键字链。
题 5
TX 很慢,但抓到 CMD53 都成功返回:更应先查第 4 课的哪一环,还是先查 Host tuning?
题 6
mmc_rescan_try_freq 里为何优先 attach_sdio 再 attach_sd?
题 7
enable_sdio_irq(0) 和 8733 的 rtw_hal_disable_interrupt 是同一层吗?
题 8
按层列出一次 RX 收包会触发的两类(或更多)dw_mci_request 事务。
15. 自测题标准答案
题 1
rtw_drv_init 是 sdio_driver.probe,只有总线里已经有匹配的 sdio_func 才会调用;
而 sdio_func 是 mmc_attach_sdio / mmc_sdio_init_card 创建并 sdio_add_func 的。
题 2
sdio_readb→mmc_io_rw_direct→ CMD52sdio_memcpy_toio→mmc_io_rw_extended→ CMD53
题 3
所有 SDIO 命令(含 enable、寄存器、FIFO)都无法真正下到硬件;
TX/RX 会超时失败,最终可能 surprise_removed。
题 4(参考)
text
DAT1
→ DW MMC INT_SDIO
→ mmc_signal_sdio_irq
→ sdio_irq_thread / process_sdio_pending_irqs
→ sd_sync_int_hdl
题 5
更应先查第 4 课 tx_allowed / 聚合 / page 分配 。
CMD53 都成功说明 Host 传输大体健康,瓶颈常在"等 FIFO 空间/软件调度"。
题 6
同一槽位可能是 SDIO、SD 或 MMC;
WiFi 模组是 SDIO,优先 SDIO 可更快正确识别,避免按存储卡流程误判。
(代码注释也写:Order's important: SDIO, then SD, then MMC。)
题 7
不是。
enable_sdio_irq:Host/MMC 层,管 DAT1 中断路由rtw_hal_disable_interrupt:芯片 HIMR 层,管芯片上报哪些事件
题 8(参考)
- 读 HISR/长度(寄存器,CMD52 或短读)→ 一次 request
read_port读 RX FIFO(CMD53)→ 又一次 request
若循环收多包,CMD53 可能多次。
16. 学完五课后你应具备的能力
- 能讲述 WiFi SDIO 从上电到 probe 的顺序
- 能区分 CMD52/CMD53/F0/FIFO
- 能讲清 RX 三层中断与 TX
tx_allowed - 能把任意
sdio_*调用映射到mmc_io_rw_*与dw_mci_request - 能按层排障:识卡 / 中断 / 总线超时 / 驱动门禁
附录 A:关键调用链速查
识卡
text
dw_mci_probe → mmc_add_host → mmc_start_host
→ mmc_rescan → mmc_attach_sdio → mmc_sdio_init_card
→ sdio_add_func → rtw_drv_init
CMD52
text
sdio_readb
→ mmc_io_rw_direct
→ mmc_wait_for_cmd
→ dw_mci_request → mmc_request_done
CMD53
text
sdio_memcpy_toio / fromio / writesb / readsb
→ mmc_io_rw_extended
→ mmc_wait_for_req
→ dw_mci_request → mmc_request_done
SDIO IRQ
text
INT_SDIO → mmc_signal_sdio_irq
→ sdio_irq_thread → sd_sync_int_hdl → sd_int_hdl