第 4 课:发送路径(从 xmit 到 TX FIFO / CMD53 写)
接第 1~3 课。
第 3 课讲清了 RX:门铃 → 读 HISR → CMD53 读 FIFO。
本课目标:搞清「上层要发一帧 → 入队/聚合 → 等 TX FIFO 有空间 → CMD53 写入」整条 TX 链。
正文自包含,不必翻代码。
对应源码(备查):
hal/rtl8733b/sdio/rtl8733bs_xmit.c(hal_xmit、xmit_thread、xmit_buf_handler、dequeue_writeport)hal/rtl8733b/sdio/rtl8733bs_io.c(write_port)hal/hal_halmac.c(sdio_tx_allowed、sdio_get_tx_addr)hal/rtl8733b/sdio/rtl8733bs_ops.c(set_hal_ops挂接 xmit)
0. 本课总画面
TX 和 RX 不对称:
| RX(第 3 课) | TX(本课) | |
|---|---|---|
| 谁先有数据 | 芯片 FIFO 先有 | 主机内存先有 |
| 触发方式 | 芯片拉 DAT1 中断 | 软件线程主动送 |
| 关键等待 | 等中断 | 等TX FIFO 有空间 |
| 关键 I/O | CMD53 读 | CMD53 写 |
一句话:
TX 是主机主动推货进芯片仓库;仓库满了就要等(
tx_allowed),有空位再 CMD53 写入。
1. 先认识几个对象
| 对象 | 含义 |
|---|---|
xmit_frame | 一帧待发(含属性、payload、TX desc) |
xmit_buf | 准备写入 SDIO 的缓冲区(可含多帧聚合) |
xmit_priv | 发送子系统私有数据(队列、信号量、统计) |
| TX FIFO / TXFF | 芯片内发送缓冲,按 page 管理 |
| HQ / NQ / LQ / EXQ | 不同优先级的 TX 队列(High/Normal/Low/Extra) |
qsel | 帧要进哪条硬件队列 |
关系可记:
网络栈 skb → xmit_frame(逻辑帧) → 聚合成 xmit_buf(一块连续内存 + TX desc) → write_port → CMD53 写入某个 TX 队列地址 → 芯片 TX FIFO → 射频发出2. TX 路径分四段
段 A:入队 hal_xmit / hal_xmit_enqueue → 帧进入软件队列,唤醒发送线程 段 B:聚合 xmit_thread → xmit_handler → xmit_xmitframes → 多帧拼进 xmit_buf,更新 TX desc 段 C:等空间 + 写出 xmit_buf_handler / dequeue_writeport → tx_allowed 查询 FIFO → write_port → CMD53 段 D:收尾 释放 xmit_buf,统计,必要时再调度本课重点在段 A / C,段 B 知道“会聚合”即可。
3. 段 A:帧如何进入发送路径
3.1rtl8733bs_hal_xmit
上层(rtw_xmit一类)把帧交给 HAL:
rtl8733bs_hal_xmit(adapter, xmitframe) 1. 设置 qsel(通常等于 priority) 2. 必要时发 ADDBA 等(HT 聚合准备,本课可略) 3. 加锁后调用 rtl8733bs_hal_xmit_enqueue3.2rtl8733bs_hal_xmit_enqueue
rtw_xmitframe_enqueue(...) // 帧进软件队列 失败 → free frame,tx_drop++ 成功 → 唤醒发送侧: 若开 TX_TASKLET → tasklet_hi_schedule 否则 → up_sema(SdioXmitSema)要点:hal_xmit一般不直接碰 SDIO,只是入队 + 叫醒工人。
真正写总线在发送线程里。
3.3 管理帧特例(了解即可)
rtl8733bs_mgnt_xmit:
- Beacon 往往直接
write_port(不走普通数据聚合队列) - 其他管理帧可
enqueue_pending_xmitbuf
4. 段 B:发送线程在干什么(概念级)
4.1 线程入口
初始化时会启动类似:
kthread_run(rtl8733bs_xmit_thread, ...)线程循环:
rtl8733bs_xmit_thread loop: xmit_handler(adapter) // 内部会做聚合等聚合函数xmit_xmitframes的职责(记住结论):
- 从各优先级软件队列取
xmit_frame - 填 TX descriptor
- 尽量聚多帧到同一个
xmit_buf(受聚合个数、page、缓冲大小限制) - 把填好的
xmit_buf挂到pending xmitbuf 队列 - 唤醒/触发真正写端口的路径(
xmit_sema等)
4.2 为何要聚合
SDIO 每次 CMD53 有固定开销。
多帧拼成一次写,可减少命令次数、提高吞吐。
这也是吞吐优化常改的地方(聚合数、MAX_XMITBUF_SZ、page 分配等)。
5. 段 C(核心):xmit_buf_handler→dequeue_writeport
5.1rtl8733bs_xmit_buf_handler
down(xmit_sema) // 等待“有活干” 若驱动停/拔出 → 失败返回 若没有 pending xmitbuf → 成功返回 (可选)register_tx_alive // 低功耗相关 循环: rtw_mi_dequeue_writeport() // 真正尝试写出 直到队列空 (可选)unregister_tx_alive可把它理解成:写端口工人线程的一轮工作。
注:工程里可能同时存在
SdioXmitSema/xmit_sema等信号量,名称因配置略有差异;语义都是“唤醒发送路径继续干活”。
5.2dequeue_writeport(本课最重要)
逐步理解:
dequeue_writeport(adapter) 1. (多功能共存时)若 SDIO 总线被 BT 占用 → 稍等返回 2. pxmitbuf = select_and_dequeue_pending_xmitbuf() 没有 → 返回“队列空” 3. while (rtw_halmac_sdio_tx_allowed(...) 失败): 若驱动不能跑了 → 丢掉/释放该 buf 轮询若干次仍不够空间: 把 buf 放回队列头 sleep 一下 返回(让出 CPU,稍后再试) 4. (可选)流量检查,决定是否离开 LPS 5. rtw_write_port(adapter, 0, len, xmitbuf) 6. free xmitbuf,up 空闲 buf 信号量等 7. 返回“还可能有更多”这里有三个关键点:
①tx_allowed:仓库还有空位吗?
rtw_halmac_sdio_tx_allowed(dvobj, buf, len)Halmac 根据:
- 该帧/聚合需要多少TX page
- 对应队列(及公共页 pubq)当前空闲 page
- 有时还查 OQT(输出队列相关限制)
判断现在能不能写进去。
- 返回成功(0):可以写
- 返回非 0:空间不够或其它门禁失败 → 驱动轮询/退避
这是 SDIO WiFi TX 最常见的停顿点:
不是 SDIO 线忙,而是芯片 TX FIFO page 不够。
② 轮询策略
空间不够时不会立刻永久失败,而是:
- 再问几次
tx_allowed(内部也可能刷新 FREE_TXPG) - 仍不够:buf 放回队头,
msleep,返回 - 线程稍后被再次唤醒继续
吞吐优化补丁常改这里:减少无效刷新、改 page 判断(>改>=)、短自旋再 sleep 等。
③write_port:真正 CMD53 写
见下一节。
6.write_port:落到第 2 课的 CMD53
6.1 调用链
rtw_write_port(adapter, 0, len, xmitbuf) → sdio_write_port / 最终 rtl8733bs_write_port6.2rtl8733bs_write_port做什么
1. txaddr = rtw_halmac_sdio_get_tx_addr(d, mem, cnt) - 根据 TX desc / 队列,算出 SDIO 命令地址 - 不同队列对应不同地址编码(HQ/NQ/LQ/EXQ 等) 2. cnt 做 4 字节对齐 3. txsize = rtw_sdio_cmd53_align_size(...) // 常对齐到 512 4. rtw_sdio_write_cmd53(d, txaddr, mem, txsize) → sdio_io → raw_write → 内核 SDIO 写 API(CMD53)结合第 2 课:
- 这是CMD53 大宗写
- 8733 封装里
fixed常为 0,实际多走sdio_memcpy_toio - 地址是 TX 队列专用编码,芯片识别为对应 TX FIFO 口
6.3 TX 地址直觉
不必背具体数值,记住分类:
| 概念队列 | 用途直觉 |
|---|---|
| HQ(High) | 高优先 / 管理类更常见 |
| NQ(Normal) | 普通 |
| LQ(Low) | 常承载 BE 数据,AP 吞吐敏感 |
| EXQ(Extra) | 额外队列 |
get_tx_addr负责:“这一包该写到哪个口”。
7. 完整 TX 流程图(请背这张)
上层 rtw_xmit / 协议栈 │ ▼ rtl8733bs_hal_xmit → hal_xmit_enqueue → 软件队列 + up_sema / tasklet │ ▼ rtl8733bs_xmit_thread → 聚合 xmit_xmitframes → 得到 pending xmit_buf │ ▼ rtl8733bs_xmit_buf_handler → down(xmit_sema) → dequeue_writeport 循环 │ ▼ dequeue_writeport → 取 pending xmit_buf → while !tx_allowed: 等待/放回/重试 → rtw_write_port │ ▼ rtl8733bs_write_port → get_tx_addr → align size → rtw_sdio_write_cmd53 │ ▼ raw_write → CMD53 写入芯片 TX FIFO │ ▼ 芯片硬件发送 / free xmit_buf8. 和 RX 对照(加深记忆)
RX:芯片有货 → 拉门铃 → 主机来取(read_port) TX:主机有货 → 先问仓库空位(tx_allowed)→ 再送货(write_port)| 步骤 | RX | TX |
|---|---|---|
| 触发 | DAT1 + HISR.RX_REQUEST | 软件入队 + 线程 |
| 问长度/空间 | get_interrupt得 rx_len | tx_allowed看 page |
| 搬数据 | read_port+ CMD53 读 | write_port+ CMD53 写 |
| 地址 | get_rx_addr(如 0x0E000) | get_tx_addr(队列口) |
若开启CONFIG_SDIO_TX_ENABLE_AVAL_INT:
芯片 TX 有空间时也可打中断(第 3 课TX_AVAL),用来up发送信号量,减少盲目轮询。
即使不开,驱动也能靠轮询tx_allowed工作。
9. 吞吐卡在哪(结合常见优化)
TX 慢时,按层排查:
| 层级 | 瓶颈可能 | 典型改动方向 |
|---|---|---|
| 聚合 | 一次写太少,CMD53 次数多 | 增大 agg、xmitbuf 尺寸 |
| page 门禁 | tx_allowed常失败 | page 分配(多给 LQ)、判断条件、减少无效刷新 |
| 等待策略 | sleep 太长/太勤 | 短自旋 + 适时 yield |
| SDIO 总线 | 时钟、宽度、对齐 | Host 侧能力、512 对齐 |
| 电源 | LPS 进出太频 | 热路径少进低功耗 |
你打开过的8733bs-tx-tp-opt.patch,核心就落在:
- PAGE gate 等待策略
- 聚合 page 上限
- TX page 向 LQ/BE 倾斜
tx_allowed判断与刷新次数- xmitbuf/agg 参数
学完本课再看该 patch,会容易很多。
10. 和第 1~3 课串联
第1课:Function 打开,block_size=512,中断挂好 第2课:CMD53 写 = write_cmd53 → raw_write 第3课:RX 用 CMD53 读 FIFO 第4课:TX 用 CMD53 写 FIFO,但多了 tx_allowed 这道闸TX 一次成功写出,最少经历:
- 帧入队
- (可能)多帧聚合进
xmit_buf tx_allowed通过write_cmd53完成
11. 本课关键名词
| 名词 | 一句话 |
|---|---|
| xmit_frame | 一帧待发 |
| xmit_buf | 待写入 SDIO 的缓冲(可聚合) |
| pending xmitbuf | 已聚好、等待 write_port 的队列 |
| tx_allowed | Halmac 判断 TX page/门禁是否允许写 |
| TX page | 芯片 TX FIFO 的分配单位 |
| pubq | 公共 page 池 |
| qsel | 帧映射到哪条硬件队列 |
| write_port | CMD53 写入 TX FIFO 的入口 |
| get_tx_addr | 计算本次 SDIO 写地址 |
| SdioXmitSema / xmit_sema | 唤醒发送路径的信号量 |
| agg | 多帧聚合,减少 SDIO 事务次数 |
12. 本课必须记住的 8 句话
- TX 多是线程主动推,不是单纯靠 DAT1。
hal_xmit主要入队,不直接写 SDIO。- 聚合把多帧变成一次
xmit_buf写出。 dequeue_writeport是写总线前最后关卡。tx_allowed查的是芯片 FIFO page,不是“内核有没有内存”。- 空间不够会放回队列等待,而不是立刻丢包(通常)。
write_port→get_tx_addr→write_cmd53→ CMD53。- 吞吐优化常盯:聚合、page 分配、
tx_allowed等待策略。
13. 自测题
题 1
为什么说hal_xmit成功返回,并不等于数据已经写进芯片 TX FIFO?
题 2
tx_allowed失败时,驱动通常怎么做?会立刻丢包吗?
题 3
write_port里为什么既要get_tx_addr,又要做 size align?
题 4
TX 路径里,CMD53 发生在哪个函数附近?
题 5
RX 用中断通知“有数据”,TX 用什么方式知道“有空间”?
题 6
按顺序列出从hal_xmit到 CMD53 写的关键步骤(写关键词即可)。
题 7
为什么增大聚合(agg)可能提升 SDIO TX 吞吐?
题 8
LQ page 变多,通常对哪类流量更有帮助?
14. 自测题标准答案
题 1
hal_xmit通常只是把帧送入软件队列并唤醒发送线程;
真正write_port/CMD53 在后续线程路径里,还可能卡在tx_allowed。
题 2
轮询/等待;多次不行就把xmit_buf放回队列头并 sleep,稍后再试。
一般不是立刻丢包(驱动停止/拔出等异常路径除外)。
题 3
get_tx_addr:决定写到哪个 TX 队列口- size align:满足芯片/SDIO block(常 512)要求,尽量一次 CMD53 写完
题 4
rtl8733bs_write_port→rtw_sdio_write_cmd53。
题 5
主要靠主动查询tx_allowed(读空闲 page 等);
若开启 TX_AVAL 中断,也可用中断唤醒发送路径。
题 6(参考)
hal_xmit → enqueue + 唤醒 → xmit_thread 聚合 → xmit_buf_handler → dequeue_writeport → tx_allowed → write_port → write_cmd53(CMD53)题 7
减少 SDIO 命令与中断/调度开销,提高总线有效载荷比例。
题 8
对 BE/普通数据流量(常走 Low 队列)更有帮助,AP 场景下 TX 吞吐往往受益。
15. 学完第 4 课的位置
已经知道:
- TX 入队 / 线程 / 聚合 /
tx_allowed/write_port全链 - TX 与 RX 在触发方式和等待点上的差异
- CMD53 写如何接上第 2 课
- 吞吐优化大致改哪些点
还不知道(第 5 课):
- MMC/SDIO 子系统如何把
sdio_writesb/memcpy_toio变成mmc_io_rw_extended - hc16
dw_mci_request如何真正打到 SDIO 线上 - Host 侧时钟、宽度、
enable_sdio_irq与 WiFi 驱动如何配合
附录:关键调用链速查
数据帧 TX
rtl8733bs_hal_xmit → rtl8733bs_hal_xmit_enqueue → rtw_xmitframe_enqueue → up_sema / tasklet → rtl8733bs_xmit_thread / xmit_handler(聚合) → rtl8733bs_xmit_buf_handler → dequeue_writeport → rtw_halmac_sdio_tx_allowed → rtw_write_port → rtl8733bs_write_port → rtw_halmac_sdio_get_tx_addr → rtw_sdio_write_cmd53与 RX 最短对照
RX: get_interrupt → read_port → read_cmd53 TX: tx_allowed → write_port → write_cmd53