Hi3516 + WS73 SDIO Wi-Fi 适配笔记

一、编译过程

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-mxxx-objsobj-y 等规则递归编译各个 .c 文件生成 .o,并把多个 .o 合成为模块主 .o。之后 scripts/Makefile.modpost 会对模块做符号检查、依赖检查、license 检查、CRC/version 处理,并生成 .mod.c.mod.oModule.symvers 等中间文件;最后再由模块最终链接阶段把模块主 .o.mod.o 链接成真正的 .ko 文件,例如 plat_soc.kowifi_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 无线管理框架中。这样用户态的 iwwpa_supplicanthostapd 等工具就可以通过 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 控制器,例如 mmc0mmc1。需要确认:

  • SDIO 的 CLKCMDDAT0~DAT3 分别接到哪些 SoC 引脚。
  • 模块供电电压、IO 电压、ENRESET_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 是否一直低。
相关推荐
CoderYanger1 小时前
视频切割脚本(Python版)
linux·开发语言·windows·后端·python·程序人生·职场和发展
辞旧 lekkk1 小时前
CMake工程指南(一)
linux·运维·服务器·开发语言·qt·学习·萌新
Wang's Blog1 小时前
Java框架快速入门:深入MyBatis-Plus高级DML操作(ID策略·逻辑删除·乐观锁)
java·数据库·mybatis
小七在进步2 小时前
数据结构:用栈实现队列
开发语言·数据结构
KaMeidebaby2 小时前
卡梅德生物技术快报|噬菌体筛选:噬菌体筛选工程化优化方案:基于侵染全链条参数调控与水环境应用验证
java·开发语言·人工智能·机器学习·spark
geovindu2 小时前
go: Floyd-Warshall Algorithms
开发语言·后端·算法·golang
CoderYanger2 小时前
视频裁剪+缩放+自动添加水印脚本(Python版)
开发语言·后端·python·程序人生·职场和发展·音视频·学习方法
酷酷的身影2 小时前
Managers/APConfigManager.cs
开发语言·ui·c#
拳里剑气2 小时前
C++算法:优先级队列
开发语言·c++·算法·优先级队列