管理帧的接收与分发 — 控制面的帧处理
"发送是塔台下指令,接收是塔台听回报——来机怎么分类、怎么处理、怎么上报,决定了调度系统的响应速度。"
¶ 本章导读
上一章(管理帧的发送 — 控制面的帧构建与下发)我们追踪了管理帧的发送路径:从 supplicant 构建 Auth/Assoc/Deauth 帧,经 nl80211 穿过内核边界,进入 QCOM 的双路径(Auth 快速通道 / Action 标准通道)和 MTK 的单入口 + 大帧直通优化,最终到达固件。
本章聚焦硬币的另一面——管理帧的接收与分发。对端发来的管理帧到达固件后,如何穿越 WMI/MBOX 上报到驱动?驱动如何按 category 分类——哪些本地消化、哪些上报 supplicant?QCOM 的两层分发和 MTK 的单层分派各有什么优劣?最后聊一类特殊的帧:RTS/CTS/ACK——为什么你永远看不到它们的代码?
如果说发送是塔台发出调度指令,那么接收就是塔台接收入港航班信息。QCOM 的接收系统像大型国际机场的行李转盘分拣 + 登机口验票两层——第一层行李转盘(mgmt_txrx 回调表)分流,第二层登机口验票(PE/LIM category switch)细查。MTK 则像区域机场的只有登机口验票(单层分派)——固件 ROM 先筛一轮,驱动认识的就地处理,不认识的一律上报。
本章覆盖四个主题:
- Action 帧的接收入口:固件 WMI 事件 →
wma_mgmt_rx_process()→wlan_mgmt_txrx_rx_frame_handler()的完整链路 - Action 帧 20+ category 的分发逻辑:谁本地处理、谁上报 supplicant
- QCOM vs MTK 的接收架构对比:两层分发 vs 单层分派,各自的取舍
- 那些你永远看不到的帧:RTS/CTS/ACK/PS-Poll 为什么必须在硬件层处理
前情提要:如果你还没读上一篇(管理帧的发送 — 控制面的帧构建与下发),建议先了解管理帧三分类框架和 supplicant 侧的帧构建逻辑(尤其是 Auth/Assoc/Deauth 的 TX 路径),因为理解接收路径的前提是知道这些帧 "从哪里来"。如果你已经清楚 supplicant 如何通过
wpa_drv_send_mlme()下发管理帧,可以直接从下面的 Action 帧接收入口开始。
¶1 Action 帧的接收入口:固件如何把帧送到分发器?
在深入 category 分发逻辑之前,必须搞清楚一个问题:Action 帧从对端发过来之后,在到达 lim_process_action_frame() 之前,经历了哪些环节?答案是两层转发——固件上报后的第一个处理函数不是 lim_process_action_frame()。
QCOM 完整链路(4 步,全部验证通过):
1 | 对端 Action 帧 OTA 到达 |
关键点:lim_process_action_frame() 不是固件上报后的第一个函数——在它之前还有 wma_mgmt_rx_process()(WMI 事件提取)和 wlan_mgmt_txrx_rx_frame_handler()(mgmt_txrx 子系统 frm_type 计算 + 回调表分发)两层中转。如果 Action 帧的 frm_type 被计算为 MGMT_FRM_UNSPECIFIED(即 category 不被 mgmt_txrx 子系统识别),它会在 wlan_mgmt_txrx_rx_frame_handler 中被直接丢弃,根本不会到达 lim_process_action_frame()。
MTK 完整链路:
1 | 对端 Action 帧 OTA 到达 |
MTK 的链路比 QCOM 短一截——因为没有 mgmt_txrx 子系统这层抽象,固件 ROM 直接按 category 分派,驱动不认识的就走 aisFuncValidateRxActionFrame 兜底上报。详细对比见下文 §2.4(MTK 分发链路)、§3.5(架构分析)和 §5(全景对比)。
¶2 Action 帧分类与分发逻辑:进港航班怎么分?
Action 帧是最复杂的管理帧类别——802.11 定义了 20+ 种 category,每种 category 下又有不同的 action field。驱动和 supplicant 各管各的,分发逻辑决定了哪些帧在本地消化、哪些上报。
¶2.1 哪些 Action 帧由驱动本地处理?
以下 category 的 Action 帧不会上报 supplicant,由驱动本地处理:
| Category | 值 | 处理方式 |
|---|---|---|
| Spectrum Mgmt | 0 | 802.11h 测量 / TPC(QCOM 本地处理) |
| QoS / WMM | 1 / 17 | ADDTS/DELTS/QoS Map Configure(双平台本地) |
| Block Ack | 3 | ADDBA Request/Response(QCOM 软件处理;MTK 完全固件 offload) |
| HT | 7 | SM Power Save(QCOM 本地) |
| SA Query | 8 | 直接回复或状态机处理(PMF 驱动层职责) |
| VHT | 21 | Operating Mode Notification / GID Management(QCOM 本地) |
这些帧的共同点:它们改变的是驱动内部的链路状态(Block Ack 窗口、QoS 参数、信道测量),和 supplicant 的状态机无关。比如 Block Ack 协商——驱动知道要不要开聚合、开多大的窗口,不需要问 supplicant。
以上 category 数值依据 IEEE 802.11-2024 §9.4.1.11(Action field category 编码表),与 QCOM 代码中的
ACTION_CATEGORY_*常量一一对应。
¶2.2 哪些 Action 帧必须上报 supplicant?
| Category | 值 | 为什么上报 |
|---|---|---|
| P2P Public Action | 4 | P2P 发现 / 协商逻辑在 supplicant,驱动只负责转发 |
| TDLS | 12 | TDLS 直连管理在 supplicant |
| Vendor Specific | 127 / 126 | 供应商私有扩展,可能包含 supplicant 关心的内容 |
除此之外,任何驱动不认识的 category 都会作为 fallback 上报 supplicant——这是安全兜底策略。
这段分发逻辑在代码中是一个巨大的 switch (action_hdr->category) 语句——802.11 标准定义了 20+ 种 category,驱动必须对每一个做明确裁决。为便于理解,以下将其拆为 4 个阶段逐段解读。
阶段 1:入关安检——长度与 PMF 双校验
帧在进入 category 分发之前,必须先通过两道安检。长度校验防止后续 switch 语句越界读取 action_hdr 字段;PMF 保护检查确保要求受保护的 category(SA Query、FT Action、Protected Public Action)的帧确实携带了有效的完整性保护,未受保护的帧直接丢弃。
1 | // QCOM: core/mac/src/pe/lim/lim_process_action_frame.c — 阶段 1:入关安检 |
这两关是帧进入 category 分发前的「安检」:长度校验防止后续 switch 语句越界读取 action_hdr 的字段;PMF 保护检查确保要求受保护的 category(如 SA Query、FT Action、Protected Public Action)的帧确实携带了有效的完整性保护——未受保护的帧直接丢弃,不给攻击者绕过保护机制的机会。
阶段 2:本地降落——驱动内部消化
通过安检后,switch 进入 category 分发。以下 category 在 PE 层本地处理,不需要惊动 supplicant:
1 | // 第三关:category 分发 —— switch (action_hdr->category) |
这些 category 的共同特征:它们改变的都是驱动内部的链路状态,与 supplicant 的状态机无关。Spectrum Mgmt 管理信道测量和 TPC 报告,QoS/WMM 管理流优先级和 ADDTS 协商,Block Ack 管理聚合窗口,HT/VHT 管理 MIMO 工作模式,SA Query 是 PMF 安全机制的驱动层闭环——所有这些处理都在 PE 层完成,帧不会上报到用户空间。
阶段 3:转国际航班——上报 supplicant
以下 category 驱动不自行处理,统一通过 lim_send_sme_mgmt_frame_ind() 上报,让 supplicant 决策:
1 | /* ====== 上报 Supplicant:驱动透传,上层决策 ====== */ |
上报策略的核心是「驱动不认识的不瞎处理」——驱动只透传,决策留给上层。
WNM(BTM/Notification)交给 supplicant 决策,驱动不预判是否切换。唯一的例外是 BTM 被「扣下」:当 STA 角色收到 BTM Query/Request/Response、且此刻存在活跃的 P2P client 或 P2P GO 连接时,cfg_p2p_is_roam_config_disabled() 判定为真,switch 直接 break 丢弃,不进上报分支。因为 BTM Request 是漫游触发器——它要求 STA 评估切走;而并发模式下切走 infra 连接会连带打断 P2P 链路(信道跳变 + 关联重建),驱动宁可暂时扣下 BTM,等 P2P 会话结束再放行给 supplicant。
Vendor Specific 需 DA 匹配确认后才上报——只有发给本机的供应商帧才上报,防止广播帧泛滥。
Public Action(category 4)是三个上报 category 里最值得拆的,因为它一个 category 内部就有三路分流,并非「全量透传」。第一路 ECSA(Extended Channel Switch Announcement)走 lim_process_ext_channel_switch_action_frame() 本地处理——信道切换声明改变的是驱动自己的 MAC 信道状态,函数内部还要拿 wlan_reg_chan_opclass_to_freq() 换算目标信道、校验 regulatory/DFS 合法性与并发会话冲突,这一切都在驱动层闭环,上报 supplicant 反而多余。
第二路 GAS(Initial Request/Response、Comeback Request/Response)是 ANQP 查询的载体:GAS Initial Request 先被 SON 模块 wlan_son_anqp_frame() 拦一份做采集,再继续上报——HS2.0/Passpoint 的 ANQP 状态机与凭据匹配整段逻辑住在 supplicant,驱动只搭桥不决策。
第三路 DPP(Wi-Fi Easy Connect 扫码配网)借道 Vendor Specific Public Action:驱动拿 OUI 与 dpp_oui({0x50, 0x6F, 0x9A, 0x1A})比对,匹配就上报,不匹配直接丢。真正「全量透传」的只有 RVS/FST。
这些 category 统一走 lim_send_sme_mgmt_frame_ind() → SME → HDD → cfg80211_rx_mgmt() → supplicant 上报通道。
阶段 4:拒绝入境——静默丢弃
1 | default: |
default 分支是安全兜底:驱动不认识的新 category 不应该猜测如何处理,沉默丢弃比错误处理更安全。注意这里与 MTK 的策略相反——MTK 对未识别帧选择全量上报 supplicant(见 §2.4)。
四阶段小结:入关安检(长度 + PMF)→ 本地降落(QoS/Spectrum/WMM/HT/VHT/SA Query/Block Ack,PE 层闭环)→ 转国际航班(WNM/Public/Vendor/RVS/FST,
lim_send_sme_mgmt_frame_ind上报)→ 拒绝入境(default 静默丢弃)。部分 category 内部还有二级switch (action_hdr->actionID)按具体 action 字段细分,如 QoS 下的 ADDTS Req/Rsp/DEL TS/Map Configure。
¶2.3 哪些帧走了特殊分发路径?
FT Auth(802.11r 认证帧):QCOM 对 FT 有特殊处理——FT Auth 帧(认证帧,Auth alg 字段标记 FT)在 Auth 帧层面就被拦截了(lim_process_ft_auth_frame(),lim_process_auth_frame.c),不走 Action 帧的分发表。注意它与 category 6 的 FT Action 帧(重关联阶段的 FT Request/Response)是两回事。
原因在于 FT(Fast BSS Transition,802.11r)的认证和重关联高度耦合:FT 协议将 4-Way Handshake 的密钥派生前置到 Auth 阶段完成。Auth 帧中携带 R0KH-ID、R1KH-ID、PMK-R0 Name 等 FT 信息元素,PE 层必须在 Auth 帧交换时就完成 FT 序列号校验和 PMK-R0/R1 密钥层次派生。
如果 FT 的密钥派生被延后到常规 Action 帧分发路径,FT 重关联会因缺少 Auth 阶段建立的密钥上下文而失败。因此 QCOM 在 lim_process_auth_frame() 中直接检测 FT Auth 帧并调用 lim_process_ft_auth_frame(),将整个 FT 流程在 PE 层闭环。
FT Action(category 6):与 FT Auth 走的是完全不同的路径,而且双平台对它的态度截然相反。category 6 的 FT Request/Response/Confirm/Ack(over-the-DS FT 的资源请求与确认)在 QCOM 的 mgmt_txrx 层就被 mgmt_get_ft_action_subtype()(wlan_mgmt_txrx_main.c:630)映射为 4 个 frm_type 槽位(MGMT_ACTION_FT_REQUEST/RESPONSE/CONFIRM/ACK,wlan_mgmt_txrx_utils_api.h:766),但 lim_process_action_frame() 的 category switch 里没有 category 6 的 case——它到不了 PE/LIM 的第二层分发;这 4 个槽位也没有任何组件向 mgmt_rx_comp_cb 注册回调,因此 FT Action 帧在 QCOM 驱动 RX 路径上实际是「映射了但无人消费」:QCOM 的 802.11r FT 走 Auth 帧路径(lim_process_ft_auth_frame)加 Reassoc 帧,over-the-DS 的 FT Action 通道在驱动层是空缺的,FT Action 的最终消费者其实落到了 supplicant 侧的 ft_rx_action()(见 §3.4)。MTK 恰好相反,有一个专门的 roaming_fsm.c 消费 FT Action(CATEGORY_FT_ACTION=6,include/nic/mac.h:916):TX 侧 roamingFsmSendFtActionFrame()(mgmt/roaming_fsm.c:151)构造 FT Request,RX 侧 roamingFsmRunEventRxFtAction()(mgmt/roaming_fsm.c:421)校验 FT Response 并在 ROAMING_STATE_WAIT_FT_RESPONSE 状态推进漫游 FSM。一个「映射后丢弃」、一个「专门 FSM 消费」,FT Action 是双平台分歧最大的一个 category。
WNM(category 10):双平台策略相反。QCOM 上报 supplicant(让上层决定是否处理 BTM Request);MTK 本地处理(wnmWNMAction(),mgmt/wnm.c),在驱动层直接应答。
进港航班(收到的 Action 帧)分三类处理:本地降落的(驱动本地处理,不需要出关)、转国际航班的(上报 supplicant,需要上层决策)、VIP 专机(FT Auth 走特殊通道,不经过常规航班分拣)。
¶2.4 MTK 如何分发收到的 Action 帧?
与 QCOM 的两层分发(mgmt_txrx 回调表 + PE/LIM category switch)不同,MTK 的 Action 帧处理采用单层 category 分派——固件 ROM 完成第一轮过滤后,驱动侧直接按 category 调用对应 handler。
入口是 nicRxProcessActionFrame()(include/nic/nic_rx.h),注意这个函数只有声明、没有驱动侧实现体——它是固件 ROM 中的 blob 函数,在固件侧完成 WLAN Action Frame Header 的解析和第一轮 category 筛分。驱动侧能看到的是固件返回后的 category 分派结果,由以下几个 handler 接管:
P2P Action → p2pFuncValidateRxActionFrame()(mgmt/p2p_func.c,签名:void p2pFuncValidateRxActionFrame(struct ADAPTER *prAdapter, struct SW_RFB *prSwRfb, u_int8_t fgIsDevInterface, uint8_t ucRoleIdx)): 处理所有 P2P Public Action 帧(category 4),包括 GO Negotiation、Provision Discovery、Invitation 等。
其中 fgIsDevInterface 标记是否为 P2P Device 接口(用于判断是否走 Device FSM 而非 Role FSM),ucRoleIdx 是 P2P Role 索引(用于定位对应的 P2P Role FSM 状态机)。
函数内部再做 P2P 子类型的二级分发——根据 action_hdr->ucCategory → ucAction → OUI/Type 判断是 P2P 还是 DPP,然后调用对应的 P2P FSM 状态机处理函数。与 QCOM 的最大差异:MTK 的 P2P Action 帧不走 mgmt_txrx 回调表,直接从固件 ROM 分派到 p2pFuncValidateRxActionFrame,少了一层抽象。
WNM Action → wnmWNMAction()(mgmt/wnm.c,约 45 行):
1 | // MTK: mgmt/wnm.c — wnmWNMAction() category 10 分发 |
与 QCOM 的 WNM 策略对比:QCOM 的 WNM Action 帧全量上报 supplicant(lim_send_sme_mgmt_frame_ind),让上层决定是否处理 BTM Request;MTK 默认驱动层本地处理 BTM(wnmRecvBTMRequest,需开启 CFG_SUPPORT_802_11V_BTM_OFFLOAD 编译选项,否则同样走 aisFuncValidateRxActionFrame 上报),只有驱动不认识的 WNM sub-action 才上报。这个差异反映了两种设计哲学:QCOM 倾向 "上层决策",MTK 倾向 "驱动自治"。
兜底:aisFuncValidateRxActionFrame()(mgmt/ais_fsm.c)——所有不命中 P2P 和 WNM 的 Action 帧最终落在这里处理。这个函数本身只有约 17 行有效代码,它不做分发,而是做最后一公里上报:校验 BSS index 是否为 AIS 网络类型后,通过 kalIndicateRxMgmtFrame() 将帧体原样透传给 cfg80211。注意该函数只接收 2 个参数——prGlueInfo 从 prAdapter->prGlueInfo 获取,ucBssIndex 从 prSwRfb->prStaRec->ucBssIndex 提取。
1 | // MTK: mgmt/ais_fsm.c:8394 — 分发链末端:未识别帧上报 wpa_supplicant |
kalIndicateRxMgmtFrame()(os/linux/gl_kal.c:7736)签名:void kalIndicateRxMgmtFrame(struct ADAPTER *prAdapter, struct GLUE_INFO *prGlueInfo, struct SW_RFB *prSwRfb, uint8_t ucBssIndex)。内部通过 wlanGetNetDev(prGlueInfo, ucBssIndex) 获取 net_device,然后调用 cfg80211_rx_mgmt() 完成上报。
QCOM vs MTK Action 帧处理对比:
| 维度 | QCOM | MTK |
|---|---|---|
| 分发层数 | 两层(mgmt_txrx frm_type + PE/LIM category) | 单层(固件 ROM → category handler) |
| 分发入口 | wlan_mgmt_txrx_rx_frame_handler → 回调表 → lim_process_action_frame | nicRxProcessActionFrame(ROM blob)→ category 分派 |
| WNM 策略 | 全量上报 supplicant | 驱动本地处理 BTM,未识别 sub-action 才上报 |
| P2P 路径 | mgmt_txrx 回调表 → P2P 专属 handler | 固件 ROM → p2pFuncValidateRxActionFrame 直连 |
| 未识别帧 | PE 层 default 静默丢弃 | AIS FSM aisFuncValidateRxActionFrame 兜底上报 |
| 代码规模 | lim_process_action_frame.c ~500 行 | 分散在 3 个文件,各 < 50 行 |
设计哲学差异:QCOM 的
lim_process_action_frame是一个 "全能分发器"——500 行 switch 覆盖 20+ category,本地处理和上报在同一个函数内裁决。MTK 则把分发逻辑 "打散" 到不同文件的 handler 函数中——P2P 一个文件、WNM 一个文件、兜底上报一个文件。这种设计的优点是每个 handler 职责单一、好维护;缺点是新增 category 需要同时修改固件 ROM 的分派表和驱动侧的 handler 注册。
¶3 管理帧接收后怎么分发?QCOM 和 MTK 谁更复杂?
管理帧的接收链路比发送链路多一层 "中转"——固件不是直接把帧丢给驱动的,而是通过 WMI 事件上报,驱动再从事件中提取帧体和元数据。理解这层中转,是理解整个 RX 路径的关键。
¶3.1 QCOM 的接收链路经过哪些关键函数?
QCOM 的接收链路从固件上报到 PE 层分发,总共经过 3 个关键函数:
1 | 固件 OTA 接收管理帧 |
关键澄清:wlan_mgmt_txrx_rx_frame_handler 不是固件上报后的第一个函数——wma_mgmt_rx_process() 才是。wma_mgmt_rx_process() 是静态函数,由 WMI 事件处理器通过 wmi_unified_register_event_handler(WMI_MGMT_RX_EVENTID, wma_mgmt_rx_process) 注册。它的职责是 "翻译"——把 WMI 事件格式的帧数据转换为 mgmt_txrx 子系统能理解的 qdf_nbuf_t + mgmt_rx_event_params 格式。
¶3.2 如何根据帧类型查回调表?
wlan_mgmt_txrx_rx_frame_handler 中最关键的步骤是计算 frm_type——它决定了帧将被哪个 handler 处理。mgmt_txrx_get_frm_type() 的实现(精简版:省略 frm_type 局部变量和 ATIM subtype,保留核心 subtype→frm_type 映射):
1 | // QCOM: umac/cmn_services/mgmt_txrx/core/src/wlan_mgmt_txrx_main.c — mgmt_txrx_get_frm_type() |
对于 Action 帧,mgmt_txrx_get_action_frm_subtype() 做二级映射(22 个 category,各有专属 helper):
1 | // 二级映射:category → frm_type(关键 category 示例) |
frm_type 的两个关键设计:
- Action 帧的二级映射:非 Action 帧直接从 subtype 得到 frm_type(O (1)),Action 帧需要先解析 category 字节再做二级 switch——这意味着整个 Action 帧的 category 分发在回调表层面就开始了
- MGMT_FRM_UNSPECIFIED 的后果:如果 frm_type 为 UNSPECIFIED,
wlan_mgmt_txrx_rx_frame_handler直接qdf_nbuf_free(buf)并返回QDF_STATUS_E_FAILURE——帧永远不会到达 PE/LIM 层
上面的「省略」其实藏着两层设计取舍,值得补全。mgmt_txrx_get_action_frm_subtype()(wlan_mgmt_txrx_main.c:1235)的完整枚举是 22 个 case + 1 个 default,按「是否走二级 helper」分成三类:
- 20 个 category 走二级 helper:Spectrum Mgmt、FT(
ACTION_FAST_BSS_TRNST)、QoS、DLS、Block Ack、Public、RRM、HT、SA Query、Protected Dual、WNM、TDLS、Mesh、Self Protected、WMM、VHT、FST、RVS、USIG、Protected EHT——各自调mgmt_get_*_action_subtype(action_code),把 category 映射到更细的 frm_type。这意味着第一层回调表的「谁想要这帧」其实在 action_code 粒度就回答了:比如 SA Query Request 和 SA Query Response 是两个不同的 frm_type 槽位,PE 层还没出场,帧的去向已经分好了 - 2 个 category 直接落常量:Vendor Specific(127)和 Vendor Specific Protected(126)不经过 helper,直接映射
MGMT_ACTION_CATEGORY_VENDOR_SPECIFIC(_PROTECTED)。原因是它们的进一步区分靠 OUI 字节 + 业务逻辑(DPP?P2P?厂商私有?),mgmt_txrx 层拿不到也不该拿这个决策权,所以只给一个粗粒度槽位,细活留给 PE/supplicant - USIG 复用 TWT 的 helper:
ACTION_CATEGORY_USIG(802.11az)调的是mgmt_get_twt_action_subtype()——USIG 的 action code 与 TWT 共用一套编号,QCOM 选择复用而非另写一个几乎相同的 helper,少一份维护面
这个「20 走 helper + 2 落常量 + 1 复用」的分布,正好是 §3.3 说的「订阅与裁决分离」在第二级映射上的投影:能静态区分到 action_code 的,第一层就分掉;区分不了需要看 OUI 的,留一个粗槽位给第二层裁决。default 分支丢给 MGMT_FRM_UNSPECIFIED 则兜住了未来标准新增 category 的「前向兼容」——新 category 出现时,老驱动不认识,直接丢弃而不是误判给某个错误 handler。一个例外值得单独记下:FT(category 6)虽然走了 mgmt_get_ft_action_subtype() 这个 helper,映射出的 4 个槽位(MGMT_ACTION_FT_REQUEST/RESPONSE/CONFIRM/ACK)却没有组件注册回调、PE 层也没有 category 6 case,FT Action 帧因此在 QCOM 驱动 RX 路径上实际无人消费(MTK 由 roaming_fsm 消费,双平台对比见 §2.3)。
Beacon/Probe Req/Probe Resp 的去向:以上 subtype→frm_type 映射中,Beacon/Probe Req/Probe Resp 的 frm_type 计算到这里就结束了——它们不会进入
lim_process_action_frame(),而是由 mgmt_txrx 回调表中的 Scan/MLME 组件通过注册MGMT_BEACON/MGMT_PROBE_REQ/MGMT_PROBE_RESP回调来消费。具体来说:Beacon 和 Probe Resp 进入 Scan 模块做 BSS 列表维护和信号强度跟踪,Probe Req 在 AP 模式下触发 Probe Resp 的构建与发送(与上一章 §2.1 的发送路径首尾衔接)。这条 Scan/MLME 消费链与 Action 帧的 PE/LIM category 分发是两条并行不悖的路径,具体处理细节留到后续章节展开。
¶3.3 两层分发如何处理同一个帧的多个消费者?
QCOM 的管理帧接收采用两层分发架构,这是理解 QCOM RX 路径的关键:
第一层 — mgmt_txrx 回调表:
15 步分段小结(阅读前先建立结构预期):
阶段 步骤 核心操作 失败后果 校验 1-4 buf/psoc 非空、帧类型、地址有效性 丢弃帧 预处理 5-8 地址修复、指针偏移、加密帧处理 帧体指针可能错误 分类 9-10 frm_type 计算、重复帧检测 丢弃帧(UNSPECIFIED / 重复) 分发 11-15 查回调表、查 peer、clone buffer 并发 N/A
下面按四个代码块逐步展开。先看校验阶段(步骤 1-4)——buf/psoc 非空、帧类型、地址有效性三道检查,任何一关不过就直接 qdf_nbuf_free(buf) 丢弃,后面阶段根本没有机会执行。
1 | // QCOM: umac/cmn_services/mgmt_txrx/core/src/wlan_mgmt_txrx_main.c — 保留核心逻辑 |
校验通过后,进入预处理阶段。前 4 步的校验确保了帧头和地址的合法性,接下来 4 步负责修正地址和计算帧体偏移——无论帧是 Beacon 还是加密的 Action 帧,都要保证 mpdu_data_ptr 指向帧体的正确起始位置。
1 | /* ====== Steps 5-8: 地址修复、指针偏移、加密帧处理 ====== */ |
地址修复、指针偏移和加密帧头偏移完成后,mpdu_data_ptr 已经指向帧体(管理帧的 Frame Body 字段)的正确起始位置。接下来两步是关键决策点——根据帧体的内容计算出 frm_type,并检查是否为重复帧。
1 | /* ====== Steps 9-10: frm_type 计算 + 重复帧检测 ====== */ |
frm_type 算出来后,进入分发阶段。前 10 步完成了帧的校验、预处理和类型计算,现在 handler 调用链开始工作:查回调表、查 peer、clone buffer 并发分发——每个已注册的 handler 都会拿到一份帧副本。
1 | /* ====== Steps 11-12: 查表、Beacon 限速 ====== */ |
以上是对 wlan_mgmt_txrx_rx_frame_handler 的 15 步完整解读。它的核心职责可以归纳为三件事:校验(类型/地址/长度/重复帧)、分类(frm_type 计算)、分发(回调表 + clone buffer 并发)。frm_type 一旦被计算出来,后续的 handler 注册者(PE/LIM、P2P 组件、TDLS 组件等)只需要向 mgmt_rx_comp_cb[frm_type] 注册回调即可——这就是 mgmt_txrx 子系统作为 "中央调度器" 的价值。
第二层 — PE/LIM 层分发:
对于非 P2P、非 TDLS 的管理帧(Auth、Assoc、Action 等),mgmt_txrx 回调表中注册的 pe_handle_mgmt_frame()(lim_api.c:1254,注册为 MGMT_FRAME_TYPE_ALL 通配回调)先把帧重新封装成 CDS 报文,经 sys_bbt_process_message_core() 投递到 PE 消息队列,再由 lim_process_message_queue() 分发到 lim_process_action_frame()(lim_process_action_frame.c)——在这里进行第二次 category 分发:本地处理的(QoS、HT、VHT、SA Query 等)在 PE 层消化;需要上报的(未识别 category、Vendor Specific)通过 lim_send_sme_mgmt_frame_ind() → cfg80211_rx_mgmt() 上报。详细逻辑见第 2 章。
两层各司其职的分工,一句话就能概括:第一层回答「谁想要这帧」,第二层回答「这帧最终怎么办」。回调表是订阅机制——P2P 组件经 p2p_mgmt_rx_action_ops()(wlan_p2p_roc.c)把 tgt_p2p_mgmt_frame_rx_cb 注册到 MGMT_ACTION_VENDOR_SPECIFIC 槽位,TDLS、SON 等组件同样按 frm_type 注册回调,新增一个消费者只需注册、不动核心分发表。
switch 是裁决机制——帧落到 PE/LIM 后,lim_process_action_frame() 的 switch 给出唯一权威结论(本地消化 / 上报 / 丢弃),穷尽且可审计。
正因为分工不同,同一个帧可以既被第一层的组件消费、又被第二层的 PE 裁决上报:GAS Initial Request 就是活例——第一层 SON 组件经 wlan_son_anqp_frame() 拦一份做 ANQP 采集,第二层 PE 的 switch 又把它 lim_send_sme_mgmt_frame_ind() 上报给 supplicant。
MTK 的单层分派把「订阅」和「裁决」合并进一个 switch,换来的是简单,代价是两者耦合:新增一个消费者(比如驱动内新增一个想监听 WNM 的模块)就得改 nicRxProcessActionFrame 的分派逻辑,而它是固件 ROM blob,驱动根本改不动。这就是 QCOM 宁可多一层抽象、也要把订阅与裁决拆开的深层原因。
¶3.4 对端发来的 Auth/Assoc 帧如何触发状态转移?
前面几节追踪了管理帧从固件到 PE/LIM 的驱动侧接收路径,但有一个问题还没回答:那些被上报的 Auth/Assoc 帧到达 supplicant 之后发生了什么? 这正好与上一篇(管理帧的发送 — 控制面的帧构建与下发)的 §2.3(Deauth 到达 supplicant 后的清理逻辑)形成首尾呼应。
当驱动通过 lim_send_sme_mgmt_frame_ind() 或 kalIndicateRxMgmtFrame() 将帧上报后,它们最终都会经过同一个内核函数——cfg80211_rx_mgmt():
1 | // include/net/cfg80211.h — cfg80211_rx_mgmt() static inline 包装函数 |
注意:
cfg80211_rx_mgmt()只是一个 static inline 包装,真正的帧上报逻辑在cfg80211_rx_mgmt_ext()(net/wireless/mlme.c)中:它遍历wdev->mgmt_registrations链表,按frame_type和match_len匹配注册的监听者,匹配成功后才通过nl80211_send_mgmt()上报。为什么要用 registration list? 因为多个用户态进程可能同时监听不同类型的帧(如 wpa_supplicant 监听 Auth/Assoc、P2P daemon 监听 P2P Action),内核需要通过 registration matching 将帧准确地推送给订阅了该帧类型的进程,而不是广播给所有进程。
在 nl80211 层,nl80211_send_mgmt() 构造 NL80211_CMD_FRAME 事件并通过 netlink socket 上报。wpa_supplicant 侧的 do_process_drv_event()(src/drivers/driver_nl80211_event.c:3910)收到这个事件后,通过 switch(cmd) 分发 NL80211_CMD_FRAME → mlme_event() → wpa_supplicant_event(),进入 supplicant 的事件处理中枢:
1 | // wpa_supplicant/events.c — wpa_supplicant_event() Auth/Assoc/Disassoc/Deauth 事件分支 |
主要功能:
EVENT_AUTH→sme_event_auth():解析 AP 返回的 Auth 响应帧,判断认证是否成功。SAE 模式下可能触发多轮 Commit/Confirm 握手;成功后自动调用sme_associate()发起关联——整个 Auth→Assoc 的 supplicant 侧衔接在这里完成闭环。为什么需要sme_event_auth做 wpa_state guard? 防止延迟到达的 Auth 帧干扰已进入 ASSOCIATING 等后续状态的状态机——如果当前状态不是 WPA_AUTHENTICATING,说明认证流程已经结束或超时,延迟帧必须被丢弃EVENT_ASSOC→wpa_supplicant_event_assoc():解析 Assoc Resp 中的 status_code 和 AID(Association ID)+ 关联 IE,成功后调用wpa_sm_notify_assoc()启动 WPA 状态机的 4-Way Handshake——管理帧的事到这里结束,EAPOL 密钥协商开始EVENT_DISASSOC→wpas_event_disassoc():当WPA_DRIVER_FLAGS_SME置位时先调用sme_event_disassoc()清理驱动侧的 authenticated 状态,最终调用wpas_event_disconnect()完成状态清理——与上一篇 §2.3 描述的三步清理逻辑一致EVENT_DEAUTH→wpas_event_deauth():不等于 DISASSOC——这条路径不经过sme_event_disassoc(),而是先wpa_reset_ft_completed()清除 FT 状态,然后直接调用wpas_event_disconnect()完成断开。DEAUTH 和 DISASSOC 是两个独立的事件路径,这点在上一篇 §2.3 已有验证
Action 帧则完全不经过上面四条连接管理分支——它们以 EVENT_RX_MGMT 事件到达,在 wpa_supplicant_event() 的 EVENT_RX_MGMT 分支里按 subtype 过滤掉 Probe Req(交给 P2P)之后,统一汇入 wpas_event_rx_mgmt_action()(events.c:5565)做 category 级分发。这个函数不是 switch,而是一串 if (category == ...) 顺序判断——每个 category 命中即 return,跑完所有判断仍未命中的帧落到最后的 wpas_p2p_rx_action()(p2p_supplicant.c:7827)兜底。分发顺序本身就是一条因果链,每一环都承接 §2.2 里 QCOM/MTK 驱动侧裁决的上游:
- WMM(category 17)→
wmm_ac_rx_action()(wmm_ac.c:732):ADDTS/DELTS 在 supplicant 侧的闭环。§2.1 说 QCOM/MTK 把 QoS/WMM 扣在驱动本地处理,但 supplicant 仍保留自己的 WMM AC 状态机,承担 WMM 参数协商的另一半 - FT(category 6)→
ft_rx_action()(events.c:5238):802.11r over-the-DS 快速切换的密钥上下文交换。注意它与 §2.3 说的 FT Auth 是两回事——FT Auth 是认证帧(看 Auth alg 字段),在 QCOM PE 层就被lim_process_ft_auth_frame()拦下;这里的 FT Action 是重关联阶段走 Action 通道的 FT Request/Response,两者共同拼出 FT 的完整流程 - SA Query(category 8)→
sme_sa_query_rx()(sme.c:3628):PMF 的 supplicant 侧闭环,与 §2.1 里 QCOM 驱动本地处理 SA Query 形成「驱动先答、supplicant 兜底」的双层防御 - WNM(category 10)→
ieee802_11_rx_wnm_action()(wnm_sta.c:1997):BTM Request/Response 的 supplicant 决策中枢。这正好接上 §2.2 里 QCOM 把 WNM 全量上报的原因——漫游决策(要不要切、切到哪个 BSS)必须在 supplicant 的 BSS 选择逻辑里做,驱动不该代劳;而 §2.4 里 MTK 却选择驱动本地处理 BTM,两条相反路线在此汇合 - GAS(Public Action 4 / Protected Dual 9)→
gas_query_rx()(gas_query.c:517):ANQP 查询收发状态机。§2.2 里 QCOM SON 模块先经wlan_son_anqp_frame()拦一份做采集再继续上报,那份「继续上报」的帧最终就落在这里 - RRM(category 5)→
wpas_rrm_handle_radio_measurement_request()(rrm.c:1415)、wpas_rrm_process_neighbor_rep()(rrm.c:62)、wpas_rrm_handle_link_measurement_request()(rrm.c:1453)三个独立入口,按 action field 细分 - DPP(Public Action 里 Vendor Specific + DPP OUI type)→
wpas_dpp_rx_action()(dpp_supplicant.c:4084):扫码配网的 supplicant 侧协议状态机,§2.2 里 QCOM 拿dpp_oui比对匹配后上报的帧就落在这里 - 兜底 →
wpas_p2p_rx_action():P2P 的 Public Action 子类型(GO Negotiation / Provision Discovery / Invitation)以及所有上面没接住的 category 都落在这里——它本身就是 supplicant 侧的「未识别帧兜底上报」镜像
注意 MBO 不在这条分发链里——它没有独立的 Action category,而是借道 WNM(BTM)和 Assoc 帧里的 MBO IE 生效,mbo_parse_rx_anqp_resp()(mbo.c:662)只在 ANQP 响应里解析 MBO 属性。这个「MBO 无专属 Action category」的事实,正好解释了为什么 §2.2 的 QCOM 驱动分发表里也找不到 ACTION_CATEGORY_MBO。
于是整条 Action 帧的裁决链被拉成三层:QCOM 的第一层(mgmt_txrx 回调表,按 frm_type 分拣)+ 第二层(PE/LIM 的 category switch,本地消化还是上报),加上 supplicant 这第三层(wpas_event_rx_mgmt_action(),上报来的帧由哪个协议子系统消费)。MTK 跳过第一层(单层分派),但 supplicant 这层两个平台共享——这是「驱动分平台、协议栈统一」的必然结果。
RX 路径全貌收束:现在你应该能把整条接收链路串起来了——固件上报(WMI/MBOX)→
wma_mgmt_rx_process/nicRxProcessActionFrame(驱动第一关)→wlan_mgmt_txrx_rx_frame_handler/ category 分派(QCOM 两层 / MTK 单层)→cfg80211_rx_mgmt()(static inline 包装,内核上报入口)→cfg80211_rx_mgmt_ext()(mgmt_registrations 链表遍历,frame_type 匹配后决定上报哪个用户态进程)→ nl80211 event(netlink broadcast)→wpa_supplicant_event()。到了 supplicant 之后分两路:连接管理帧(Auth/Assoc/Disassoc/Deauth)走sme_event_auth()/wpa_supplicant_event_assoc()/wpas_event_disassoc()/wpas_event_deauth()四个事件分支;Action 帧走EVENT_RX_MGMT→wpas_event_rx_mgmt_action()的 category 分发(WMM/FT/SA Query/WNM/GAS/RRM/DPP/P2P 兜底)。一条从对端空中信号到 supplicant 状态转移的完整链路,至此闭合。
¶3.5 MTK 为什么只需要一层分发?
MTK 的管理帧接收链路比 QCOM 短得多——没有 WMI 事件中间层,没有 mgmt_txrx 回调表,直接从固件 ROM 按 category 分派。
与 QCOM 的关键差异:
- 无 WMI 层:MTK 不使用 WMI 事件机制上报管理帧——固件通过 NIC RX 中断直接把帧数据传递给驱动侧的
SW_RFB(Receive Frame Buffer)结构体,省去了 WMI 事件打包 / 解包的开销 - 无 mgmt_txrx 回调表:没有 frm_type 计算、没有 handler 链表遍历、没有 clone buffer 并发机制——category 分派是固件 ROM 直接完成的
nicRxProcessActionFrame是 ROM blob:这个函数不是驱动代码,驱动代码中只有它的声明(include/nic/nic_rx.h),实际实现在 WiFi 固件的 ROM 中。这意味着它的行为取决于固件版本,驱动无法修改它的分派逻辑- 兜底策略相反:QCOM 不认识的 category 在 PE 层
default分支静默丢弃;MTK 不认识的 category 通过aisFuncValidateRxActionFrame→kalIndicateRxMgmtFrame全量上报 supplicant
具体的 category→handler 分派链路(nicRxProcessActionFrame → p2pFuncValidateRxActionFrame / wnmWNMAction / aisFuncValidateRxActionFrame → kalIndicateRxMgmtFrame)已在 §2.4 完整描述,此处不再重复。上述 4 点架构差异解释了 MTK 为什么只需要单层分发:无 WMI 中间层意味着没有事件格式转换开销,无回调表意味着没有 frm_type 计算和 handler 遍历,ROM blob 意味着分派逻辑对驱动而言是黑盒,顺序分派意味着没有并发同步的复杂度——但也失去了并发处理多个帧的能力。
把这条链逐函数铺开,MTK 的「单层」其实也能拆成四个时序节点。第一节点是固件 ROM 的 nicRxProcessActionFrame()(include/nic/nic_rx.h:1804,仅声明、实现在固件 blob)——它在固件侧完成 Action Frame Header 解析和 category 三选一分派,是整条链里驱动看不见的「黑盒前置」。第二节点由三个驱动侧 handler 之一接管:category 4 进 p2pFuncValidateRxActionFrame()(mgmt/p2p_func.c:4448)做 P2P 子类型二级分发并推进 P2P FSM;category 10 进 wnmWNMAction()(mgmt/wnm.c:85)按 action 细分 BTM/Timing Measure,开启 CFG_SUPPORT_802_11V_BTM_OFFLOAD 时本地应答、否则转兜底;其余 category 进 aisFuncValidateRxActionFrame()(mgmt/ais_fsm.c:8394)。第三节点是 aisFuncValidateRxActionFrame 的 BSS 类型闸门——非 AIS 直接 return。第四节点 kalIndicateRxMgmtFrame()(os/linux/gl_kal.c:7736)取 net_device、调 cfg80211_rx_mgmt() 出内核边界。对比 QCOM 的 15 步:MTK 把「frm_type 计算 + 回调表查表 + clone 分发」压缩成固件 ROM 里的一次 category switch,步数少了一个数量级,代价是每一帧只命中一个 handler。
「无回调表」的代价可以量化。QCOM 第一层回调表是 mgmt_rx_comp_cb[MGMT_MAX_FRAME_TYPE](wlan_mgmt_txrx_main_i.h:220,139 个 frm_type 槽位),每个槽位是一条 struct mgmt_rx_handler 链表而非单个函数指针——Scan、MLME、PE、P2P 等组件各自向同一槽位注册回调,分发时逐个 qdf_nbuf_clone() 复制 buffer 再调用(§3.3 步骤 14),同一个帧因此能被多个消费者同时看到。
MTK 恰好相反:单条 SW_RFB 顺序走完,nicRxProcessActionFrame 的 category switch 三选一(P2P / WNM / AIS 兜底),一帧只命中一个 handler,没有 clone、没有多消费者——需要共享时只能在上层再复制或串行排队,这就是「无回调表」的并发代价。
「三选一」的兜底分支值得单独拆开看容错语义。aisFuncValidateRxActionFrame()(ais_fsm.c:8394)是所有不命中 P2P/WNM 的 Action 帧的最终落点,但它不是无条件上报——先 if (!IS_BSS_INDEX_AIS(...)) 校验帧所属 BSS index 必须是 AIS 网络类型,非 AIS(比如帧落在 P2P 接口的 BSS 上)直接 return 丢弃,只有 AIS 帧才经 kalIndicateRxMgmtFrame() 透传 cfg80211。这意味着 MTK 的「全量上报」其实有两道前置闸门:固件 ROM 按 category 三选一筛掉 P2P/WNM,驱动侧再按 BSS 类型筛掉非 AIS——两道都放行的才到 supplicant。
于是「未知 category」的错误处理路径在双平台呈现出镜像的两端:QCOM 对不认识的 category 在 mgmt_txrx 层(MGMT_FRM_UNSPECIFIED)或 PE 层(switch 的 default)静默丢弃,只打一行 pe_warn_rl 日志;MTK 则一路兜底上报 supplicant。QCOM 的选择是「驱动不猜」——802.11 标准还在演进,老驱动遇到新 category 时,猜错处理路径比丢弃更危险(可能误改链路状态或误触发状态机);代价是若新 category 恰是 supplicant 认识的,QCOM 会因提前丢弃而错过,必须升级驱动固件才能放行。
MTK 的选择是「上层兜底」——驱动只做粗筛,细判全交给 supplicant,前向兼容性更好(supplicant 更新快、懂的新 category 多);代价是驱动不认识的所有帧都涌向 supplicant,恶意对端可以构造海量怪帧打满 netlink 上报通道,supplicant 必须自己再做一轮合法性校验兜底。这个「丢在驱动」vs「漏给上层」的取舍,和 §2.4 里 WNM 的「上层决策 vs 驱动自治」是同一枚硬币的两面。
这种「一帧多消费者」与「一帧一消费者」的分野,在 Linux 内核和蜂窝协议栈里都有先例。netfilter 的 nf_register_net_hook() 允许多个钩子注册到同一个 hook 点、依次处理同一个 skb,是 QCOM 多消费者思想的雏形——但它比 QCOM 的槽位多了一维排序:5 个 hook 点(NF_INET_PRE_ROUTING/NF_INET_LOCAL_IN/NF_INET_FORWARD/NF_INET_LOCAL_OUT/NF_INET_POST_ROUTING)相当于 frm_type 槽位,nf_hook_ops.priority 字段(include/linux/netfilter.h:97)把同一 hook 点的钩子按 NF_IP_PRI_FIRST(INT_MIN)到 NF_IP_PRI_LAST(INT_MAX,include/uapi/linux/netfilter_ipv4.h:31/45)升序排列,priority 小的先执行,先后顺序显式可控。而 QCOM 的 struct mgmt_rx_handler 只有 next 指针,同一槽位的 handler 按注册顺序执行,先后不可控。
LTE/NR 的 MAC 层按 LCID(逻辑信道 ID)把一条 PDU 解复用给唯一的 RLC 实体——这是 MTK category 三选一的同构。
更深一层的差异在索引维度:netfilter 的 5 个 hook 点按包方向切分(入站 → 本地投递 → 转发 → 本地发出 → 出站),一个 skb 沿流向依次命中不同的 hook 点;QCOM 的 139 个 frm_type 槽位按帧类型切分,每个 subtype / action category 独占一个槽,一个帧只命中其中一个槽。同为「多消费者」调度表,一个以「这包往哪走」为轴,一个以「这帧是什么」为轴。
这种「多槽位、按类型索引」的分发表,在硬件层也有同构:PCIe 的 MSI-X 让一个设备函数最多分配 2048 个 vector,每个 vector 独立路由到不同 CPU core——一张「哪类中断 → 哪个处理队列」的表,与 mgmt_rx_comp_cb 的「哪类帧 → 哪个 handler 槽」是同一张表。差别在扇出方向:QCOM 命中槽位后靠 qdf_nbuf_clone() 把一帧复制成多份给多个消费者(一帧多消费者),MSI-X 命中 vector 后只触发绑定的那一个软中断(一中断一消费者)。
QCOM 的接收系统像机场的行李转盘分拣 + 登机口验票两层:第一层是行李转盘(mgmt_txrx 回调表),行李(帧)贴好 frm_type 标签后,转盘按标签自动路由——同一件行李还能克隆多份,同时送到 Scan、PE、P2P 多个领取口;第二层是登机口验票(PE/LIM category switch),看具体舱位(category),本地乘客在候机厅等,国际乘客送海关(cfg80211)出境。MTK 则像只有登机口验票的单层小机场——一张票(SW_RFB)一次只验一个旅客,固件 ROM 看 category 三选一,没有行李转盘的多份克隆,一次只放行一个。
QCOM vs MTK RX 对比:
| 维度 | QCOM | MTK |
|---|---|---|
| 分发层数 | 两层(mgmt_txrx + PE/LIM) | 单层(nicRx + FSM) |
| 回调机制 | 回调表 + handler 链表(多注册) | 直接 category 分派 |
| P2P/TDLS 路径 | 第一层就走专属 handler | 和其他帧同路径 |
| 未识别帧 | PE 层 default 静默丢弃 | AIS FSM 验证后上报 |
| 并发处理 | handler 链表 clone buffer 并发 | 顺序分派 |
以上追踪的都是驱动能 "看到" 的管理帧——Auth/Assoc/Action 帧从固件上报到驱动,经回调表或 category 分派,部分本地消化、部分上报 supplicant,整条软件栈都有对应的处理代码。但还有一类帧,它们同样属于管理帧或控制帧范畴,却在软件层完全不可见——不是因为代码被隐藏了,而是因为它们的设计目标要求响应时间在微秒级,软件根本来不及参与。下面就来看看这些 "隐身" 的帧。
¶4 那些你永远看不到的帧:RTS/CTS/ACK/PS-Poll 去哪了?
最后聊一类特殊的帧——你永远看不到它们的代码,因为它们根本不走驱动软件栈。
| 帧类型 | 处理者 | 软件角色 |
|---|---|---|
| RTS/CTS | 固件 MAC 层 | 驱动通过 WMI/MBOX 设置 RTS threshold 参数,具体每个帧的 RTS/CTS 交换由固件自主决定 |
| ACK | 固件 MAC 层 | 设置 ACK timeout 参数后,固件在收到数据帧后自动回复 ACK,CPU 不参与 |
| PS-Poll | 固件省电引擎 | 驱动只配置 PS-Poll count 和间隔参数,帧的生成和发送全在固件 |
PS-Poll 帧格式:PS-Poll(Power Save Poll)是控制帧的一种(IEEE 802.11-2024 §9.3.1.5),用于 STA 从省电模式唤醒后向 AP 请求缓存帧。它的结构极其简洁——仅 16 字节:
| 字段 | 长度 | 说明 |
|---|---|---|
| Frame Control | 2 字节 | Type=Control, Subtype=PS-Poll (1 0 1 0) |
| Duration/ID (AID) | 2 字节 | STA 的 Association ID(AID),两 MSB 置 1(非 BDT 变体) |
| RA (Receiver Address) | 6 字节 | AP 的 BSSID |
| TA (Transmitter Address) | 6 字节 | STA 的 MAC 地址 |
注意 PS-Poll 帧没有 Sequence Control 字段——它不是一个数据帧,不需要重传窗口管理。Duration/ID 字段的两 MSB 置 1,低 14 位携带 STA 的 Association ID(AID)。STA 收到 Beacon 后检查 TIM(Traffic Indication Map)位图中自己 AID 对应的 bit 是否置位,若置位则发送 PS-Poll;AP 收到 PS-Poll 后从 Duration/ID 字段提取 AID,据此查找该 STA 的缓存帧。整个 PS-Poll 在固件省电引擎中全自动完成:固件检测到 Beacon 中 TIM 位图对应 bit 置位→生成的 PS-Poll 帧→发送到 AP→接收缓存帧→返回省电模式,驱动层完全不参与。
ACK timeout 与 PS-Poll 的双平台配置对比:虽然这些帧本身不可见,但它们的时间参数和省电策略仍然可以通过驱动配置来调整。
这些帧的共同特征是:硬实时——SIFS(Short Interframe Space)只有 10 微秒(802.11b DSSS)或 16 微秒(802.11a/g/n/ac/ax OFDM,取决于 PHY 类型而非频段),软件根本来不及反应。所以它们的设计哲学是 "参数配置 + 全自动执行"。
为什么 SIFS 决定了这些帧必须在硬件层处理? 以 ACK 帧为例:对端收到数据帧后,必须在 SIFS 时间(10us 或 16us) 内发出 ACK 帧。这个时间窗口极其苛刻——一次 CPU 中断响应就需要 5-10us,一次 DMA 传输也需要数个微秒。如果把 ACK 帧的生成交给软件(哪怕只是固件内的 CPU 进程),调度延迟就足以让它错过 SIFS 窗口。因此 802.11 协议从设计层面就要求这些帧由 MAC 硬件状态机 自主生成:硬件在收到完整数据帧后,自动在 SIFS 时间点触发 ACK 发送,CPU 全程不参与。
RTS/CTS 的交互也是同样的硬实时约束——从 CTS 帧结束到数据帧开始,间隔也是 SIFS,硬件必须自主完成。
RTS threshold 配置——软件层唯一的 "遥控按钮"。虽然 RTS/CTS 帧本身不可见,但驱动可以配置 RTS threshold 参数来控制固件的 RTS 行为:当数据帧长度超过该阈值时,固件在发送前自动发起 RTS/CTS 握手。以下是双平台如何设置这个参数:
1 | // QCOM: core/hdd/src/wlan_hdd_cfg80211.c — set_wiphy_params |
1 | // MTK: os/linux/gl_p2p_cfg80211.c — mtk_p2p_cfg80211_set_wiphy_params |
mac80211 侧:RTS threshold 在发送路径的实际生效点在 ieee80211_tx_h_rate_ctrl()(net/mac80211/tx.c)——if (len > tx->local->hw.wiphy->rts_threshold) { txrc.rts = true; }。固件根据 use_rts 标志自动决定是否在发送数据帧前发起 RTS/CTS 交换。
PS-Poll 与省电参数的双平台配置:除了 RTS threshold,驱动还能通过以下参数影响固件的 PS-Poll 和省电行为:
1 | // QCOM: PS-Poll 与省电相关 INI 参数(core/hdd/inc/wlan_hdd_cfg.h) |
MTK 走同样的策略,但参数命名风格不同:
1 | // MTK: PS-Poll 配置通过 power management 参数下发(mgmt/power.c) |
双平台在 PS-Poll 管理上的策略一致:驱动只下发参数上限,具体执行全在固件。驱动不关心某个特定帧是第几次 PS-Poll、AP 有没有返回缓存帧——这些都是固件省电引擎的内部状态。
这些帧就像跑道的自动引导灯系统。塔台(驱动)不是每到一架飞机就过去手动开一次灯——它只负责设置开灯规则(什么时候亮、亮多久、亮多亮),具体的开关由跑道系统(固件 / 硬件)全自动执行。这样飞机滑行时(SIFS 时间内)灯能瞬间反应,不需要等塔台打电话通知。
¶5 双平台架构差异有多大?QCOM vs MTK 全景对比
| 维度 | QCOM | MTK |
|---|---|---|
| 帧校验位置 | 固件 FCS + 驱动二次校验(PMF/长度/地址) | 固件 ROM FCS 单次校验 |
| RX 架构 | 两层分发(mgmt_txrx 回调表 + PE/LIM) | 单层分发(FSM category 分派) |
| Auth 处理 | PE 层直发,跳过 ROC/Policy Manager | 与其他帧同路径,经 MBOX |
| Action 分发 | 第一层按 frm_type 查回调表,第二层 PE 按 category 二次分发 | AIS FSM 直接按 category 分派 |
| 通信机制 | WMI(控制面) | MBOX 消息队列 |
| 并发 | Handler 链表 + clone buffer | 顺序分派 |
| ROC(Remain on Channel,信道驻留)管理 | 独立的 Policy Manager 审批 | MBOX 参数直传 |
| 未识别帧 | PE 层 default 静默丢弃 | AIS FSM 兜底全量上报 |
QCOM 的设计哲学:用复杂度换灵活性。双路径 + 两层分发让 QCOM 在 P2P、TDLS、多信道并发等场景下有更多优化空间,但代价是代码路径多、调试复杂。
MTK 的设计哲学:用简洁性换可靠性。单入口 + 单层分发让代码路径少、状态管理简单,大帧直通是点睛之笔——控制面的帧走数据面的 DMA 通道,反映了 MTK 对硬件能力务实的利用。
回顾全链路,两个平台的 Action 帧接收分别由一对入口函数统领:QCOM 的 lim_process_action_frame()(PE 层 500 行 category 大全分发器)和 MTK 的 nicRxProcessActionFrame()(固件 ROM blob 入口,驱动侧按 category 直连 handler)。前者是所有 Action 帧的「中央调度塔」,后者是固件与驱动的「分工边界碑」——理解了这两个函数的定位,就理解了全章。
QCOM 的塔台是一个大型国际机场——值机 + 登机口两道关卡(两层分发)、VIP 通道(Auth 快速路径)。MTK 的塔台是一个支线航站楼——只设一道安检口(单层分派)、但给特大件开了货运电梯(大帧直通)。两种设计没有绝对的好坏,只有场景适配的区别。
¶6 总结:双平台接收架构对比
从对端空中信号到 supplicant 状态转移,管理帧接收路径的两条实现——QCOM 的两层分发与 MTK 的单层分派——至此全部走完。收束全章,可以归结为一张全链路回顾表和一张关键常量速查表。
全链路回顾表:
| 环节 | QCOM | MTK |
|---|---|---|
| 固件上报 | WMI 事件(WMI_MGMT_RX_EVENTID)→ wma_mgmt_rx_process() | NIC RX 中断 → ROM blob nicRxProcessActionFrame() |
| 中央分发 | wlan_mgmt_txrx_rx_frame_handler():frm_type 计算 + 回调表 + clone 多消费者 | 固件 ROM category 三选一:P2P / WNM / 兜底 |
| 二次分发 | lim_process_action_frame():category switch 四阶段(安检→本地→上报→丢弃) | 无(单层,无二次分发) |
| 本地处理 | QoS/Spectrum/WMM/HT/VHT/SA Query/Block Ack(PE 层闭环) | wnmWNMAction()(BTM offload)、p2pFuncValidateRxActionFrame()(P2P FSM) |
| 未识别帧 | default 静默丢弃 | aisFuncValidateRxActionFrame() 兜底上报 |
| 上报通道 | lim_send_sme_mgmt_frame_ind() → SME → HDD | kalIndicateRxMgmtFrame() |
| 内核汇聚 | cfg80211_rx_mgmt() → cfg80211_rx_mgmt_ext() → nl80211_send_mgmt() | 同左 |
| supplicant 闭环 | wpa_supplicant_event() 四分支(Auth/Assoc/Disassoc/Deauth) | 同左 |
关键常量速查表:
| 常量 | 值 | 定义位置 | 含义 |
|---|---|---|---|
MGMT_MAX_FRAME_TYPE | 139 | wlan_mgmt_txrx_utils_api.h:866 | QCOM 回调表 frm_type 槽位数 |
MGMT_FRM_UNSPECIFIED | -1 | wlan_mgmt_txrx_utils_api.h:726 | 未识别帧类型,直接丢弃 |
| SIFS | 10 / 16 us | IEEE 802.11(b / a·g·n·ac·ax) | 帧间最小间隔,决定 ACK/RTS/CTS 必须硬件处理 |
| PS-Poll 帧长度 | 16 字节 | IEEE 802.11-2024 §9.3.1.5 | 省电轮询帧固定长度 |
ACTION_CATEGORY_* | 0~127 | IEEE 802.11-2024 §9.4.1.11 | Action 帧 category 编码(20+ 种) |
CFG_SUPPORT_802_11V_BTM_OFFLOAD | 编译选项 | MTK mgmt/wnm.c | 开启后 MTK 本地处理 BTM |
两套架构,同一个目标:把对端发来的每一条管理帧,准确、及时地送到该去的地方——本地消化的在驱动闭环,需要决策的交 supplicant。从固件的第一声「有帧来了」,到 supplicant 的状态机向前推进一步,管理帧接收的控制面,至此闭合。
协议依据:IEEE 802.11-2024 §9.3.3(管理帧格式)、§9.4.1.11(Action 帧 category 定义)、§11.3(Auth/Assoc 流程)。源码路径见各代码块注释。
本文源码取自 QCOM qcacld-3.0、qcacmn;MTK gen4m;wpa_supplicant 来自 w1.fi。