2. Wi-Fi 驱动框架解析:Linux 无线子系统与 Tina 平台适配
做 Wi-Fi 驱动开发,第一件事不是看芯片手册,而是搞懂 Linux 无线子系统的分层结构。说白了,你得知道你的代码站在哪一层,上下分别跟谁打交道。
我刚开始接触这块时,也犯过迷糊——明明在应用层调了 iwconfig,怎么底层寄存器就变了?后来把整个框架捋了一遍,才明白中间隔了多少层抽象。今天我们就一层层拆开来看。
2.1 整体架构:三层两接口
Linux 无线子系统可以概括为「三层两接口」:
- 上层:用户空间工具(iw、wpa_supplicant、hostapd)
- 中间层:cfg80211 和 mac80211(内核无线框架)
- 下层:驱动适配层 + 总线接口(SDIO/USB/PCIe)
上下层之间通过 nl80211 和 ieee80211_ops 两个关键接口通信。我习惯把 nl80211 比作「命令通道」,把 ieee80211_ops 比作「硬件操作表」。
2.2 cfg80211:策略与监管中心
cfg80211 是内核无线配置管理层。它不直接操作硬件,而是负责:
- 监管合规:不同国家/地区的信道、功率限制
- 连接管理:扫描、关联、认证的流程控制
- 虚拟接口:支持多 vif(station、AP、monitor 模式)
- 扫描结果缓存:避免重复扫描
我踩过的坑:有一次客户反馈 5G 信道扫描不到。查了半天,发现是 cfg80211.regdom 默认设成了 CN,而 CN 的 5G 信道列表跟 FCC 不一样。后来我在驱动初始化时显式调了 regulatory_hint(),问题解决。
cfg80211 通过 struct cfg80211_ops 向下注册回调。驱动需要实现 add_virtual_intf、change_virtual_intf、scan、connect 等接口。你想想看,如果没有这层抽象,每个驱动都要自己处理监管列表——那得多乱。
2.3 mac80211:软件 MAC 协议引擎
mac80211 是 Linux 无线子系统的「心脏」。它实现了 802.11 MAC 层的核心协议逻辑,包括:
- 帧处理:封装/解封装 802.11 帧头
- 速率控制:minstrel_ht 算法选择最佳 MCS
- 电源管理:PS-Poll、U-APSD 等省电机制
- TX/RX 队列管理:多优先级队列调度
驱动通过 struct ieee80211_ops 向 mac80211 注册硬件操作。我列几个最常用的回调:
| 回调函数 | 作用 | 调用时机 |
|---|---|---|
start |
启动硬件 | 网络设备 up 时 |
stop |
停止硬件 | 网络设备 down 时 |
add_interface |
创建虚拟接口 | 创建 vif 时 |
config |
配置信道/功率 | 切换信道或调整功率时 |
tx |
发送数据帧 | 有数据要发送时 |
sta_add |
添加 station 信息 | 新 station 关联时 |
个人习惯:调试 mac80211 驱动时,我总会在 tx 和 config 回调里加 print_hex_dump,打印前 64 字节。这样能快速判断帧结构是否正确,信道是否切到位。
2.4 Tina 平台驱动适配层
Tina Linux 在标准 Linux 无线框架之上,加了一层平台适配。为什么需要这层?说白了,Allwinner 的 SoC 有自己的一套时钟、GPIO、电源管理逻辑,标准驱动没法直接复用。
适配层主要做三件事:
- 硬件资源抽象:把 SoC 的 GPIO、IRQ、DMA 通道封装成平台设备
- 电源管理桥接:对接 Tina 的
sunxi-pm框架,实现 Wi-Fi 休眠唤醒 - 固件加载:从文件系统加载芯片固件,并完成初始化序列
我举个例子。在 Tina 上适配 RTL8822CS 时,驱动适配层需要实现:
// 平台设备结构体
static struct platform_driver rtl8822cs_sdio_driver = {
.probe = rtl8822cs_sdio_probe,
.remove = rtl8822cs_sdio_remove,
.driver = {
.name = "rtl8822cs",
.pm = &rtl8822cs_pm_ops, // 挂起/恢复
},
};
// 电源管理回调
static int rtl8822cs_suspend(struct device *dev) {
// 保存寄存器状态
// 关闭 Wi-Fi 时钟
// 通知 mac80211 进入休眠
ieee80211_suspend(hw);
return 0;
}
注意:Tina 适配层跟芯片厂商的 HAL 是两层。厂商 HAL 管芯片内部寄存器,Tina 适配层管 SoC 侧资源。两者分工明确,别混在一起写。
2.5 SDIO 与 USB 接口驱动
Wi-Fi 芯片跟主控通信,最常见的就是 SDIO 和 USB。我两种都调过,说说区别。
2.5.1 SDIO 接口
SDIO 是嵌入式 Wi-Fi 的主流选择。它复用 SD 卡控制器,速度快(SDIO 3.0 可达 200MB/s),而且支持 in-band interrupt——芯片有事件时直接拉中断线,不需要轮询。
SDIO 驱动的核心是注册一个 sdio_driver:
static const struct sdio_device_id rtl8822cs_sdio_ids[] = {
{ SDIO_DEVICE(0x024c, 0xb822) }, // VID:PID
{ /* end */ },
};
static struct sdio_driver rtl8822cs_sdio_drv = {
.name = "rtl8822cs",
.id_table = rtl8822cs_sdio_ids,
.probe = rtl8822cs_sdio_probe,
.remove = rtl8822cs_sdio_remove,
};
probe 函数里要做的事:
- 使能 SDIO 功能(
sdio_enable_func) - 设置块大小(
sdio_set_block_size) - 申请中断(
sdio_claim_irq) - 分配 ieee80211_hw 结构体
2.5.2 USB 接口
USB 接口的 Wi-Fi 芯片(比如 RTL8811CU)走的是 usb_driver 框架。USB 的好处是即插即用,但延迟比 SDIO 高一些。
USB 驱动需要处理 URB(USB Request Block) 的提交和回调。我调 USB Wi-Fi 时最头疼的是 端点配置——不同芯片的 bulk 端点、interrupt 端点编号不一样,得仔细看 datasheet。
static struct usb_device_id rtl8811cu_table[] = {
{ USB_DEVICE(0x0bda, 0xc811) },
{ /* end */ },
};
static int rtl8811cu_probe(struct usb_interface *intf,
const struct usb_device_id *id) {
// 获取端点描述符
struct usb_host_interface *iface_desc = intf->cur_altsetting;
// 遍历端点,找到 bulk in/out 和 interrupt 端点
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
// 保存端点地址到私有结构体
}
// 注册 mac80211 硬件
}
我曾经踩过的坑:USB Wi-Fi 在 Tina 上经常遇到 usb_submit_urb 返回 -ENODEV。后来发现是 USB 控制器驱动没开 CONFIG_USB_EHCI_HCD。嗯,内核配置检查一遍,问题就没了。
2.6 驱动初始化流程总结
我把整个驱动初始化流程画成了一张图,方便你对照代码看:
嗯,到这里,Wi-Fi 驱动框架的核心脉络就清楚了。从用户空间的 iw 命令,一路穿透到芯片寄存器,中间每一层都有明确的职责。你在 Tina 上做驱动移植时,只要对照这张图,就知道该改哪一层、不该动哪一层。
我个人建议:拿到一个新芯片的驱动源码,先别急着编译。花半小时把 ieee80211_ops 里的回调函数列表过一遍,看看哪些实现了、哪些是空的。空的回调往往就是后面出问题的地方。