☰
【发送路径(从 xmit 到 TX FIFO / CMD53 写)】
2026/10/11 10:24:39 网站建设 项目流程

第 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/OCMD53 读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_enqueue

3.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的职责(记住结论):

  1. 从各优先级软件队列取xmit_frame
  2. 填 TX descriptor
  3. 尽量聚多帧到同一个xmit_buf(受聚合个数、page、缓冲大小限制)
  4. 把填好的xmit_buf挂到pending xmitbuf 队列
  5. 唤醒/触发真正写端口的路径(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 不够。

② 轮询策略

空间不够时不会立刻永久失败,而是:

  1. 再问几次tx_allowed(内部也可能刷新 FREE_TXPG)
  2. 仍不够:buf 放回队头,msleep,返回
  3. 线程稍后被再次唤醒继续

吞吐优化补丁常改这里:减少无效刷新、改 page 判断(>改>=)、短自旋再 sleep 等。

③write_port:真正 CMD53 写

见下一节。


6.write_port:落到第 2 课的 CMD53

6.1 调用链

rtw_write_port(adapter, 0, len, xmitbuf) → sdio_write_port / 最终 rtl8733bs_write_port

6.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_buf

8. 和 RX 对照(加深记忆)

RX:芯片有货 → 拉门铃 → 主机来取(read_port) TX:主机有货 → 先问仓库空位(tx_allowed)→ 再送货(write_port)
步骤RXTX
触发DAT1 + HISR.RX_REQUEST软件入队 + 线程
问长度/空间get_interrupt得 rx_lentx_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,核心就落在:

  1. PAGE gate 等待策略
  2. 聚合 page 上限
  3. TX page 向 LQ/BE 倾斜
  4. tx_allowed判断与刷新次数
  5. 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 一次成功写出,最少经历:

  1. 帧入队
  2. (可能)多帧聚合进xmit_buf
  3. tx_allowed通过
  4. write_cmd53完成

11. 本课关键名词

名词一句话
xmit_frame一帧待发
xmit_buf待写入 SDIO 的缓冲(可聚合)
pending xmitbuf已聚好、等待 write_port 的队列
tx_allowedHalmac 判断 TX page/门禁是否允许写
TX page芯片 TX FIFO 的分配单位
pubq公共 page 池
qsel帧映射到哪条硬件队列
write_portCMD53 写入 TX FIFO 的入口
get_tx_addr计算本次 SDIO 写地址
SdioXmitSema / xmit_sema唤醒发送路径的信号量
agg多帧聚合,减少 SDIO 事务次数

12. 本课必须记住的 8 句话

  1. TX 多是线程主动推,不是单纯靠 DAT1。
  2. hal_xmit主要入队,不直接写 SDIO。
  3. 聚合把多帧变成一次xmit_buf写出。
  4. dequeue_writeport是写总线前最后关卡。
  5. tx_allowed查的是芯片 FIFO page,不是“内核有没有内存”。
  6. 空间不够会放回队列等待,而不是立刻丢包(通常)。
  7. write_port→get_tx_addr→write_cmd53→ CMD53。
  8. 吞吐优化常盯:聚合、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
  • hc16dw_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

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

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

立即咨询