2934 字
15 分钟
AIC8800 USB 网卡在 6.8 内核上启用 WiFi + 蓝牙

从”插上看不见网卡”到”WiFi + BT 同时可用”的完整过程。每一步都给出命令、预期输出和失败分支,照着走可复现。

0. 硬件与环境指纹(决定后面所有配置)#

项目值怎么确认
设备三段 PIDa69c:5730(U 盘)→ a69c:8d80(bootrom)→ 2604:0023(WiFi+BT)lsusb,每次拔插看一眼
芯片族/变种chip_id=7, chip_mcu_id = 1(MCU1,关键)dmesg | grep chip_mcu_id
接口拓扑(正常态)3 个:e0/01/01×2 → btusb,ff/ff/ff → aic8800_fdrvlsusb -t
内核Ubuntu 24.04,6.8.0-147-generic,gcc-13uname -r
Secure Boot关闭(否则未签名模块拒载)mokutil --sb-state
驱动上游goecho/aic8800_linux_drvier(厂商 dump + 少量社区修补)—

前置依赖:linux-headers-$(uname -r)、gcc-13、make、eject、bluez(bluetoothctl)。

1. 复现步骤#

Step 0:确认设备阶段#

Terminal window
lsusb | grep -iE "a69c|2604"
  • 只有 a69c:5730 → 还在 U 盘模式,sudo eject /dev/sdX(盘标 TENDA)切出来
  • 出现 a69c:8d80 → bootrom,可以往下走
  • 已是 2604:0023 → 芯片里已有固件在跑,改动后需冷插拔才生效

Step 1:取源码、开分支#

Terminal window
git clone https://github.com/goecho/aic8800_linux_drvier
cd aic8800_linux_drvier
git switch -c dev # main 保持纯净,所有改动放 dev

Step 2:四处代码改动#

四个改动互相独立,缺任一处都表现为”WiFi 正常、蓝牙没有”,所以建议按顺序改、按顺序验证。

2.1 cfg80211 签名守卫:>= 6.0 是错的,应为 6.17#

satic 检查过 v6.8/v6.9/6.10/6.12/6.13/6.15/6.16(旧签名)与 v6.17/v6.19(新签名):radio_idx / link_id 参数只有 6.17 才出现。上游写成 >= 6.0 导致 6.8–6.16 全部编译失败(incompatible pointer type / too many arguments)。

drivers/aic8800/aic8800_fdrv/rwnx_compat.h 增加:

/* These specific cfg80211 entry points only gained radio_idx / link_id in
* 6.17; other ops such as stop_ap carry link_id much earlier, so keep this
* per call site. link_id is the last arg of cfg80211_cac_event() but comes
* before gfp in cfg80211_rx_*_frame(), hence two helpers. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
#define RWNX_RADIO_IDX_ARG int radio_idx,
#define RWNX_LINK_ID_ARG -1,
#define RWNX_CAC_LINK_ID_ARG , 0
#else
#define RWNX_RADIO_IDX_ARG
#define RWNX_LINK_ID_ARG
#define RWNX_CAC_LINK_ID_ARG
#endif

替换调用/定义:

rwnx_main.c
static int rwnx_cfg80211_set_wiphy_params(struct wiphy *wiphy, RWNX_RADIO_IDX_ARG u32 changed)
RWNX_RADIO_IDX_ARG enum nl80211_tx_power_setting type, int mbm)
/* start_radar_detection 的 #if 判断从 6,0,0 改成 6,17,0;新形参改名为 int link_id */
/* rwnx_radar.c */
cfg80211_cac_event(..., GFP_KERNEL RWNX_CAC_LINK_ID_ARG);
/* rwnx_rx.c */
cfg80211_rx_spurious_frame(vif->ndev, hdr->addr2, RWNX_LINK_ID_ARG GFP_ATOMIC);
cfg80211_rx_unexpected_4addr_frame(vif->ndev, sta->mac_addr, RWNX_LINK_ID_ARG GFP_ATOMIC);

2.2 补 Tenda 2604:0023,且只匹配 WiFi 接口#

这颗是 combo,三条 interface 里两条是蓝牙。用 USB_DEVICE 会连带匹配蓝牙接口(靠枚举顺序才没抢到,不可依赖):

aicwf_usb.h
#define USB_PRODUCT_ID_TENDA_D80 0x0023
/* aicwf_usb.c : aicwf_usb_id_table[] */
{USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_TENDA, USB_PRODUCT_ID_TENDA_D80,
0xff, 0xff, 0xff)},

aicwf_usb_chipmatch() 已有 vid == USB_VENDOR_ID_TENDA → PRODUCT_ID_AIC8800D81,不用改。

2.3 打开 BT 补丁上传#

drivers/aic8800/aic_load_fw/aic_compat_8800d80.c 的 8800D80 U02 路径里,四步被 #if 0 关着。删掉外层 #if 0 与其配套 #endif(连同包住 patch table 的内层 #if 0/#else/#endif),使其变成:

if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_adid, FW_ADID_BASE_NAME_8800D80_U02)) return -1;
if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_patch, FW_PATCH_BASE_NAME_8800D80_U02)) return -1;
if (aicbt_ext_patch_data_load(usb_dev, &patch_info, FW_PATCH_BASE_NAME_8800D80_U02_EXT)) return -1;
if (aicbt_patch_table_load(usb_dev, head)) return -1;

2.4 MCU1 必须开 cache(最隐蔽的一处)#

drivers/aic8800/aic_load_fw/aic_compat_8800d80.c 的 system_config_8800d80() 里,打印 chip_mcu_id 之后、写 syscfg 表之前插入:

const u32 cache_mem_addr = 0x40100020;
if (chip_mcu_id) {
ret = rwnx_send_dbg_mem_read_req(usb_dev, cache_mem_addr, &rd_mem_addr_cfm);
if (ret) { printk("%x rd fail: %d\n", cache_mem_addr, ret); return ret; }
rd_mem_addr_cfm.memdata |= 0x01;
ret = rwnx_send_dbg_mem_write_req(usb_dev, cache_mem_addr, rd_mem_addr_cfm.memdata);
if (ret) { printk("%x write fail: %d\n", cache_mem_addr, ret); return ret; }
}

MCU1 的 BT 核从 cache 取指,没有这一步 WiFi 固件照跑、BT 核一执行就死。

2.5 U 盘模式自动 eject(可选但强烈建议)#

tools/aic.rules 追加一行(必须单行,udev 不支持续行):

KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5730", SYMLINK+="tendaudisk", RUN+="/usr/bin/eject /dev/%k"

Step 3:固件必须成对替换#

fw/aic8800D80/ 里旧的一套没有 BT 描述项。fw_patch_table 是 BT 的”描述表”:旧版 984 字节、info_len=4,没有任何 ext_patch 项,于是 aicbt_ext_patch_data_load() 循环 0 次、ext0.bin 永远不会被上传,日志里连错误都没有。

替换后的 18 个文件(fw/aic8800D80/):

aic_powerlimit_8800d80.txt 3688
aic_userconfig_8800d80.txt 2724 ← 旧 2448
aic_userconfig_8800d80_u11.txt 2724
aic_userconfig_8800d80_u11_cus.txt 2724
aic_userconfig_8800d80_u11_pro.txt 2724
calibmode_8800d80.bin 17076
fmacfw_8800d80_h_u02.bin 358640
fmacfw_8800d80_h_u02_ipc.bin 352268
fmacfw_8800d80_u02.bin 358072 ← 旧 327620
fmacfw_8800d80_u02_ipc.bin 351828 ← 旧 326033
fw_adid_8800d80_u02.bin 1708
fw_ble_scan_ad_filter.bin 371284 ← 旧 328508
fw_patch_8800d80_u02.bin 32700 ← 旧 25300
fw_patch_8800d80_u02_ext0.bin 16136 ← 新增(BT 补丁本体)
fw_patch_8800d80_u04.bin 4800
fw_patch_table_8800d80_u02.bin 1384 ← 旧 984(关键)
fw_patch_table_8800d80_u04.bin 416
lmacfw_rf_8800d80_u02.bin 283846 ← 旧 227839

不能只换 fw_patch_table:表里的 BT 地址(如 0x1e7e9c)属于配套的 fw_patch_8800d80_u02.bin 布局,混版本会静默错位。

Step 4:编译#

Terminal window
make -C drivers/aic8800 clean
make -C drivers/aic8800
ls -l drivers/aic8800/*/*.ko
modinfo -F vermagic drivers/aic8800/aic8800_fdrv/aic8800_fdrv.ko # 必须等于 uname -r

预期两个 .ko 产出,无 error:。常见编译报错都对应 2.1 的四处签名。

Step 5:安装#

Terminal window
sudo make install # 必须在仓库根目录(脚本用相对路径 ./fw ./tools)

它做三件事:

  1. 两个 .ko → /lib/modules/$(uname -r)/kernel/drivers/net/wireless/aic8800/ + depmod
  2. fw/aic8800D80/ → /lib/firmware/,tools/aic.rules → /etc/udev/rules.d/
  3. udevadm trigger / control --reload

开机自加载:

Terminal window
echo aic8800_fdrv | sudo tee /etc/modules-load.d/aic8800.conf

aic8800_fdrv 的依赖是 cfg80211,aic_load_fw,一条 modprobe aic8800_fdrv 就够了。

Step 6:冷插拔(必须物理拔插)#

Terminal window
sudo modprobe -r aic8800_fdrv aic_load_fw
sudo modprobe cfg80211 # 注意:modprobe -r 会连带卸掉 cfg80211
sudo modprobe aic_load_fw # 预加载 loader,保证设备出现在 8d80 前它就绪
# 现在拔下网卡、等 3 秒、插回

不能用 /sys/bus/usb/devices/*/authorized = 0/1 代替:它只让设备重新枚举,不给芯片断电,固件仍在上一次状态里跑,测不出任何东西。

Step 7:验证#

Terminal window
lsusb -t | sed -n '/Port [0-9]*, Class=Wireless\|Vendor/p' # 2 个 Wireless + 1 个 Vendor
bluetoothctl show | head -4 # Manufacturer / Version 有值
ip -br link | grep -E "wlx|wlan" # WiFi 接口存在
rfkill list | grep -A2 -i bluetooth # 未阻塞

一次成功时 journalctl -k 里的关键行(顺序即真实流程):

chip_id=7, chip_mcu_id = 1
### Upload fw_patch_table_8800d80_u02.bin fw_patch_table, size=1384
aicbt_patch_info_unpack ext patch id:0 addr:20b43c
### Upload fw_adid_8800d80_u02.bin firmware, @ = 201940 size=1708
### Upload fw_patch_8800d80_u02.bin firmware, @ = 1e0000 size=32700
aicbt_ext_patch_data_load fw_patch_8800d80_u02_ext0.bin id:0 addr:20b43c
### Upload fw_patch_8800d80_u02_ext0.bin firmware, @ = 20b43c size=16136
aicbt_patch_table_load bt btmode[4]:5
patch version - ...
### Upload fmacfw_8800d80_u02.bin firmware, @ = 120000 size=358072
usb 1-7: Product: AIC 8800D80
usbcore: registered new interface driver btusb

2. 三个坑的判据(症状几乎一样,根因完全不同)#

三种故障都表现为”WiFi 能用、蓝牙没有”,只看结果无法区分,必须按下面顺序逐层看:

层判据根因
① 代码lsusb -t 只有 1 个接口(ff/ff/ff)BT 上传那段 #if 0 没打开
② 固件3 个接口,但日志没有 ext_patch / size=984patch table 是旧版,缺 ext_patch 描述项
③ 硬件变种3 个接口 + ext0 已上传 + btusb 已绑,仍报 hci0: Opcode 0x0c03 failed: -110chip_mcu_id=1 未开 cache

0x0c03 = HCI_Reset,-110 = ETIMEDOUT;此时 bluetoothctl 报 No default controller available。

3. 排障速查#

现象原因 / 处理
too many arguments / incompatible pointer typecfg80211 签名版本边界,见 2.1
固件下载成功但无 wlan*,lsusb 显示新 PID 且 driver=noneUSB ID 表缺该 PID,见 2.2
上电停在 a69c:5730没 eject;sudo eject /dev/sdX 或补 udev 规则
Unknown symbol cfg80211_*(insmod 时报)先 modprobe cfg80211,或改用 modprobe 而非 insmod
hci0: Opcode 0x0c03 failed: -110见 2.4(MCU1 cache)
蓝牙能用、重启后又没了modules-load.d 没配,或 loader 未更新
加载成功但无射频/连不上板级配置:aic8800_fdrv/Makefile 的 CONFIG_VRF_DCDC_MODE 与 CONFIG_PMIC_SETTING 要对调试
换内核后模块消失out-of-tree,需重新 make && sudo make install,长期方案 DKMS

日志开关:aic_load_fw / aic8800_fdrv 都有 aicwf_dbg_level(默认 LOGERROR|LOGINFO);参考实现默认开到 LOGDEBUG|LOGTRACE,排查时可 insmod ... aicwf_dbg_level=31。

两个不能当判据的东西:

  • /sys/class/bluetooth/hci0/{address,type} 在这套 btusb 组合下恒为空文件,用 bluetoothctl show 才准;
  • authorized 0/1 不等于断电重启,验证”改动是否生效”必须物理拔插。

4. 验收清单#

  • lsmod:aic8800_fdrv + aic_load_fw(后者的 srcversion 必须等于 drivers/aic8800/aic_load_fw/aic_load_fw.ko)
  • lsusb -t:e0/01/01×2 → btusb,ff/ff/ff → aic8800_fdrv
  • bluetoothctl show:有 Manufacturer / Version(本机为 0x0b3b / 0x0d,即 AIC、HCI 5.3)
  • bluetoothctl scan on 能看到设备;ip -br link 有 WiFi 接口,nmcli device wifi list 能扫到 AP(2.4G + 5G)
  • 未连 AP 时 ip link 显示 DORMANT / NO-CARRIER 属正常

5. 内核升级:能升到多新#

实测(容器里用真实内核头文件编译,不动宿主):

内核结果说明
6.8.0-147(Ubuntu 24.04)✅ 生产使用中WiFi + BT
6.17.0-41(Ubuntu 25.10)✅ 编译通过,两个 .ko 都产出>=6.17 守卫生效,6.8 那套补丁在 6.17 上是惰性的
7.0.0-38(Ubuntu 26.04)✅ 编译通过add_key 等仍是 struct net_device *
7.1.x❌ 编译失败cfg80211_ops 从 7.1 起改用 struct wireless_dev *(已核 v7.1 头文件)
7.2.11(Arch / Fedora 43、44)❌ 编译失败在 7.1 基础上,strncpy 也从 include/linux/string.h 移除

结论:6.8 → 7.0 都不需改代码,分界线在 7.1(ops 签名)和 7.2(strncpy)。

复现方法(宿主零改动):

Terminal window
git archive dev | tar -x -C /tmp/k17check
docker run --rm -v /tmp/k17check:/src -w /src ubuntu:25.10 bash -lc \
'apt-get update -qq && apt-get install -yqq linux-headers-generic build-essential && \
K=$(ls -d /usr/src/linux-headers-*generic | tail -1) && \
make -C drivers/aic8800 KDIR=$K'

升到 6.17 只需重装模块(固件在 /lib/firmware 不受内核升级影响):

Terminal window
make -C drivers/aic8800 clean && make -C drivers/aic8800 && sudo make install
sudo systemctl restart NetworkManager # 见第 6 节

真要上 7.1+ 时的改动备忘#

  1. cfg80211_ops 的 struct net_device * → struct wireless_dev *(7.1 起):add_key / get_key / del_key / set_default_key / set_default_mgmt_key / add_station / del_station / change_station / get_station / dump_station
  2. cfg80211_new_sta() / cfg80211_del_sta() 首参改为 struct wireless_dev *
  3. get_channel 多一个参数
  4. strncpy → strscpy(7.2 起 string.h 不再声明,rwnx_msg_tx.c:3761 等处)

照 6.17 的做法全包在 #if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 1, 0)(strncpy 部分用 7, 2, 0)里,旧内核行为不变。

6. DKMS(选了暂不做,仅备忘)#

DKMS 让模块跟着内核自动重编:源码登在 /var/lib/dkms/<包>/<版本>/,内核安装/删除时由 dpkg hook 针对新内核重编一次。本仓库落地形状:

PACKAGE_NAME="aic8800"
PACKAGE_VERSION="1.0"
MAKE[0]="make -C drivers/aic8800 KVER=${kernelver} KDIR=/lib/modules/${kernelver}/build"
BUILT_MODULE_NAME[0]="aic_load_fw"
BUILT_MODULE_NAME[1]="aic8800_fdrv"
DEST_MODULE_LOCATION[0..1]="/kernel/drivers/net/wireless/aic8800"
AUTOINSTALL="yes"

三个局限:① 只管模块不管固件(/lib/firmware/aic8800D80/ 要单独放,内核升级不影响它);② 需要对应内核的 linux-headers,自编内核没装 headers 时会静默跳过(journalctl -u dkms);③ 不解决版本兼容——7.x 上照样失败,只是失败发生在内核安装时。

7. 热点(AP 模式)“能开但扫不到”#

现象:蓝牙正常、WiFi 能扫能连,但开热点后手机搜不到该 SSID,nmcli 里设备却显示热点已激一。

根因:userspace 的能力缓存过期,不是驱动/固件问题#

关键日志(journalctl -u wpa_supplicant):

wlx...: Driver does not support AP mode
wlx...: Reject scan trigger since one is already pending

而同一时间 nmcli -f WIFI-PROPERTIES device show <if> 报 WIFI-PROPERTIES.AP: yes。一个说支持、一个说不支持,因为它们是两个不同的读法:

  • NM 读内核 wiphy 的 interface_modes(现在时)
  • wpa_supplicant 在自己启动时读一次 nl80211 能力位(WPA_DRIVER_FLAGS_AP)并缓存

本机上 supplicant 是两天前启动的,而驱动今天被反复卸载/重装多次 → 缓存与现状不一致,supplicant 直接拒绝创建 AP;此时内核日志零记录(START_AP 从未到达驱动),这条就是判断“问题在 userspace 还是内核”的分水岭。

修法#

Terminal window
sudo systemctl restart NetworkManager # ← 必须重启 NM,不只 supplicant
nmcli connection up Hotspot

只 systemctl restart wpa_supplicant 不够(实测失败,报 No suitable device found),因为 NM 自己也持有一层设备/能力状态。重启 NM 后立即可用:

内核: usb … AP started: ch=0, bcmc_idx=33 channel=2462 bw=1
nmcli: wlx…:connected:Hotspot ip: <热点网关>/24

运维规则:每次重装/重载该驱动(make install、拔插、换内核)后,重启一次 NetworkManager。

8. 未完成 / 后续#

  • 上游 PR:已向上游提两个(6.8 兼容、Tenda ID);BT 那套(含 18 个固件文件)未提,要提的话注意二进制固件体量大。
  • DKMS:暂不做(见第 6 节)。
  • 本文不写入 MAC / 主机名 / 局域网地址等环境信息。

9. 这次踩坑的方法论#

  1. 症状相同不代表根因相同:三层问题都表现为“无 hci0”,只有按 ①接口数 → ②ext0 是否上传 → ③chip_mcu_id/cache 逐层取证才能定位。先建立判据,再动手。
  2. 日志的时间顺序会骗人:参考实现默认开 DEBUG,几百行 printk 拖慢了流程,一度让“代码完全相同却结果不同”看起来像时序竞争;用 aicwf_dbg_level=31 复现同样的输出后,才能排除打印带来的影响。
  3. 代码相同 ≠ 二进制相同:最后是靠“函数级机器码大小差异”(objdump -d --disassemble=<fn>)反查到 system_config_8800d80 少了 14 行,再回到源码定位 cache 使能。源码 diff 只比函数名/结构行是不够的。
  4. 不要相信单一判据:hci0 文件存在不等于控制器可用(曾据此误判 PASS),address 为空也不等于没控制器。判据要能区分“设备存在”和“设备可用”。
  5. 一个症状先定位到层:热点走的是 userspace 栈(NM → supplicant → nl80211)。判断方法很简单——看内核日志有没有记录:没有记录说明请求没到内核,别去改驱动;有记录再看返回值。
AIC8800 USB 网卡在 6.8 内核上启用 WiFi + 蓝牙
https://fenlyin.dpdns.org/posts/aic8800-wifi-driver/
作者
Hiyuki
发布于
2026-10-24
许可协议
CC BY-NC-SA 4.0