4、软件架构:Android/Linux WiFi与蓝牙的驱动栈与HCI接口
好,咱们进入第四讲。这一讲,我打算把WiFi和蓝牙在软件层面是怎么“搭积木”的,给大家拆开揉碎了讲清楚。
很多做上层应用开发的兄弟,一遇到WiFi和蓝牙互相干扰的问题,就习惯性地往硬件上找原因。其实,我这些年调过的板子里,有将近一半的干扰问题,根因都在软件栈的配合上。说白了,就是驱动层没沟通好,或者HCI接口上的数据打架了。
4.1 整体架构:一个宏观的俯视图
我们先看一张大图。在Android/Linux系统里,WiFi和蓝牙的软件栈,其实长得挺像的。它们都遵循一个经典的分层模型:
- 应用层(Application Framework):你写的App,或者系统自带的Settings。它不直接操作硬件,而是通过系统服务发命令。
- 系统服务层(System Service):比如Android里的WifiService、BluetoothService。它们负责管理策略、权限,以及跟底层HAL打交道。
- HAL层(Hardware Abstraction Layer):这是Google为了屏蔽厂商差异搞出来的。WiFi有wpa_supplicant/hostapd,蓝牙有Bluetooth HAL。它们把内核驱动的接口封装成更友好的API。
- 内核驱动层(Kernel Driver):这是今天的主角之一。WiFi驱动(通常是cfg80211/mac80211框架)和蓝牙驱动(通常是基于HCI的USB/UART驱动)就在这里。
- 固件(Firmware):跑在芯片内部的小系统。它直接控制射频收发。
嗯,这里要注意。虽然分层清晰,但实际调试时,问题往往发生在层与层的交界处。我个人习惯,遇到WiFi断流或者蓝牙卡顿,第一件事就是先确认问题出在哪一层。
4.2 WiFi驱动栈:从cfg80211到mac80211
WiFi驱动这块,Linux内核已经帮我们搭好了很好的框架。核心就是两个家伙:cfg80211 和 mac80211。
- cfg80211:这是内核里WiFi配置管理的核心。它负责跟用户态的wpa_supplicant通信。你想想看,wpa_supplicant要扫描、要连接、要配密钥,都是通过netlink socket发消息给cfg80211。
- mac80211:这是软件MAC层的实现。它处理了大部分802.11协议的逻辑,比如帧聚合、功率管理、速率控制。驱动开发者只需要实现底层的硬件操作函数(ops)就行。
我在项目中遇到过,有些厂商为了省事,直接跳过mac80211,自己搞一套全硬件的MAC。结果呢?虽然性能好了,但跟标准内核框架的兼容性就差了,一升级内核就出问题。
关键点:WiFi驱动与蓝牙驱动的“握手”,通常发生在MIMO(多入多出)共享天线或者共用晶振的场景下。这时候,驱动里会有一个专门的“共存仲裁模块”。
4.3 蓝牙驱动栈:HCI接口是命脉
蓝牙的软件栈,比WiFi要更“标准化”一些。它的核心就是HCI(Host Controller Interface)。
HCI定义了主机(Host,也就是你的AP/CPU)和控制器(Controller,也就是蓝牙芯片)之间的通信协议。它支持三种传输层:
- UART(H4/H5):最常见,成本低。我调试过的绝大多数IoT设备都用这个。
- USB:带宽大,适合高速蓝牙(比如蓝牙5.0+)。
- SDIO/SPI:比较少用,但某些WiFi+蓝牙 combo芯片会这么干。
在Android里,蓝牙的HAL层(libbt-vendor.so)直接操作HCI接口。它通过打开一个串口设备(比如/dev/ttyS1)或者USB设备,发送HCI命令、接收HCI事件。
我的经验:调试蓝牙连接不稳定时,我建议先用hcidump或者btmon抓一下HCI日志。看看是不是有大量的HCI命令超时或者事件丢失。如果是,那八成是HCI接口上的数据被WiFi的高优先级流量给“饿死”了。
4.4 共存的关键:PTA与驱动层的协同
好了,WiFi和蓝牙各自的栈都清楚了。那它们是怎么“打架”又怎么“和好”的呢?
这里要引入一个概念:PTA(Packet Traffic Arbitration)。PTA是一个硬件模块,通常集成在WiFi/蓝牙 combo芯片内部。它的作用就是仲裁:当WiFi和蓝牙同时要发数据时,谁先发?
但是,光有硬件PTA不够。软件驱动层必须告诉PTA当前的状态。比如:
- WiFi驱动说:“我正在传一个重要的视频流,优先级高!”
- 蓝牙驱动说:“我正在传音频,延迟敏感,别抢我!”
这些信息,就是通过驱动之间的私有接口传递的。在Linux内核里,常见的做法是:
- WiFi驱动和蓝牙驱动共享一个GPIO或者Mailbox。
- 或者,通过一个虚拟总线(比如MIPS的
btcoex框架)来交换状态。
我曾经调试过一个项目,WiFi吞吐量一高,蓝牙鼠标就卡顿。抓了三天日志,最后发现是蓝牙驱动在初始化时,没有正确配置PTA的优先级寄存器。导致WiFi一直抢占信道,蓝牙只能“捡剩饭”。
避坑指南:千万不要以为PTA是硬件的事,软件就不用管了。我曾经见过一个团队,硬件PTA设计得完美,但驱动里忘了在WiFi扫描时通知蓝牙“我要扫描了,你忍一下”。结果每次WiFi扫描,蓝牙就断连。嗯,这种问题,查硬件是查不出来的。
4.5 实战:如何从日志中定位驱动栈问题
说了这么多理论,来点实际的。当你遇到WiFi蓝牙共存干扰时,怎么从日志里找到驱动栈的蛛丝马迹?
- 抓内核日志(dmesg):看有没有
btcoex或者pta相关的打印。很多驱动在共存状态变化时会打印信息。 - 抓WiFi驱动日志:比如
iw event或者cat /proc/net/wireless。看看有没有大量的TX dropped或者retry。 - 抓蓝牙HCI日志:用
btmon。重点看HCI Command Complete事件的延迟。如果某个命令的响应时间超过100ms,那基本可以断定HCI通道被阻塞了。 - 检查中断:
cat /proc/interrupts。看看WiFi和蓝牙的中断频率。如果蓝牙中断数突然下降,而WiFi中断数飙升,那说明蓝牙被“饿死”了。
一个典型的共存问题日志特征:
[ 123.456] btcoex: wifi status changed to SCAN
[ 123.457] btcoex: bt status changed to ACTIVE
[ 123.458] btcoex: ARBITER: wifi wins, bt paused
[ 123.459] Bluetooth: hci0: command 0x2005 timeout
[ 123.460] Bluetooth: hci0: command 0x2005 timeout
看到没?WiFi一扫描,蓝牙命令就超时。这就是典型的PTA仲裁没做好,或者驱动没及时通知蓝牙“我要抢资源了”。
4.6 小结:软件栈的“潜规则”
最后,我总结几条软件栈调试的“潜规则”:
- 规则一:不要只看WiFi或者蓝牙的单独日志。共存问题,必须两边日志一起看,时间戳要对齐。
- 规则二:HCI接口是蓝牙的“咽喉”。任何WiFi的高负载操作(扫描、大流量收发),都可能卡住HCI。
- 规则三:PTA的配置,一定要在驱动初始化阶段就做好。我见过太多“默认配置能用,但一跑大流量就崩”的案例。
- 规则四:如果条件允许,用逻辑分析仪抓一下WiFi和蓝牙的硬件信号(比如PTA的GPIO)。这比看日志更直观,能直接看到仲裁结果。
好了,这一讲就到这里。下一讲,我们会深入硬件层面,看看天线隔离和射频前端是怎么影响共存的。到时候,我会分享一个我亲手调过的“天线串扰”案例,保证让你大开眼界。