☰
【中断与收包】
2026/10/11 10:31:55 网站建设 项目流程

第 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 总线中断芯片拉 DAT1MMC 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 子系统唤醒/调度中断处理 → 调用本驱动注册的 handler

Host 侧通常还会:

先 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_REQUESTRX FIFO 有数据可读读 FIFO,进收包路径
TX_AVAL(若开启)TX FIFO 有空间唤醒发送线程
CPWM电源/时钟状态变化电源状态处理
TXERR / RXERR传输出错读状态、打日志、必要时恢复
RXFOVWRX 溢出打日志,说明来不及收

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 中断的一点对比(防混淆)

RXTX(若开 AVAL 中断)
门铃含义FIFO 有数据可读FIFO 有空间可写
关键动作CMD53 读 RX唤醒发送线程去写
主 I/Oread_portwrite_port(第 4 课)

本课先吃透 RX;TX 完整路径下一课讲。


9. 和第 1、2 课如何串起来

第1课:claim_irq 挂好门铃按钮 第2课:学会用 CMD52/53 开门取物 第3课:门铃响了以后: CMD52/寄存器读 → 看 HISR/长度 CMD53 → 取 RX FIFO

一次完整收包最少包含两类访问:

  1. 读中断状态/长度(寄存器访问,偏 CMD52 或短读)
  2. 读 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. 本课关键名词

名词一句话
DAT1SDIO 中断线,芯片通知 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 句话

  1. 中断是门铃,不是数据包本身。
  2. DAT1、驱动回调、HISR 是三层,别混。
  3. 先get_interrupt拿 hisr + rx_len,再决定读不读 FIFO。
  4. RX 主路径在sd_int_dpc的RX_REQUEST分支。
  5. sd_recv_rxfifo→read_port→ CMD53。
  6. 中断里标记 irq_thd,避免重复 claim_host。
  7. rxhandler只入队并调度,重解析在后半程。
  8. 清 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

  1. HISR(中断状态)
  2. 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_hdl

RX 取数

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询