第 3 课:中断与收包(从 DAT1 到 RX FIFO)
接第 1、2 课。
第 1 课:挂上
sdio_claim_irq(sd_sync_int_hdl)第 2 课:会用 CMD52/CMD53 读写
本课目标:搞清「芯片有包 → 拉中断 → 驱动读 HISR → CMD53 拉 FIFO → 交给协议栈」整条 RX 链。
正文自包含,不必翻代码。
对应源码(备查):
os_dep/linux/sdio_intf.c(sd_sync_int_hdl、sdio_alloc_irq)hal/rtl8733b/sdio/rtl8733bs_io.c(sd_int_hdl、sd_int_dpc、sd_recv_rxfifo、read_port)hal/rtl8733b/sdio/rtl8733bs_ops.c(get_interrupt、clear_interrupt、HIMR 初始化)hal/rtl8733b/sdio/rtl8733bs_recv.c(rxhandler、recv_hdl)
0. 本课总画面(先建立)
收包不是"中断一来,数据就自动到内存",而是:
text
1. 芯片把数据放进 RX FIFO,并置位内部中断状态(HISR)
2. 芯片拉 SDIO DAT1 线(总线级中断通知)
3. Host/MMC 发现 DAT1,回调 WiFi 驱动
4. 驱动用 CMD52/寄存器读 读出 HISR + 本次 RX 长度
5. 若是 RX 请求:再用 CMD53 从 RX FIFO 把数据读出来
6. 清芯片中断位,把数据丢进接收队列,后续解析上送
一句话:
中断只是门铃;真正取包裹要用 CMD53 去开 RX FIFO。
1. 三层中断,必须分清
这是本课最容易混的地方。
| 层级 | 名称 | 谁产生 | 谁处理 | 作用 |
|---|---|---|---|---|
| 第 1 层 | DAT1 总线中断 | 芯片拉 DAT1 | MMC Host + SDIO 子系统 | "有事找你" |
| 第 2 层 | 驱动回调 | sdio_claim_irq 注册的函数 |
sd_sync_int_hdl → sd_int_hdl |
进入 WiFi 驱动 |
| 第 3 层 | 芯片 HISR/HIMR | 芯片内部中断状态/掩码 | get_interrupt / clear_interrupt |
知道是 RX 还是 TX 错误等 |
1.1 HIMR 与 HISR
- HIMR:Host Interrupt Mask,允许哪些中断源上报
- HISR:Host Interrupt Status,当前哪些中断源发生了
驱动常见逻辑:
text
读 HISR
用 HIMR 做掩码:hisr &= himr
按 bit 分发处理
写 HISR(write-1-clear)清掉已处理位
第 1 课提过两层使能:
sdio_claim_irq:打开总线层 DAT1 通知rtw_hal_enable_interrupt:把芯片 HIMR 打开(允许 RX_REQUEST 等)
两层都开,门铃才会响,且响了之后才知道是什么事。
2. 从 DAT1 到驱动回调
2.1 硬件到 MMC
text
芯片拉 DAT1
→ hc16 DW MMC 感知 SDIO 中断
→ 调用 mmc_signal_sdio_irq()
→ SDIO 子系统唤醒/调度中断处理
→ 调用本驱动注册的 handler
Host 侧通常还会:
text
先 enable_sdio_irq(0) // 暂时关掉,防中断风暴
处理完再 enable_sdio_irq(1)
本课记住:总线中断处理期间,Host 常会先关再开 DAT1 中断。
2.2 驱动入口:sd_sync_int_hdl
第 1 课注册的回调,逻辑是:
text
sd_sync_int_hdl(func)
1. dvobj = sdio_get_drvdata(func) // 找回设备对象
2. rtw_sdio_set_irq_thd(current) // 标记:我是 IRQ 线程
3. sd_int_hdl(primary_adapter) // 进入芯片中断处理
4. rtw_sdio_set_irq_thd(NULL) // 清除标记
为什么要标记 IRQ 线程?
因为后面读 HISR、读 FIFO 还会走第 2 课的 raw_read/write;
若再次 claim_host,可能死锁。标记后 claim_host_needed 返回否。
3. 芯片中断处理主函数:sd_int_hdl
这是本课核心函数之一,按步骤理解:
text
sd_int_hdl(adapter)
1. 若驱动已停止 / surprise_removed → 直接返回
2. 检查芯片是否处于可响应中断的电源状态
(异常时可能打 unexpected interrupt)
3. rtl8733bs_get_interrupt(&hisr, &rx_len)
4. 若 hisr 与 himr 有交集:
hisr &= himr
sd_int_dpc(adapter) // 分发处理
rtl8733bs_clear_interrupt(hisr) // 清中断
否则:
打印 HISR/HIMR 不匹配调试信息
3.1 get_interrupt 读什么
8733 做法(理解语义即可):
text
从 REG_SDIO_HISR 起连续读 8 字节
前 4 字节 → HISR
后 4 字节 → 本次 RX FIFO 数据长度(SdioRxFIFOSize)
所以一次读取同时得到:
- 发生了什么中断
- 若是收包,要读多长
这非常重要:RX 不是盲目读,而是先问长度再按长度搬。
3.2 clear_interrupt 做什么
text
向 HISR 写回对应 bit(write-1-clear)
表示:这些中断我处理了,可以再产生新的。
注意:有的 RX 请求位在 DPC 里用异或清软件副本,注释写"无需写 1 clear";不同 bit 清法可能不同,本课先抓主流程。
4. 中断分发:sd_int_dpc
sd_int_dpc 按 HISR 各个 bit 做事。本课重点是 RX,其他知道用途即可。
4.1 常见中断源(概念)
| 中断类型 | 大致含义 | 驱动动作 |
|---|---|---|
| RX_REQUEST | RX FIFO 有数据可读 | 读 FIFO,进收包路径 |
| TX_AVAL(若开启) | TX FIFO 有空间 | 唤醒发送线程 |
| CPWM | 电源/时钟状态变化 | 电源状态处理 |
| TXERR / RXERR | 传输出错 | 读状态、打日志、必要时恢复 |
| RXFOVW | RX 溢出 | 打日志,说明来不及收 |
4.2 RX_REQUEST 主路径(必背)
text
若 hisr 含 BIT_RX_REQUEST:
1. 从软件 hisr 去掉该 bit(本轮按 RX 处理)
2. rx_len = SdioRxFIFOSize
3. 循环:
若 rx_len == 0 → 结束
若 rx_len 非法过大 → 丢弃/退出
precvbuf = sd_recv_rxfifo(adapter, rx_len)
成功 → rtl8733bs_rxhandler(adapter, precvbuf)
失败 → 计数,必要时 drop 或短暂等待后重试
再次 get_interrupt 只取新的 rx_len
若还有数据继续;否则结束
为什么是循环?
一次中断可能对应 FIFO 里不止一包,或者读完后芯片又堆积了新数据;
驱动会尽量把当前可读数据收干净。
5. 真正搬数据:sd_recv_rxfifo
这是"开箱取货"函数。
5.1 步骤
text
sd_recv_rxfifo(adapter, size)
1. readsz = 按 4 字节数字对齐 size(某些 Host 兼容)
2. bufsz = 再按 SDIO block(512) 对齐(cmd53_align_size)
3. 从 free_recv_buf_queue 取一个 recv_buf
4. 分配 skb,大小至少 bufsz
5. rtl8733bs_read_port(d, bufsz, rbuf) ← CMD53 读 FIFO
6. 填写 recvbuf 的 len/pdata 等指针
7. 返回 recvbuf
任一失败(没 buf、没 skb、read_port 失败)都会把资源还回空闲队列。
5.2 read_port 如何变成第 2 课的 CMD53
text
rtl8733bs_read_port
→ rxaddr = rtw_halmac_sdio_get_rx_addr(...) // 如 0x0E000|seq
→ 长度再对齐,保证尽量一次命令读完
→ rtw_sdio_read_cmd53(d, rxaddr, buf, buflen)
→ sdio_io → raw_read → 内核 SDIO API(CMD53)
结合第 2 课:
- 这是 CMD53 大宗读
- 8733 路径里
fixed常为 0,实际多走sdio_memcpy_fromio - 但地址是 RX 专用编码(
0x0E000一类),芯片当 FIFO 口处理
5.3 为什么要对齐
- 4 字节对齐:规避部分 Host 非对齐访问问题
- 512 对齐:配合第 1 课
block_size=512,利于 CMD53 block 模式一次搬完
可以多读一点对齐填充,但 recvbuf->len 仍按真实 size 解释有效数据。
6. 收包后半程:rxhandler → recv_hdl
读出 FIFO 后,还没到"应用看到网络包"。
6.1 rtl8733bs_rxhandler
text
把 recvbuf 挂到 recv_buf_pending_queue
调用 start_rx_handle(adapter) // 触发后续处理(tasklet/线程等)
含义:中断上下文尽量短,重活后移。
6.2 rtl8733bs_recv_hdl(后续处理)
text
循环从 pending_queue 取 recvbuf:
看 RX desc 是否 C2H(固件命令)
是 → c2h_handler
否 → recvbuf_handler(解析成 802.11/以太网帧,上送协议栈)
处理完:释放 skb,recvbuf 回 free 队列
本课对后半程只需记住分层:
text
中断里:搬 FIFO(I/O 密集)
后半程:解析描述符 / 组 skb / 送网络栈(协议密集)
7. 完整 RX 流程图(请背这张)
text
芯片:包进入 RX FIFO,置 HISR.RX_REQUEST,拉 DAT1
│
▼
Host/MMC: DAT1 IRQ
│
▼
sd_sync_int_hdl(func)
set_irq_thd(current)
│
▼
sd_int_hdl
get_interrupt(hisr, rx_len)
│
hisr & himr 有 RX?
│是
▼
sd_int_dpc
│
▼
sd_recv_rxfifo(rx_len)
alloc recvbuf/skb
read_port → CMD53 读 RX FIFO
│
▼
rtl8733bs_rxhandler
enqueue pending
start_rx_handle
│
▼
clear_interrupt(hisr)
set_irq_thd(NULL)
│
▼
rtl8733bs_recv_hdl(后半程)
解析 RX desc / 上送协议栈
8. 和 TX 中断的一点对比(防混淆)
| RX | TX(若开 AVAL 中断) | |
|---|---|---|
| 门铃含义 | FIFO 有数据可读 | FIFO 有空间可写 |
| 关键动作 | CMD53 读 RX | 唤醒发送线程去写 |
| 主 I/O | read_port |
write_port(第 4 课) |
本课先吃透 RX;TX 完整路径下一课讲。
9. 和第 1、2 课如何串起来
text
第1课:claim_irq 挂好门铃按钮
第2课:学会用 CMD52/53 开门取物
第3课:门铃响了以后:
CMD52/寄存器读 → 看 HISR/长度
CMD53 → 取 RX FIFO
一次完整收包最少包含两类访问:
- 读中断状态/长度(寄存器访问,偏 CMD52 或短读)
- 读 FIFO 数据(CMD53 大块读)
10. 常见异常现象(本课级排障思路)
| 现象 | 可能卡在哪 |
|---|---|
| 完全无 RX 中断 | HIMR 没开 / claim_irq 失败 / DAT1 线或 Host SDIO IRQ 没开 |
| 有中断但 HISR&HIMR 不匹配 | 掩码配置不对,或误触发 |
| 有 RX_REQUEST 但 read_port 失败 | CMD53/时钟/对齐/掉电/surprise_removed |
| alloc recvbuf/skb 失败 | 内存或缓冲池耗尽,来不及处理 |
| Rx Overflow | 收太慢,FIFO 溢出 |
| unexpected interrupt | 电源状态不对时仍来了中断 |
排障口诀:
先看门铃有没有响(DAT1/回调),再看门牌(HISR),最后看取货(CMD53)成不成。
11. 本课关键名词
| 名词 | 一句话 |
|---|---|
| DAT1 | SDIO 中断线,芯片通知 Host"有事" |
| HIMR | 芯片中断掩码 |
| HISR | 芯片中断状态 |
| RX_REQUEST | "RX FIFO 有数据" |
| SdioRxFIFOSize | 本次应读长度 |
| recv_buf | 驱动接收缓冲描述符 |
| read_port | 从 RX FIFO 做 CMD53 读取 |
| write-1-clear | 写 1 清除对应中断位 |
| pending_queue | 已读出、待解析的包队列 |
| irq_thd | 标记当前在 SDIO 中断处理线程 |
12. 本课必须记住的 8 句话
- 中断是门铃,不是数据包本身。
- DAT1、驱动回调、HISR 是三层,别混。
- 先
get_interrupt拿 hisr + rx_len,再决定读不读 FIFO。 - RX 主路径在
sd_int_dpc的RX_REQUEST分支。 sd_recv_rxfifo→read_port→ CMD53。- 中断里标记 irq_thd,避免重复 claim_host。
rxhandler只入队并调度,重解析在后半程。- 清 HISR 后,才能健康地接下一次中断。
13. 自测题
题 1
为什么说"有 SDIO 中断"不等于"已经收到完整网络包"?
题 2
sd_sync_int_hdl 里为什么要 set_irq_thd(current)?
题 3
get_interrupt 一次读出哪两类信息?
题 4
RX 路径里,CMD53 发生在哪个函数附近?
题 5
sd_recv_rxfifo 为什么常常把长度对齐到 512?
题 6
按顺序列出从 DAT1 到 rxhandler 的关键函数链。
题 7
HIMR 没开、但 claim_irq 成功,会出现什么现象?
题 8
rxhandler 和 recv_hdl 分工有何不同?
14. 自测题标准答案
题 1
中断只表示芯片通知"有事件/有数据可读"。
真正的包还在 RX FIFO 里,必须再发 CMD53 读出来,并经过解析,才成为网络栈里的包。
题 2
避免中断处理路径里再次 sdio_claim_host 造成死锁;
让 raw_read/write 发现当前已是 IRQ 线程后跳过 claim。
题 3
- HISR(中断状态)
- RX 长度(SdioRxFIFOSize)
题 4
sd_recv_rxfifo → rtl8733bs_read_port → rtw_sdio_read_cmd53。
题 5
配合 SDIO block_size=512,让 CMD53 更适合块模式、并兼容某些 Host;尽量一次命令搬完。
题 6(参考)
text
DAT1
→ mmc_signal_sdio_irq
→ sd_sync_int_hdl
→ sd_int_hdl
→ get_interrupt
→ sd_int_dpc
→ sd_recv_rxfifo
→ read_port(CMD53)
→ rtl8733bs_rxhandler
题 7
总线层也许能收到少数异常/空跑,但芯片不允许的中断源不会按预期上报;
典型结果是:几乎收不到正常 RX_REQUEST,表现为不收包或收包极少。
(实践中两层都要正确配置。)
题 8
rxhandler:中断后半段,入队 + 调度,尽量短recv_hdl:真正解析 RX desc、区分 C2H/数据包、上送协议栈、回收缓冲
15. 学完第 3 课的位置
已经知道:
- 收包中断三层模型
sd_int_hdl/sd_int_dpc/sd_recv_rxfifo职责- RX 如何落到第 2 课的 CMD53
- 为何中断里要做 irq_thd 标记
还不知道(第 4 课):
- TX 如何从
xmit走到write_port - 如何用
tx_allowed等 FIFO 空间 - TX 聚合、队列(High/Normal/Low)与吞吐关系
附录:关键调用链速查
中断入口
text
sdio_claim_irq(sd_sync_int_hdl)
→ DAT1
→ sd_sync_int_hdl
→ sd_int_hdl
RX 取数
text
get_interrupt(hisr, rx_len)
→ sd_int_dpc (RX_REQUEST)
→ sd_recv_rxfifo
→ rtl8733bs_read_port
→ rtw_sdio_read_cmd53
→ rtl8733bs_rxhandler
→ clear_interrupt
后半程
text
start_rx_handle
→ rtl8733bs_recv_hdl
→ c2h_handler / recvbuf_handler