一、编译过程
WS73 驱动的编译入口一般是执行 make platform wifi,它本质上会分别进入 WS73 的 platform 和 wifi 驱动目录,然后借用 Linux 内核的外部模块编译机制,类似执行 make -C /path/linux-5.10.y ARCH=arm CROSS_COMPILE=arm-v01c02-linux-musleabi- M=/path/ws73/driver/platform modules。这里 -C /path/linux-5.10.y 表示进入内核源码/构建目录,M=... 表示当前要编译的是外部模块目录。Linux 顶层 Makefile 发现 M 参数后,会按外部模块方式处理这个目录,读取该目录下的 Makefile/Kbuild,再通过 scripts/Makefile.build 根据 obj-m、xxx-objs、obj-y 等规则递归编译各个 .c 文件生成 .o,并把多个 .o 合成为模块主 .o。之后 scripts/Makefile.modpost 会对模块做符号检查、依赖检查、license 检查、CRC/version 处理,并生成 .mod.c、.mod.o、Module.symvers 等中间文件;最后再由模块最终链接阶段把模块主 .o 和 .mod.o 链接成真正的 .ko 文件,例如 plat_soc.ko 或 wifi_soc.ko。所以整体可以理解为:WS73 Makefile 只是组织编译入口和传参,真正的 .c -> .o -> .ko 流程是借用 Linux Kbuild 外部模块机制完成的。
二、加载 plat_soc.ko
1、加载 plat_soc.ko 的主要作用不是立即下载固件,而是初始化WS73的平台资源、电源管理、HCC通信框架和SDIO通道,并为后续固件下载做好准备;
2、如果Linux MMC核心启动时尚未发现WS73,原来的 hisi_sdio_rescan() 会要求对应的 mmc_host 再次扫卡,但现在系统已经生成了对应的 mmc_card/sdio_func,因此可以屏蔽这一步。
3、随后 sdio_register_driver() 根据枚举阶段获得的 vendor=0x12D1、device=0x0073 在Linux设备模型中匹配WS73,这个匹配过程只是软件比较ID,并不会向Wi-Fi硬件发送数据;匹配成功后调用 sdio_probe(),再由 sdio_enable_func() 通过MMC核心和 mmc_host->ops 发送SDIO CMD52命令,使能WS73的Function 1,同时设置块大小、FIFO和SDIO中断。
4、真正的固件下载通常在首次打开WLAN、BLE或SLE业务时触发,Linux先从 /etc/ws73/ws73.bin 等文件读取数据,再经过 firmware_send_func() → hcc_bus_patch_write() → hcc_sdio_patch_write() → oal_sdio_writesb(),通过SDIO Function 1的FIFO把下载命令和固件数据分块写入WS73内部RAM,因此固件下载也是走SDIO,但使用的是启动/补丁下载协议,不是正常网络数据协议。
三、加载wifi_soc.ko
wifi_soc.ko 的作用不是单纯注册 wiphy,而是同时完成 WiFi 控制面和数据面的接入。
1、在控制面上,wifi_soc.ko 会注册 wiphy,并把驱动中的 cfg80211_ops 挂接到 Linux 的 cfg80211 无线管理框架中。这样用户态的 iw、wpa_supplicant、hostapd 等工具就可以通过 nl80211/cfg80211 向 WiFi 驱动下发控制命令,这就会调用wiphy里面的ops。这类控制命令最终会被驱动封装成 WS73 固件能够识别的命令消息,再通过 HCC/SDIO 层调用 mmc_host,使用 SDIO CMD52/CMD53 等方式发送给 WS73 芯片。
2、在数据面上,wifi_soc.ko 还会注册 wlan0 这个 net_device,并绑定 net_device_ops。当应用程序通过 socket 发送 TCP、UDP、ICMP 等网络数据时,数据会先经过 Linux TCP/IP 协议栈,协议栈根据路由选择 wlan0 作为出口,然后调用 wlan0 对应的 net_device_ops->ndo_start_xmit()。驱动在这个发送函数中拿到 skb 数据包,再经过 WAL/HMAC/HCC 等层进行封装,最后通过 SDIO 框架调用 mmc_host,使用 CMD53 把数据写入 WS73 芯片的发送队列或数据窗口中,之后由 WS73 固件和 WiFi MAC/PHY 负责把数据通过无线空口发送出去。
3、当 WS73 向主机发送数据时,流程则是反过来的。WS73 固件收到无线数据包或者产生控制事件后,会先把数据放到芯片内部的 RX FIFO、buffer 或 mailbox 中,然后通过 SDIO 中断通知主机。主机侧的 mmc_host / uSDHC 控制器检测到 SDIO 中断后,会进入 WiFi 驱动的中断处理流程。驱动通常先读取中断状态和数据长度,然后通过 SDIO CMD53 从 WS73 的 RX 数据窗口中把数据读出来。读出的数据再经过 HCC 解包和 WAL/HMAC 分发:如果是网络数据包,就构造成 skb,通过 netif_rx() 或 NAPI 交给 Linux TCP/IP 协议栈。
4、因此,可以总结为:wiphy/cfg80211 主要负责 WiFi 的控制面,wlan0/net_device_ops 主要负责 WiFi 的数据面;无论是控制命令还是网络数据,最终都会经过驱动内部的 WAL/HMAC/HCC 层,并通过 SDIO/mmc_host 与 WS73 固件进行通信。
1. 适配的第一步:确认硬件,不是先编译
拿到 WS73 驱动后,第一件事应查看原理图,确认 WS73 接到了 Hi3516 的哪一路 SDIO/MMC 控制器,例如 mmc0 或 mmc1。需要确认:
- SDIO 的
CLK、CMD、DAT0~DAT3分别接到哪些 SoC 引脚。 - 模块供电电压、IO 电压、
EN、RESET_N、唤醒 GPIO、是否有外部中断。 - CMD 和 DAT0~DAT3 的上拉电阻是否存在、阻值与电源是否正确。
- 设备树中对应 MMC/SDIO host 是否启用,pinmux、总线位宽、最大频率、电压能力是否与原理图一致。
Wi-Fi 模块完成供电、解除复位、引脚复用配置后,Linux 的 MMC/SDIO 框架应能枚举出设备。例如:
/sys/bus/sdio/devices/mmc0:0001:1
| | |
| | `-- SDIO Function 1
| `--------- 卡 RCA,通常为 0001
`-------------- mmc0 主机控制器
只有出现这个 SDIO 设备后,WS73 的 sdio_register_driver() 才有对象可以匹配。若连 mmc0:0001:1 都没有,问题仍在供电、复位、DAT0、引脚复用、设备树或 MMC 控制器枚举阶段,尚未进入 Wi-Fi 驱动。
MMC 相关配置需要按实际 Hi3516 host 驱动确认:
CONFIG_MMC=y
CONFIG_MMC_SDHCI=y // 仅当实际 host 使用 SDHCI 框架时需要
CONFIG_MMC_BLOCK=y // 对 SD 卡块设备有用;SDIO Wi-Fi 本身不依赖它
CONFIG_CFG80211=y // Wi-Fi 控制框架必需
cfg80211 不是把 AT 命令封装给 SDIO 的框架。它负责 Linux 无线控制面,例如 scan、connect、disconnect、设置密钥等;WS73 驱动再把这些请求封装成自身固件协议消息,经 HCC/SDIO 发给芯片。
2. WS73 的编译方式
通常使用:
make wifi_clean platform_clean
make platform wifi
本质是借用 Linux Kbuild 编译外部模块:
WS73 Makefile
|
`-- make -C <linux-kernel> M=<ws73-driver-dir> modules
|
|-- 编译各 .c 文件为 .o
|-- 按 obj-m / xxx-objs 合并模块对象
|-- modpost 检查符号、依赖、许可证、CRC
`-- 链接生成 plat_soc.ko、wifi_soc.ko 等模块
模块必须使用与板端运行内核完全一致的内核源码、.config、内核头文件和 Module.symvers 编译。否则即使能加载,也可能出现符号找不到、struct wireless_dev 布局不一致或运行时崩溃。
3. plat_soc.ko:平台与 SDIO 通道准备
plat_soc.ko 的主要职责不是注册 wlan0,而是准备 WS73 的平台资源、HCC 通信通道和 SDIO Function。
insmod plat_soc.ko
|
|-- 初始化平台资源
| // 电源、GPIO、HCC 总线、SDIO host 相关资源。
|
|-- 必要时触发 SDIO 重扫描
| // 只有尚未枚举到 SDIO 卡时才有意义。
|
|-- sdio_register_driver()
| // 注册 WS73 的 SDIO 驱动及 vendor/device ID 表。
|
`-- sdio_probe()
|
|-- 匹配 vendor=0x12D1、device=0x0073
| // 只是 Linux 设备模型的软件 ID 比较。
|
|-- sdio_enable_func()
| // 经 MMC host 发 CMD52,使能 Function 1。
|
|-- 设置 block size、FIFO、SDIO 中断
`-- 建立 HCC/SDIO 通信通道
如果系统已经有 mmc0:0001:1,把旧的 hisi_sdio_rescan() 改成空操作可以绕过未导出符号问题;但这只是"设备已经成功枚举"的前提下的兼容处理。若 SDIO 设备根本没有枚举,不能靠屏蔽扫描函数解决硬件问题。
固件下载一般发生在首次启动 WLAN、BLE 或 SLE 业务时,具体时机以 WS73 源码为准。下载路径通常类似:
读取 /etc/ws73/*.bin
|
v
firmware_send_func()
|
v
hcc_bus_patch_write()
|
v
hcc_sdio_patch_write()
|
v
oal_sdio_writesb()
|
`-- SDIO Function 1 FIFO
// 分块写入启动命令、补丁和固件到 WS73 内部 RAM。
固件下载也走 SDIO,但属于启动/补丁协议,不是普通网络收发协议。
4. wifi_soc.ko:控制面与数据面
wifi_soc.ko 同时接入无线控制面和网络数据面。
wifi_soc.ko
|
|-- 创建 wiphy
| |
| |-- 注册 cfg80211_ops
| | // scan、connect、disconnect、set_key 等控制入口。
| |
| `-- 注册到 cfg80211
|
|-- 创建 net_device:wlan0
| |
| |-- 注册 netdev_ops
| | // ndo_open、ndo_stop、ndo_start_xmit 等网络收发入口。
| |
| `-- 注册到 Linux 网络协议栈
|
`-- 创建 wireless_dev:wdev
|
|-- wdev->wiphy = wiphy
|-- wdev->netdev = wlan0
|-- wdev->iftype = NL80211_IFTYPE_STATION
`-- wlan0->ieee80211_ptr = wdev
// 将 wlan0 与 cfg80211 无线对象关联。
控制面路径:
wpa_supplicant / iw
|
v
nl80211
|
v
cfg80211
|
v
wiphy->cfg80211_ops
|
v
WS73 WAL/HMAC/HCC
|
`-- SDIO CMD52/CMD53 -> WS73 固件
数据面发送路径:
应用程序 socket
|
v
Linux TCP/IP 协议栈
|
v
wlan0->netdev_ops->ndo_start_xmit()
|
v
WS73 WAL/HMAC/HCC
|
v
SDIO CMD53
|
`-- WS73 TX FIFO/队列 -> Wi-Fi MAC/PHY -> 无线空口
接收路径:
WS73 收到无线数据或控制事件
|
|-- 写入内部 RX FIFO / mailbox
`-- SDIO 中断通知主机
|
|-- 驱动读取状态与数据长度
|-- CMD53 读取 RX 数据
|-- HCC/WAL/HMAC 解包
`-- netif_rx() / NAPI -> Linux TCP/IP 协议栈
5. wiphy、wlan0 与 wdev 的关系
wdev 不是驱动私有数据,而是驱动与 cfg80211 共用的无线对象。真正的私有数据通常在:
wiphy_priv(wiphy);
netdev_priv(net_dev);
如果开启:
CONFIG_CFG80211_WEXT=y
cfg80211 可能在 wlan0 被拉起时进入 WEXT 兼容流程,并访问 wdev->wext 字段。若 WS73 模块与当前内核的 struct wireless_dev 布局不一致,或者驱动没有使用当前内核要求的初始化流程,便可能访问异常内存并崩溃。
因此要点是:
CONFIG_CFG80211是现代nl80211/wpa_supplicant的基础。CONFIG_CFG80211_WEXT是旧 WEXT 兼容层,不应因为"需要 Wi-Fi"而默认开启。- 若 WS73 依赖 WEXT 符号,则需保证内核配置、模块编译环境和
wdev初始化方式完全一致。 - "关闭 WEXT 不崩溃"只能说明问题位于 WEXT 兼容路径,仍应检查
wdev的分配、初始化和内核 ABI 一致性。
6. SDIO 枚举失败:error -16 的含义
以下日志说明问题发生在 Wi-Fi 驱动加载前:
CMD5 成功
CMD3 成功
CMD7 成功
card_busy timeout opcode=52 arg=0x00000000
sdio_read_cccr err=-16
mmc_sdio_init_card err=-16
-16 是 -EBUSY,不是 CMD5 无响应超时。日志中的第一次 CMD5 err=-110 是单独的超时现象;后面 CMD5 已成功并不代表 SDIO 数据线已经验证成功。
opcode=52 是 CMD52,arg=0x00000000 表示:
CMD52 read
Function 0
address = 0x00000
读取 CCCR_CCCR
也就是在读取 SDIO 公共控制寄存器 CCCR 的第一步失败,尚未访问 Wi-Fi Function 1,也尚未进入 WS73 的 sdio_probe()。
更准确地说,失败发生在 host 准备发 CMD52 前检查到 card busy 一直未释放。主机侧通常等价于认为 DAT0 仍处于低电平 busy 状态:
CMD5/CMD3/CMD7 成功
|
// 主要证明 CMD + CLK 通路基本正常
v
首次 CMD52 前 host 检查 card busy
|
`-- DAT0 长时间为低 / host busy 状态异常
|
`-- 返回 -EBUSY(-16)
重点排查:
DAT0是否被模块持续拉低。- DAT0 上拉电阻、电源、焊接、卡座/模组连接是否正常。
- CMD7 后模块是否进入异常状态。
- SDIO IO 电压与模块要求是否一致。
- SoC pinmux、DAT0 输入状态、host 的
card_busy()极性或寄存器判断是否正确。 - 模块是否真的完成上电、解除复位和时钟稳定。
因为已测试过降频、1-bit、关闭 high-speed 和 1.8V 仍失败,问题更偏向 DAT0、电源、复位、引脚复用或 host busy 判断,而不是高速模式协商。
建议使用示波器或逻辑分析仪查看 CMD7 成功后的总线:
空闲状态通常应为:
CMD、DAT0、DAT1、DAT2、DAT3:高电平
CLK:由 host 驱动,可能停止或保持固定状态
重点:
CMD7 后 DAT0 是否一直低。