【中断与收包】

第 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

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

  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. 本课关键名词

名词 一句话
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 句话

  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

配合 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
相关推荐
youshi66682 小时前
平衡车——PID算法优化
stm32·单片机·嵌入式硬件·算法·pid·平衡车
comyccccc3 小时前
高效降压电路布局与铺铜优化技巧学习笔记
单片机·嵌入式硬件
白搞电子3 小时前
ESP32-S31 WASM 应用开发实录:重力弹球从电脑预览到 ESP-Mosaico 实机测试
单片机·嵌入式硬件·物联网·电脑·wasm
尼喃3 小时前
集成输入反接保护的USB限流保护芯片IC反向电流阻断验证
嵌入式硬件
ye150127774553 小时前
220V降压24V700mA交转直芯片WT5110
stm32·单片机·嵌入式硬件·其他·硬件工程
BSD_HY4 小时前
薄膜开关矩阵扫描在低功耗 MCU 上的 GPIO 扩展
单片机·嵌入式硬件·矩阵
时代的凡人5 小时前
生活中的恒流源和恒压源
嵌入式硬件
恒锐丰科技林技术员6 小时前
EG2181 半桥驱动芯片原理与应用解析
经验分享·嵌入式硬件·硬件工程
Jason_zhao_MR6 小时前
电力数据采集核心板选型指南
linux·嵌入式硬件·fpga开发·嵌入式