绿联 AX300(AIC8800DC)在 OpenWrt Linux 6.6 / 鲁班猫 2 上的驱动移植——软路由搭建指南
绿联 AX300(AIC8800DC)在 OpenWrt Linux 6.6 / 鲁班猫 2 上的驱动移植——软路由搭建指南
关键词:绿联 AX300、UGREEN CM760、AIC8800DC、AIC8800DC OpenWrt 驱动、Linux 6.6、OpenWrt 24.10、鲁班猫 2、RK3568、USB Wi-Fi、Wi-Fi 6、cfg80211、mac80211、usbmode
笔者有一个闲置的usb网卡,因此想到和鲁班猫2 rk3568开发板组合,刷入OpenWrt系统制作一个软路由。
本文记录将绿联 AX300 USB 无线网卡移植到鲁班猫 2 OpenWrt 24.10(Linux 6.6)上的完整过程。最终成果不是一个可跨设备直接安装的“通用 ARM64 IPK”,而是一套可以放进目标 OpenWrt 源码树、随目标内核重新编译的驱动包、兼容补丁和启动恢复机制。
本文使用的适配源码已经公开:
- OpenWrt 包与补丁:https://github.com/kasonhaimen/openwrt-aic8800dc
- 驱动上游:https://github.com/For-ACGN/AIC8800DC
- 驱动固定提交:
b4e4a49137f08eab403770d323274fd95514c830 - 鲁班猫 OpenWrt 源码:https://github.com/LubanCat/LubanCatWrt,
v24.10分支
文章目录
- 绿联 AX300(AIC8800DC)在 OpenWrt Linux 6.6 / 鲁班猫 2 上的驱动移植——软路由搭建指南
1. 实验环境和最终验证结果
本次实际验证环境如下:
| 项目 | 环境 |
|---|---|
| 开发板 | EmbedFire LubanCat 2,RK3568,ARM64 |
| OpenWrt | LubanCatWrt / OpenWrt 24.10,提交 5f736f2 |
| Linux(构建机和目标机保持一致,我的构建机用了Windows的wsl2 ubuntu24.04) | 6.6.79 |
| 工具链 | GCC 13.3.0、musl 1.2.5 |
| 无线网卡 | 绿联 CM760 / AX300,AIC8800DC |
| USB ID | 初始 a69c:5722,切换后 a69c:88de |
| 驱动模块 | aic_load_fw.ko、aic8800_fdrv.ko |
| 最终驱动包 | kmod-aic8800dc_6.6.79.2025.03.03-r7_aarch64_generic.ipk |
实际完成了固件加载、phy0/wlan0 注册、2.4 GHz AP、HE20(802.11ax)、WPA2 关联和持续流量验证。
需要先明确:内核模块和 OpenWrt 的 kernel ABI、内核配置以及 cfg80211/mac80211 backport 紧密绑定。即使另一台设备也是 ARM64,也应在它自己的 SDK 或完整源码树内重新编译本包,不能把上述 IPK 当作通用包强制安装。
2. 识别网卡:为什么插上后只看到一个“光驱”
在运行中的 OpenWrt 路由器 SSH/串口终端中输入:
lsusb
上述命令的终端输出最初类似如下(这是观察结果,不要输入):
Bus 006 Device 002: ID a69c:5722 aicsemi Aic MSC
MSC 表示 Mass Storage Class。该网卡上电后先以虚拟光驱/存储设备出现,这是因为 AX300 产品中还包括放置 Windows 驱动的存储区域。模式切换成功后,再次在终端执行 lsusb,应从终端输出中看到(不要输入这一行):
Bus 005 Device 003: ID a69c:88de AICSemi AIC8800DC
因此只移植内核驱动还不够,系统必须同时具备 USB 模式切换工具和对应规则。
这里需要区分两个名称:
- OpenWrt 软件包名是
usb-modeswitch; - 该软件包安装的实际可执行程序是
/sbin/usbmode。
也就是说,在 OpenWrt 中通常执行 opkg install usb-modeswitch,而不是寻找一个名为 usbmode 的 opkg 软件包。OpenWrt 使用自己的 usbmode 程序和 /etc/usb-mode.json 数据库,并不是桌面 Linux 发行版常见的 usb_modeswitch 命令及其配置目录。
2.1 方法一:编译固件时内置(本文推荐)
在 OpenWrt 源码根目录的 Linux/WSL 终端执行 make menuconfig,然后在菜单中依次进入并选中(以下是菜单路径,不是终端命令):
Utilities --->
<*> usb-modeswitch
如果使用可复现的 seed 配置,则编辑项目文件 configs/bypass-router.seed,在软件包配置区域加入以下一行;若不用 seed,也可在 OpenWrt 源码根目录的 .config 中加入,但下一次 make defconfig 可能重排该文件:
CONFIG_PACKAGE_usb-modeswitch=y
然后在 OpenWrt 源码树(不是运行中路由器的根文件系统)创建以下文件;它位于 usbmode 包的 data 目录,文件名就是初始 VID-PID:
package/utils/usbmode/data/a69c-5722
新建文件 package/utils/usbmode/data/a69c-5722,从文件开头写入以下全部内容(该文件只有这些内容):
# UGREEN CM760 AX300 (AICSemi AIC8800DC) virtual CD-ROM mode
TargetVendor=0xa69c
TargetProduct=0x88de
StandardEject=1
WaitBefore=1
这个文本文件是构建输入。编译 usb-modeswitch 时,OpenWrt 的 convert-modeswitch.pl 会把 package/utils/usbmode/data/ 中的规则合并并转换为固件里的下列运行时文件路径(这是路径说明,不要输入):
/etc/usb-mode.json
因此不能简单地在运行中的路由器上创建 /package/utils/usbmode/data/a69c-5722;运行时根本不会读取这个源码目录。
保存规则后,在 Linux/WSL 构建机终端中进入 OpenWrt 源码根目录并输入:
cd openwrt
make defconfig
make package/utils/usbmode/clean
make package/utils/usbmode/compile V=sc -j1
上述单包编译成功后,如需生成整机固件,继续在同一个 OpenWrt 源码根目录终端输入:
make V=sc -j"$(nproc)"
将固件写入路由器并启动后,在 OpenWrt SSH/串口终端输入以下检查命令;每条命令都应列出对应文件:
ls -l /sbin/usbmode
ls -l /etc/usb-mode.json
ls -l /etc/init.d/usbmode
2.2 方法二:在已经运行的 OpenWrt 中安装
如果正在运行的 OpenWrt 24.10 能联网,在它的 SSH/串口终端输入:
opkg update
opkg install usb-modeswitch usbutils
如果目标系统明确使用 apk 而不是 opkg,才在该 OpenWrt 终端改用:
apk update
apk add usb-modeswitch usbutils
但是,只安装官方 usb-modeswitch 包不一定包含 a69c:5722 这条新增规则。本文的稳定做法是把规则放进源码树,重新编译包含该规则的 usb-modeswitch IPK 或整个固件,再安装生成的自定义 IPK。
不建议直接手工编辑 /etc/usb-mode.json 作为长期方案:它是构建过程中自动生成的文件,重新安装或升级软件包后可能被覆盖。手工 JSON 更适合临时诊断,格式和命令可参考 OpenWrt 官方 USB mode switch 文档。
2.3 usbmode 如何自动工作
安装完成后,下列内容是路由器根文件系统中应出现的文件路径(用于理解和检查,不是一次性粘贴执行的命令):
/sbin/usbmode
/etc/usb-mode.json
/etc/init.d/usbmode
/etc/hotplug.d/usb/20-usb_mode
/etc/init.d/usbmode 文件的 start_service() 函数会让 procd 启动下列命令;这是启动机制说明,通常不需要读者手工输入:
/sbin/usbmode -s
USB 热插拔时,hotplug 脚本会再次启动 usbmode 服务。因此规则已经进入 /etc/usb-mode.json 后,正常使用方式就是插入网卡,不需要每次手工运行切换命令。
在运行中的 OpenWrt SSH/串口终端按顺序输入以下命令。以 # 开头的行是说明性注释,可以随命令一起粘贴,也可以跳过:
# 确认软件已安装
opkg list-installed | grep usb-modeswitch
# 查看 usbmode 识别到的 USB 设备
/sbin/usbmode -l
# 确认数据库中包含初始 VID:PID
grep -i 'a69c:5722' /etc/usb-mode.json
# 插入网卡后观察初始和目标 ID
lsusb
logread -f
# 必要时手工触发一次系统扫描
/sbin/usbmode -s
# 或通过服务触发
/etc/init.d/usbmode restart
# 切换成功后应看到
lsusb | grep -Ei 'a69c:88de|AIC8800DC'
如果网卡始终停留在 a69c:5722,在 OpenWrt SSH/串口终端依次输入:
test -x /sbin/usbmode && echo usbmode-ok
test -f /etc/usb-mode.json && echo database-ok
grep -i 'a69c:5722' /etc/usb-mode.json
logread | grep -Ei 'usbmode|a69c|usb'
dmesg | grep -Ei 'a69c|usb-storage|CD-ROM|AIC'
若数据库中没有该 ID,说明自定义 data 文件没有进入本次 usb-modeswitch 构建产物,应清理并重新编译该包,而不是继续排查 AIC 内核模块。
2.4 参考资料
- OpenWrt 官方 USB mode switch 使用说明:介绍
/etc/usb-mode.json、usbmode -l、usbmode -s和自定义 JSON 调试方法。 - OpenWrt 的 usbmode 软件包 Makefile:可以看到 data 文件如何经
convert-modeswitch.pl转换成/etc/usb-mode.json,以及 init/hotplug 文件的安装位置。 - OpenWrt usbmode 项目源码:OpenWrt 所用 USB mode-switching 程序的上游源码。
- OpenWrt 软件包信息:usb-modeswitch:软件包名称、依赖、架构和许可证信息。
用于构建本文固件的项目文件 configs/bypass-router.seed 应在软件包配置区域包含以下两行;如果直接维护 OpenWrt .config,则检查其中有相同配置:
CONFIG_PACKAGE_usbutils=y
CONFIG_PACKAGE_usb-modeswitch=y
这里容易混淆两个概念:此处的切换是 USB 设备内部工作模式的切换,usbmode 负责把设备从 5722 切换到 88de;网络默认路由、旁路由网关和流量转发则属于 netifd、UCI 与防火墙的工作,不能靠 USB 驱动完成。
3. 获取 OpenWrt 和 AIC8800DC 源码
3.1 获取鲁班猫 2 的 OpenWrt 24.10 源码
在 Linux/WSL 构建机终端中,先切换到准备存放源码的、区分大小写的 Linux 文件系统目录,然后输入:
git clone --branch v24.10 --single-branch \
https://github.com/LubanCat/LubanCatWrt.git openwrt
cd openwrt
完成克隆后,当前终端已经位于 openwrt 源码根目录;输入以下命令切换到本文验证过的提交:
git checkout 5f736f2
OpenWrt 必须在区分大小写的 Linux 文件系统中编译。WSL2 用户不要直接在 /mnt/c 或 /mnt/d 下构建,应将工程复制到 WSL 的 ext4 文件系统,例如 ~/openwrt。
如果 WSL 自动把 Windows PATH 拼进 Linux PATH,包含空格和括号的路径可能进入 U-Boot 等递归 Makefile。构建前在当前 Linux/WSL 终端输入以下命令;它只修改当前 shell 的 PATH:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
3.2 获取已经整理好的 OpenWrt 驱动包
回到同时包含(或准备包含)openwrt/ 目录的上一级 Linux/WSL 工作目录,在终端输入以下命令。命令会克隆配套仓库,并把驱动包与 usbmode 规则复制到 OpenWrt 源码树:
git clone https://github.com/kasonhaimen/openwrt-aic8800dc.git
cp -a openwrt-aic8800dc/package/kernel/aic8800dc \
openwrt/package/kernel/
cp openwrt-aic8800dc/package/utils/usbmode/data/a69c-5722 \
openwrt/package/utils/usbmode/data/
配套仓库文件 package/kernel/aic8800dc/Makefile 的开头、PKG_NAME/PKG_VERSION/PKG_RELEASE 之后是源码定义。读者应在该位置检查或写入以下变量,而不是在终端输入:
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/For-ACGN/AIC8800DC.git
PKG_SOURCE_VERSION:=b4e4a49137f08eab403770d323274fd95514c830
PKG_MIRROR_HASH:=ad91b1715892d7e1b1c104ab9e26dbbc6802ed392aa93cbfb2b1ec8d68028c9d
固定 commit 和 mirror hash 很重要:它保证其他人获取的是同一份驱动与固件,而不是上游后来发生变化的 main 分支。
4. OpenWrt 内核包的核心写法
编辑 package/kernel/aic8800dc/Makefile:在 include $(INCLUDE_DIR)/package.mk 之后的包定义区域加入以下完整 KernelPackage/aic8800dc 段;配套仓库已经包含,无需重复添加:
define KernelPackage/aic8800dc
SUBMENU:=Wireless Drivers
TITLE:=AIC8800DC/FC USB Wi-Fi driver
DEPENDS:=@USB_SUPPORT +@DRIVER_11AX_SUPPORT +kmod-cfg80211 +kmod-mac80211 +kmod-usb-core
FILES:= \
$(PKG_BUILD_DIR)/drivers/aic8800/aic_load_fw/aic_load_fw.ko \
$(PKG_BUILD_DIR)/drivers/aic8800/aic8800_fdrv/aic8800_fdrv.ko
AUTOLOAD:=$(call AutoLoad,50,aic_load_fw aic8800_fdrv)
endef
OpenWrt 的无线栈使用 backports,不能仅引用内核源码原生头文件。编辑 package/kernel/aic8800dc/Makefile,在 KernelPackage/aic8800dc/description 结束之后、Build/Compile 之前加入以下 NOSTDINC_FLAGS 段:
NOSTDINC_FLAGS := \
$(KERNEL_NOSTDINC_FLAGS) \
-I$(PKG_BUILD_DIR)/drivers/aic8800 \
-I$(STAGING_DIR)/usr/include/mac80211-backport \
-I$(STAGING_DIR)/usr/include/mac80211-backport/uapi \
-I$(STAGING_DIR)/usr/include/mac80211 \
-I$(STAGING_DIR)/usr/include/mac80211/uapi \
-include backport/backport.h
继续编辑同一个 package/kernel/aic8800dc/Makefile,紧接 NOSTDINC_FLAGS 段之后定义/替换 Build/Compile,内容如下:
define Build/Compile
+$(KERNEL_MAKE) $(PKG_JOBS) \
$(KERNEL_MAKE_FLAGS) \
M="$(PKG_BUILD_DIR)/drivers/aic8800" \
KBUILD_EXTRA_SYMBOLS="$(LINUX_DIR)/../symvers/mac80211.symvers" \
NOSTDINC_FLAGS="$(NOSTDINC_FLAGS)" \
CONFIG_PLATFORM_UBUNTU=n \
modules
endef
这解决的是“在 OpenWrt 的无线 backport 环境里编译”,不是简单地“让 gcc 找到一个 cfg80211.h”。如果混用了内核原生头文件和 OpenWrt backport 头文件,可能编译通过一部分,最后却出现结构体、函数原型或模块符号版本冲突。
5. Linux 6.6 / OpenWrt backport 的主要报错与原因
5.1 为什么代码判断了 Linux 版本,仍然会报 API 不匹配
最关键的认识是:OpenWrt 24.10 虽然运行 Linux 6.6,但其 cfg80211/mac80211 API 来自更新内核的 backport。驱动中只用 LINUX_VERSION_CODE 判断函数签名并不可靠。
在 Linux/WSL 构建机终端执行 make package/kernel/aic8800dc/compile V=sc -j1 时,可能从终端构建日志中看到以下类型的错误(这些是示例输出,不要输入):
error: too few arguments to function 'cfg80211_ch_switch_notify'
error: too many arguments to function 'cfg80211_ch_switch_started_notify'
error: initialization of ... from incompatible pointer type
error: 'struct cfg80211_ops' ... incompatible pointer type for 'change_beacon'
error: too few arguments to function 'cfg80211_cac_event'
原因不是 ARM64 或 RK3568 本身,而是驱动假设的 cfg80211 API 与 OpenWrt 实际编译头文件不一致。
5.2 方案一:不修改源码版本,直接使用配套补丁编译(推荐)
适用人群:使用本文指定的 AIC8800DC 上游提交、OpenWrt 24.10/LubanCat2基线,只想复现实验并生成驱动或固件的读者。
如果你的硬件环境与本文一致或接近(本文使用鲁班猫2 RK3568开发板),请优先使用配套仓库中的补丁。 后文展示每一处 C 代码修改,是为了说明报错原因和方便排查,并不意味着读者必须逐行编辑上游源码。逐行手改容易漏掉关联修改,也不利于升级、清理和重复构建。
OpenWrt 会在准备驱动源码时,按照文件名顺序自动应用包目录 patches/ 下的补丁。本文配套补丁位于:
openwrt-aic8800dc/package/kernel/aic8800dc/patches/
上面是配套仓库中的目录路径,用于确认文件位置,不是终端命令。当前共有 001~012 十二个补丁;Linux 6.6 和 OpenWrt cfg80211 backport 适配主要集中在 006、010、011 和 012。
如果已经按照第 3.2 节复制了整个 aic8800dc 包,那么补丁也已经一起复制,无需再执行其他 patch 命令。可以在 Linux/WSL 构建机终端进入 OpenWrt 源码根目录,然后输入以下命令确认:
ls package/kernel/aic8800dc/patches/
终端应列出 001-...patch 到 012-...patch。OpenWrt 在准备软件包源码时会按照文件名顺序自动应用补丁。确认后,在同一个 OpenWrt 源码根目录终端输入以下命令,清除旧的解包结果并重新编译:
make package/kernel/aic8800dc/clean
make package/kernel/aic8800dc/compile V=sc -j1
执行 clean 很重要:如果驱动源码此前已经在 build_dir 中解包,直接再次编译可能继续使用旧源码,让人误以为新补丁没有生效。重新编译时,OpenWrt 会重新下载/解包固定版本的上游源码,并自动按顺序应用 patches/ 目录中的文件。
如果构建日志出现类似下面的内容,说明某个补丁没有成功应用;这是构建终端中的错误输出示例,不要输入:
Patch failed! Please fix ...
Hunk #1 FAILED at ...
这通常表示目标驱动版本或 OpenWrt API 已经与本文基线不同。此时不要使用 --force 跳过失败补丁,而应检查:
PKG_SOURCE_VERSION是否仍为本文固定的上游提交;- 是否误用了其他版本的 AIC8800DC 源码;
- 目标 OpenWrt 的 cfg80211/mac80211 backport 是否已经更新;
- 失败补丁修改的函数在目标源码中是否更名或改变参数。
对于本方案,到这里即可跳转到第 7 章的完整构建流程。后面的第 5.3 节属于源码移植原理和手工修改参考,使用本文固定版本及配套补丁时可以跳过。
5.3 方案二:更换源码或 OpenWrt 版本时的代码修改参考
适用人群:准备使用不同 AIC8800DC 驱动提交、不同 OpenWrt 分支或不同 cfg80211/mac80211 backport,需要重新制作或调整补丁的开发者。
本节解释现有补丁具体修改了什么。这里展示的代码不是终端命令;使用第 5.2 节固定源码和现成补丁的读者无需再次手工修改。
如果需要在另一份已经解压的上游驱动源码中测试现有补丁,应先进入那份上游源码的根目录,再在 Linux/WSL 终端输入下面的命令;路径请按实际 OpenWrt 工程位置调整:
patch -p1 --dry-run < /path/to/openwrt/package/kernel/aic8800dc/patches/010-build-against-openwrt-cfg80211-backport.patch
patch -p1 < /path/to/openwrt/package/kernel/aic8800dc/patches/010-build-against-openwrt-cfg80211-backport.patch
第一条只检查补丁能否应用,不修改文件;只有 --dry-run 没有报错时才执行第二条。正常使用第 5.2 节的 OpenWrt 包构建方式时不需要执行这两条命令。
5.3.1 适配 cfg80211 信道切换、Beacon 和雷达接口
这里需要修改两个“信道切换通知”函数的参数,使它们与当前 OpenWrt 的 cfg80211 接口一致:
cfg80211_ch_switch_notify():通知系统“信道切换已经完成”;cfg80211_ch_switch_started_notify():通知系统“信道切换已经开始”。
编辑驱动源码文件 drivers/aic8800/aic8800_fdrv/rwnx_main.c,分别找到上述两个函数的调用位置,改成下面的形式。本文提供的 OpenWrt 驱动包已经通过 patches/010-build-against-openwrt-cfg80211-backport.patch 自动完成这项修改,因此使用配套驱动包的读者只需理解代码,不需要在终端输入或再次手工修改:
cfg80211_ch_switch_notify(vif->ndev, &csa->chandef, 0);
cfg80211_ch_switch_started_notify(dev, &csa->chandef, 0,
params->count, params->block_tx);
同一上游文件 drivers/aic8800/aic8800_fdrv/rwnx_main.c 的 rwnx_cfg80211_change_beacon() 定义处需要使用 backport 提供的 cfg80211_ap_update。下列代码由补丁 010-build-against-openwrt-cfg80211-backport.patch 替换原函数签名和构建 beacon 的调用:
static int rwnx_cfg80211_change_beacon(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_ap_update *info)
{
/* ... */
buf = rwnx_build_bcn(bcn, &info->beacon);
/* ... */
}
在 drivers/aic8800/aic8800_fdrv/rwnx_main.c 中找到 rwnx_cfg80211_set_monitor_channel() 函数定义,在参数列表中增加 net_device 参数;配套包通过补丁 010-build-against-openwrt-cfg80211-backport.patch 完成:
static int rwnx_cfg80211_set_monitor_channel(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_chan_def *chandef)
在 drivers/aic8800/aic8800_fdrv/rwnx_main.c 中找到 rwnx_cfg80211_start_radar_detection() 的函数定义,在参数列表末尾增加 link_id;该修改位于补丁 010-build-against-openwrt-cfg80211-backport.patch:
int rwnx_cfg80211_start_radar_detection(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_chan_def *chandef,
u32 cac_time_ms,
int link_id)
在上游文件 drivers/aic8800/aic8800_fdrv/rwnx_radar.c 的 rwnx_radar_cac_work() 和 rwnx_radar_cancel_cac() 附近,给 cfg80211_cac_event() 调用补充最后一个 link ID 参数;配套包由 patches/011-add-cac-link-id-for-cfg80211-backport.patch 修改:
cfg80211_cac_event(ndev, &ctxt->chan_def,
NL80211_RADAR_CAC_FINISHED, GFP_KERNEL, 0);
5.3.2 网络设备注册必须经过 cfg80211
在未修改的上游文件 drivers/aic8800/aic8800_fdrv/rwnx_main.c 中,接口创建和删除函数附近原本能看到以下调用(这是原代码摘录,不要在终端输入):
register_netdevice(ndev);
unregister_netdevice(dev);
在同一个 rwnx_main.c 的相同位置,应把上述两处分别替换为下列调用;配套包通过 patches/012-use-cfg80211-netdevice-registration.patch 自动完成:
cfg80211_register_netdevice(ndev);
cfg80211_unregister_netdevice(dev);
这使 netdev 的注册/注销与 cfg80211 的锁和对象生命周期一致。本项目 r3 真机运行时,在鲁班猫 2 串口终端和 dmesg 中曾看到以下 ARM64 Oops;原始记录保存在项目文件 artifacts/r3首次开机.txt 中(这是日志输出,不要输入):
Internal error: Oops: 0000000096000006 [#1] SMP
pc : rwnx_cfg80211_probe_client+0xf00/0x2988 [aic8800_fdrv]
lr : cfg80211_scan+0x180/0x1b8 [cfg80211]
仅凭一次栈回溯不能把所有责任归结为同一行代码,但它明确说明“模块能加载”不等于驱动已经正确适配 cfg80211 生命周期。后续版本加入 backport API 修正和 cfg80211 netdevice 注册后,继续进行扫描、AP 和持续流量测试。
5.3.3 内核 6.x 的写法与 GCC 13 警告
GCC 13 和内核构建选项会把部分警告当成错误。对应修正包括:
在上游文件 drivers/aic8800/aic8800_fdrv/rwnx_tx.c 的 rwnx_select_txq() switch 中,用 fallthrough; 明确标记 AP_VLAN 分支穿透;配套包由 patches/004-mark-ap-vlan-fallthrough.patch 修改:
case NL80211_IFTYPE_AP_VLAN:
rwnx_vif = rwnx_vif->ap_vlan.master;
fallthrough;
case NL80211_IFTYPE_AP:
在 drivers/aic8800/aic8800_fdrv/rwnx_main.c 的 rwnx_interface_add() 设置接口 MAC 的位置,用内核接口替换对 ndev->dev_addr 的直接 memcpy;该代码来自 patches/006-update-main-for-linux-6.x.patch:
eth_hw_addr_set(ndev, params->macaddr);
在 drivers/aic8800/aic8800_fdrv/rwnx_msg_rx.c 的 rwnx_rx_sm_disconnect_ind() 局部变量声明区,将 macaddr 声明改为以下类型;该修改位于 patches/002-use-const-netdev-address.patch:
const u8 *macaddr;
在 drivers/aic8800/aic8800_fdrv/rwnx_rx.c 的 rwnx_rx_add_rtap_hdr() 中,替换向 radiotap HE 区域写入数据的原 memcpy;该修改位于 patches/003-annotate-variable-radiotap-buffer-copy.patch:
unsafe_memcpy(pos, &he, sizeof(he),
"radiotap header is a variable-length skb buffer");
在 drivers/aic8800/aic8800_fdrv/rwnx_debugfs.c 的 rwnx_dbgfs_rc_fixed_rate_idx_write() 中,替换解析父目录 MAC 地址的原 sscanf 和无效的 mac == NULL 判断;该修改位于 patches/008-validate-debugfs-mac-parse.patch:
if (sscanf(name, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&mac[0], &mac[1], &mac[2],
&mac[3], &mac[4], &mac[5]) != ETH_ALEN)
return 0;
此外,在 drivers/aic8800/aic8800_fdrv/rwnx_main.c 的 rwnx_csa_finish() 末尾,原代码在完成路径调用了 wiphy_lock(),但上下文实际需要释放锁。该行由 patches/010-build-against-openwrt-cfg80211-backport.patch 改为:
wiphy_unlock(rwnx_hw->wiphy);
此类修改应结合目标头文件和调用上下文审查,不能机械地按内核版本号替换。
6. 驱动加载了,为什么仍然找不到固件
第一次驱动探测时,在鲁班猫 2 串口/SSH 终端执行 dmesg 或 logread 能看到以下关键输出;原始记录位于项目文件 artifacts/网卡检查.txt(这是日志,不要输入):
rwnx_load_firmware: firmware path = /vendor/etc/firmware/aic8800DC/fmacfw_patch_8800dc_u02.bin
rwnx_load_firmware: fmacfw_patch_8800dc_u02.bin file failed to open
usb_err:<aicwf_usb_probe,...>: failed with errno -1
aic8800_fdrv: probe ... failed with error -1
而安装 IPK 或刷入固件后,固件应出现在路由器根文件系统的下列目录(这是路径说明,不要输入):
/lib/firmware/aic8800DC/
调试阶段为了验证“仅仅是路径错误”,曾在运行中的鲁班猫 2 OpenWrt 终端输入以下临时命令。它只是诊断手段,正式固件应修改源码,不应长期依赖该软链接:
mkdir -p /vendor/etc
ln -s /lib/firmware /vendor/etc/firmware
软链接后固件能够继续加载,证明问题是硬编码路径,而不是固件文件缺失或 USB 传输失败。正式修改位于上游文件 drivers/aic8800/aic_load_fw/aicbluetooth.c 靠近全局默认固件路径定义处;配套包由 patches/009-use-openwrt-firmware-path.patch 替换为:
#if defined(CONFIG_PLATFORM_UBUNTU)
static const char *aic_default_fw_path = "/lib/firmware/";
#else
static const char *aic_default_fw_path = "/lib/firmware";
#endif
编辑 package/kernel/aic8800dc/Makefile,在文件末尾、$(eval $(call KernelPackage,aic8800dc)) 之前的 KernelPackage/aic8800dc/install 定义中加入固件复制逻辑:
define KernelPackage/aic8800dc/install
$(INSTALL_DIR) $(1)/lib/firmware/aic8800DC
$(CP) $(PKG_BUILD_DIR)/fw/aic8800DC/* \
$(1)/lib/firmware/aic8800DC/
endef
刷入新固件或安装新 IPK、重启并插入网卡后,在鲁班猫 2 的 OpenWrt SSH/串口终端输入以下命令验证:
find /lib/firmware/aic8800DC -type f
dmesg | grep -Ei 'aic|8800|firmware|cfg80211|wlan'
lsmod | grep -Ei 'aic|cfg80211|mac80211'
iw phy
ip link show
7. 编译配置与构建步骤
编辑项目文件 configs/bypass-router.seed,在目标设备和软件包配置区域检查或加入以下配置;如果不使用项目 seed,则在 OpenWrt 源码根目录 .config 中确保 make defconfig 后能看到相同内容:
CONFIG_TARGET_rockchip=y
CONFIG_TARGET_rockchip_armv8=y
CONFIG_TARGET_DEVICE_rockchip_armv8_DEVICE_embedfire_lubancat-2=y
CONFIG_PACKAGE_usbutils=y
CONFIG_PACKAGE_usb-modeswitch=y
CONFIG_PACKAGE_kmod-aic8800dc=y
CONFIG_PACKAGE_wpad-openssl=y
CONFIG_DRIVER_11AX_SUPPORT=y
不要同时保留多个 wpad/hostapd 完整实现。Wi-Fi 6 AP 测试使用 wpad-openssl,并启用 CONFIG_DRIVER_11AX_SUPPORT,否则 netifd 生成的 HE 参数可能被 hostapd 拒绝。
在 Linux/WSL 构建机终端进入 OpenWrt 源码根目录,然后按顺序输入以下命令;其中以 # 开头的行是终端注释:
cd openwrt
./scripts/feeds update -a
./scripts/feeds install -a
make menuconfig
make defconfig
make download -j"$(nproc)"
# 先单独验证驱动包,便于看到完整错误
make package/kernel/aic8800dc/clean
make package/kernel/aic8800dc/compile V=sc -j1
# 驱动成功后再构建固件
make V=sc -j"$(nproc)"
使用 -j1 V=sc 重现驱动编译问题非常重要。并行构建时,屏幕最后一行往往只是上层 Makefile 的 Error 2,真正的 C 编译错误可能在此前数百行。
构建完成后,下列是相对于 OpenWrt 源码根目录的产物路径模式(用于查找文件,不是终端输出,也不是需要逐行输入的命令):
bin/targets/rockchip/armv8/
bin/targets/rockchip/armv8/packages/kmod-aic8800dc_*.ipk
8. 鲁班猫 2 真机调试过程概述
本项目不是一次编译成功,而是按以下顺序逐步排除问题:
- 初始固件:系统能启动,但网卡保持
a69c:5722,确认需要 usbmode 自动弹出规则。 - USB mode 测试:网卡变为
a69c:88de,驱动开始 probe,但固件从/vendor/etc/firmware加载失败。 - 固件路径测试:通过软链接验证后,把默认路径正式改为
/lib/firmware。 - r3 / backport 适配:模块和固件可以加载,
phy0出现;扫描期间发生rwnx_cfg80211_probe_clientARM64 Oops,继续修正 cfg80211 API 和 netdev 注册。 - r4 / AP 调试:驱动注册成功,但 hostapd 报配置错误或
Failed to set up interface。先用HT20、固定 2.4 GHz/信道 6 排除 HE 配置影响。 - r5 / 11ax:加入
wpad-openssl与CONFIG_DRIVER_11AX_SUPPORT,再开启 HE20,验证 WPA2 和 Wi-Fi 6 AP。 - r6 / 开机重试:解决 usbmode、固件上传、PHY 注册晚于 netifd 首轮无线初始化的问题。
- r7 / 动态 path:解决 USB 总线号跨启动变化后,UCI 中静态
radio0.path指向旧 sysfs 路径的问题。
AP 最小诊断配置可以从保守值开始。在运行中的鲁班猫 2 OpenWrt SSH/串口终端输入以下 UCI 命令;这些命令会修改 /etc/config/wireless 中的 radio0 和 default_radio0 段:
uci set wireless.radio0.country='CN'
uci set wireless.radio0.band='2g'
uci set wireless.radio0.channel='6'
uci set wireless.radio0.htmode='HT20'
uci set wireless.radio0.disabled='0'
uci set wireless.default_radio0.disabled='0'
uci commit wireless
wifi reload
确认普通 HT20 AP 正常后,再改成目标 HE 模式。这样可以区分“驱动/AP 基础功能故障”和“hostapd 的 11ax 配置/能力协商故障”。
建议每轮都在鲁班猫 2 OpenWrt SSH/串口终端输入以下只读诊断命令,并把终端输出保存到日志文件用于版本间对比:
lsusb
lsmod | grep -Ei 'aic|cfg80211|mac80211'
iw phy
iw dev
ubus call network.wireless status
uci show wireless
dmesg | grep -Ei 'aic|8800|usb|firmware|cfg80211|wlan|oops'
logread | grep -Ei 'netifd|hostapd|AP-ENABLED|failed|error'
9. 为什么需要开机无线恢复服务
手工执行 wifi up radio0 能成功,并不代表每次冷启动都会成功。下面是启动时序示意图,用于阅读理解,不是终端命令或实际日志:
a69c:5722 虚拟光驱
-> usbmode 弹出
-> USB 重新枚举为 a69c:88de
-> aic_load_fw 上传固件
-> aic8800_fdrv 注册 phy0
-> netifd/hostapd 配置 radio0
netifd 可能在 phy0 出现前已经完成首轮无线设置并放弃。另一个问题是 USB 重新枚举后的总线拓扑可能从例如 5-1 变成其他路径,/etc/config/wireless 中保存的 radio0.path 因此失效;再次运行 wifi config 还可能生成额外的禁用 radio 配置。
r7 服务安装一个 START=99 的晚启动脚本。新建包内文件 package/kernel/aic8800dc/files/aic8800dc-wifi-retry.init,从文件开头写入以下全部内容;安装 IPK 后它会成为路由器上的 /etc/init.d/aic8800dc-wifi-retry:
#!/bin/sh /etc/rc.common
START=99
start() {
/usr/sbin/aic8800dc-wifi-retry &
}
恢复逻辑位于包内文件 package/kernel/aic8800dc/files/aic8800dc-wifi-retry.sh。在 sync_radio_path() 函数开头、局部变量声明之后加入以下代码,用于读取真实 sysfs 路径;安装后对应 /usr/sbin/aic8800dc-wifi-retry:
phy_path="$(readlink -f /sys/class/ieee80211/phy0 2>/dev/null)"
phy_path="${phy_path#/sys/devices/}"
phy_path="${phy_path%/ieee80211/phy0}"
继续编辑同一个 aic8800dc-wifi-retry.sh,在 sync_radio_path() 中完成 phy_path 非空检查后加入以下条件块,同步 radio0.path:
if [ "$(uci -q get wireless.radio0.path)" != "$phy_path" ]; then
logger -t aic8800dc "updating radio0 path to $phy_path"
uci set wireless.radio0.path="$phy_path"
uci commit wireless
ubus call network reload >/dev/null 2>&1
sleep 3
fi
本实验板只有一个 WLAN PHY,因此脚本还会清理绑定旧 USB 枚举路径的 radio。以下两个循环位于同一文件 aic8800dc-wifi-retry.sh 的 sync_radio_path() 中,放在设置 wireless.radio0.path 之后、uci commit wireless 之前:
for iface in $(uci -q show wireless |
sed -n 's/^wireless\.\([^.=]*\)=wifi-iface$/\1/p'); do
iface_device="$(uci -q get wireless."$iface".device)"
[ "$iface_device" = "radio0" ] || uci -q delete wireless."$iface"
done
for section in $(uci -q show wireless |
sed -n 's/^wireless\.\([^.=]*\)=wifi-device$/\1/p'); do
[ "$section" = "radio0" ] || uci -q delete wireless."$section"
done
同一文件 aic8800dc-wifi-retry.sh 的后半部分,在 sync_radio_path() 函数结束后定义 radio_up()(配套仓库实际把这个小函数放在文件开头也可以),并在脚本主流程末尾加入以下轮询重试逻辑。以下是文件代码,不是在终端逐行输入:
radio_up() {
ubus call network.wireless status 2>/dev/null |
jsonfilter -e '@.radio0.up' 2>/dev/null
}
attempt=0
retries=0
while [ "$attempt" -lt 12 ]; do
sleep 5
attempt=$((attempt + 1))
[ "$(radio_up)" = "true" ] && exit 0
[ -e /sys/class/ieee80211/phy0 ] || continue
retries=$((retries + 1))
wifi up radio0
sleep 8
[ "$(radio_up)" = "true" ] && exit 0
[ "$retries" -ge 3 ] && break
done
注意:清理非 radio0 配置的逻辑只适合本实验的单 PHY 板。如果目标设备同时有板载 Wi-Fi 或多个 USB 无线网卡,必须根据 VID/PID、驱动名或 sysfs 路径精确识别 AIC 设备,不能直接删除其他 radio。
10. 旁路由网络和“自动路由”配置
kmod-tun 和 kmod-nft-tproxy 是通用的 OpenWrt 内核网络功能包,与 AIC8800DC 无线驱动本身没有直接依赖关系:
kmod-tun提供 Linux TUN/TAP 虚拟网络接口支持;kmod-nft-tproxy提供 nftables 的透明代理数据包处理能力。
如果后续网络方案需要虚拟三层接口或透明转发能力,可以在项目文件 configs/bypass-router.seed 的软件包配置区域保留以下两行;如果只验证 AIC8800DC 无线驱动和普通 AP,这两个包不是必需项:
CONFIG_PACKAGE_kmod-tun=y
CONFIG_PACKAGE_kmod-nft-tproxy=y
上面是构建配置文件内容,不是运行中路由器终端需要输入的命令。两个 kmod 都必须随目标 OpenWrt 固件一起编译,或从与当前固件的内核版本、内核配置和 kernel ABI 完全匹配的软件源安装;不能仅因为设备同为 RK3568 或 ARM64 就混用其他固件的 kmod IPK。
无线驱动只提供链路,旁路由仍需明确配置管理地址、上级网关、DNS、DHCP 和 WAN。新建项目文件 scripts/configure-bypass.sh,从文件开头写入以下脚本内容;这是文件源码,不要直接逐行粘贴进终端:
#!/bin/sh
set -eu
LAN_IP="$1"
GATEWAY="$2"
NETMASK="${3:-255.255.255.0}"
uci batch <<EOF
set network.lan.proto='static'
set network.lan.ipaddr='$LAN_IP'
set network.lan.netmask='$NETMASK'
set network.lan.gateway='$GATEWAY'
del_list network.lan.dns='$GATEWAY'
add_list network.lan.dns='$GATEWAY'
set network.wan.disabled='1'
set network.wan6.disabled='1'
set dhcp.lan.ignore='1'
EOF
uci commit network
uci commit dhcp
/etc/init.d/dnsmasq restart
/etc/init.d/network restart
将 scripts/configure-bypass.sh 复制到鲁班猫 2 后,在它的 OpenWrt SSH/串口终端进入脚本所在目录,并输入以下命令。执行会立即修改网络配置并重启网络,SSH 地址随后变为 192.168.1.2:
sh configure-bypass.sh 192.168.1.2 192.168.1.1 255.255.255.0
脚本禁用 OpenWrt 自己的 LAN DHCP,防止与主路由形成两个 DHCP 服务;同时将主路由设为默认网关和 DNS。若只希望个别终端经过鲁班猫 2 转发,应在主路由中为这些终端单独设置网关/DNS,或在终端手工配置,而不是让整个局域网无条件改道。
11. 将恢复脚本打进 IPK
编辑 package/kernel/aic8800dc/Makefile,找到文件末尾的 KernelPackage/aic8800dc/install 定义,用以下完整定义替换原安装段;它必须位于最终 $(eval $(call KernelPackage,aic8800dc)) 之前:
define KernelPackage/aic8800dc/install
$(INSTALL_DIR) $(1)/lib/firmware/aic8800DC
$(CP) $(PKG_BUILD_DIR)/fw/aic8800DC/* $(1)/lib/firmware/aic8800DC/
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/aic8800dc-wifi-retry.init \
$(1)/etc/init.d/aic8800dc-wifi-retry
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) ./files/aic8800dc-wifi-retry.sh \
$(1)/usr/sbin/aic8800dc-wifi-retry
endef
把生成的 IPK 安装到鲁班猫 2 后,在 OpenWrt SSH/串口终端输入以下命令,建立开机启动链接、立即启动服务并读取日志:
/etc/init.d/aic8800dc-wifi-retry enable
/etc/init.d/aic8800dc-wifi-retry start
logread | grep aic8800dc
如果脚本随固件预装,还应确认 OpenWrt 打包阶段是否自动生成了 init 链接;若只安装 IPK,则显式执行 enable 最稳妥。
12. 完整验收清单
每次升级驱动、内核或 OpenWrt backport 后,至少完成以下测试:
- 冷启动时
a69c:5722能自动切换为a69c:88de。 aic_load_fw和aic8800_fdrv自动加载,无 Unknown symbol/invalid module format。- 固件从
/lib/firmware/aic8800DC成功读取。 iw phy出现phy0,iw dev出现接口。- 扫描不会再触发 Oops。
- HT20 AP 能启动,日志出现
AP-ENABLED。 - WPA2 客户端能够反复关联、断开和重连。
- 开启 HE20 后客户端能够协商 802.11ax。
- 持续传输和经鲁班猫 2 路由转发的流量保持稳定。
- 至少进行多次断电冷启动,确认 USB 总线号变化时
radio0.path会自动更新。 - 拔插网卡后验证模块、netifd 和配置不会进入不可恢复状态。
完成一次冷启动后,在鲁班猫 2 OpenWrt SSH/串口终端输入以下只读检查命令;终端输出应分别反映 USB ID、无线 UCI、实际 sysfs path、netifd 状态、无线接口和关键日志:
lsusb
cat /etc/config/wireless
readlink -f /sys/class/ieee80211/phy0
ubus call network.wireless status
iw dev
logread | grep -Ei 'aic8800dc|hostapd|AP-ENABLED|failed'
dmesg | grep -Ei 'aic|8800|firmware|oops|unknown symbol'
13. 移植到其他 ARM64 OpenWrt 设备时要改什么
源码包可以继续移植,但应考虑下列情况:
- 在目标 OpenWrt SDK/源码树内重新编译,绝不强装鲁班猫 2 的 IPK。
- 根据目标树的
cfg80211.h、net/cfg80211.h和struct cfg80211_ops核对回调原型。 - 核对
mac80211.symvers和 backport include 路径。 - 确认目标内核的 USB Host、电源和 cfg80211/mac80211 配置。
- 多 PHY 设备必须改写 r7 的 radio 清理逻辑。
- Linux 6.1、5.15、6.12 或 OpenWrt 25.x 不能假设直接适用现有补丁。
判断兼容性的单位不是“ARM64”,甚至也不只是“Linux 6.6”,而是目标 OpenWrt 的完整内核 ABI、无线 backport API 和配置组合。
结语
这次移植最容易误判的地方有三个:第一,网卡附带了一个 U盘,首次识别到的是U盘,驱动根本没有机会 probe;第二,驱动能够编译和加载,并不代表它已经适配 OpenWrt 的 cfg80211 backport;第三,手工 wifi up 成功不代表冷启动可靠,usbmode、固件加载、PHY 注册和 netifd 之间仍存在时序竞争。
本文将USB 模式切换、驱动源码补丁、固件路径、OpenWrt backport 集成、hostapd 11ax 配置以及延迟启动恢复服务作为一个整体处理,最终得到可重复构建、可多次冷启动并可持续运行的无线软路由。
更多推荐



所有评论(0)