第 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. 本课总画面(先建立)
收包不是“中断一来,数据就自动到内存”,而是:
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,当前哪些中断源发生了
驱动常见逻辑:
读 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
芯片拉 DAT1 → hc16 DW MMC 感知 SDIO 中断 → 调用 mmc_signal_sdio_irq() → SDIO 子系统唤醒/调度中断处理 → 调用本驱动注册的 handlerHost 侧通常还会:
先 enable_sdio_irq(0) // 暂时关掉,防中断风暴 处理完再 enable_sdio_irq(1)本课记住:总线中断处理期间,Host 常会先关再开 DAT1 中断。
2.2 驱动入口:sd_sync_int_hdl
第 1 课注册的回调,逻辑是:
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
这是本课核心函数之一,按步骤理解:
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.1get_interrupt读什么
8733 做法(理解语义即可):
从 REG_SDIO_HISR 起连续读 8 字节 前 4 字节 → HISR 后 4 字节 → 本次 RX FIFO 数据长度(SdioRxFIFOSize)所以一次读取同时得到:
- 发生了什么中断
- 若是收包,要读多长
这非常重要:RX 不是盲目读,而是先问长度再按长度搬。
3.2clear_interrupt做什么
向 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 主路径(必背)
若 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 步骤
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.2read_port如何变成第 2 课的 CMD53
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.1rtl8733bs_rxhandler
把 recvbuf 挂到 recv_buf_pending_queue 调用 start_rx_handle(adapter) // 触发后续处理(tasklet/线程等)含义:中断上下文尽量短,重活后移。
6.2rtl8733bs_recv_hdl(后续处理)
循环从 pending_queue 取 recvbuf: 看 RX desc 是否 C2H(固件命令) 是 → c2h_handler 否 → recvbuf_handler(解析成 802.11/以太网帧,上送协议栈) 处理完:释放 skb,recvbuf 回 free 队列本课对后半程只需记住分层:
中断里:搬 FIFO(I/O 密集) 后半程:解析描述符 / 组 skb / 送网络栈(协议密集)7. 完整 RX 流程图(请背这张)
芯片:包进入 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 课如何串起来
第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
配合 SDIOblock_size=512,让 CMD53 更适合块模式、并兼容某些 Host;尽量一次命令搬完。
题 6(参考)
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)与吞吐关系
附录:关键调用链速查
中断入口
sdio_claim_irq(sd_sync_int_hdl) → DAT1 → sd_sync_int_hdl → sd_int_hdlRX 取数
get_interrupt(hisr, rx_len) → sd_int_dpc (RX_REQUEST) → sd_recv_rxfifo → rtl8733bs_read_port → rtw_sdio_read_cmd53 → rtl8733bs_rxhandler → clear_interrupt后半程
start_rx_handle → rtl8733bs_recv_hdl → c2h_handler / recvbuf_handler