从”插上看不见网卡”到”WiFi + BT 同时可用”的完整过程。每一步都给出命令、预期输出和失败分支,照着走可复现。
0. 硬件与环境指纹(决定后面所有配置)
| 项目 | 值 | 怎么确认 |
|---|---|---|
| 设备三段 PID | a69c: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_fdrv | lsusb -t |
| 内核 | Ubuntu 24.04,6.8.0-147-generic,gcc-13 | uname -r |
| Secure Boot | 关闭(否则未签名模块拒载) | mokutil --sb-state |
| 驱动上游 | goecho/aic8800_linux_drvier(厂商 dump + 少量社区修补) | — |
前置依赖:linux-headers-$(uname -r)、gcc-13、make、eject、bluez(bluetoothctl)。
1. 复现步骤
Step 0:确认设备阶段
lsusb | grep -iE "a69c|2604"- 只有
a69c:5730→ 还在 U 盘模式,sudo eject /dev/sdX(盘标 TENDA)切出来 - 出现
a69c:8d80→ bootrom,可以往下走 - 已是
2604:0023→ 芯片里已有固件在跑,改动后需冷插拔才生效
Step 1:取源码、开分支
git clone https://github.com/goecho/aic8800_linux_drviercd aic8800_linux_drviergit switch -c dev # main 保持纯净,所有改动放 devStep 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替换调用/定义:
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 会连带匹配蓝牙接口(靠枚举顺序才没抢到,不可依赖):
#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 3688aic_userconfig_8800d80.txt 2724 ← 旧 2448aic_userconfig_8800d80_u11.txt 2724aic_userconfig_8800d80_u11_cus.txt 2724aic_userconfig_8800d80_u11_pro.txt 2724calibmode_8800d80.bin 17076fmacfw_8800d80_h_u02.bin 358640fmacfw_8800d80_h_u02_ipc.bin 352268fmacfw_8800d80_u02.bin 358072 ← 旧 327620fmacfw_8800d80_u02_ipc.bin 351828 ← 旧 326033fw_adid_8800d80_u02.bin 1708fw_ble_scan_ad_filter.bin 371284 ← 旧 328508fw_patch_8800d80_u02.bin 32700 ← 旧 25300fw_patch_8800d80_u02_ext0.bin 16136 ← 新增(BT 补丁本体)fw_patch_8800d80_u04.bin 4800fw_patch_table_8800d80_u02.bin 1384 ← 旧 984(关键)fw_patch_table_8800d80_u04.bin 416lmacfw_rf_8800d80_u02.bin 283846 ← 旧 227839不能只换 fw_patch_table:表里的 BT 地址(如 0x1e7e9c)属于配套的 fw_patch_8800d80_u02.bin 布局,混版本会静默错位。
Step 4:编译
make -C drivers/aic8800 cleanmake -C drivers/aic8800ls -l drivers/aic8800/*/*.komodinfo -F vermagic drivers/aic8800/aic8800_fdrv/aic8800_fdrv.ko # 必须等于 uname -r预期两个 .ko 产出,无 error:。常见编译报错都对应 2.1 的四处签名。
Step 5:安装
sudo make install # 必须在仓库根目录(脚本用相对路径 ./fw ./tools)它做三件事:
- 两个
.ko→/lib/modules/$(uname -r)/kernel/drivers/net/wireless/aic8800/+depmod fw/aic8800D80/→/lib/firmware/,tools/aic.rules→/etc/udev/rules.d/udevadm trigger/control --reload
开机自加载:
echo aic8800_fdrv | sudo tee /etc/modules-load.d/aic8800.confaic8800_fdrv 的依赖是 cfg80211,aic_load_fw,一条 modprobe aic8800_fdrv 就够了。
Step 6:冷插拔(必须物理拔插)
sudo modprobe -r aic8800_fdrv aic_load_fwsudo modprobe cfg80211 # 注意:modprobe -r 会连带卸掉 cfg80211sudo modprobe aic_load_fw # 预加载 loader,保证设备出现在 8d80 前它就绪# 现在拔下网卡、等 3 秒、插回不能用 /sys/bus/usb/devices/*/authorized = 0/1 代替:它只让设备重新枚举,不给芯片断电,固件仍在上一次状态里跑,测不出任何东西。
Step 7:验证
lsusb -t | sed -n '/Port [0-9]*, Class=Wireless\|Vendor/p' # 2 个 Wireless + 1 个 Vendorbluetoothctl 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=1384aicbt_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=32700aicbt_ext_patch_data_load fw_patch_8800d80_u02_ext0.bin id:0 addr:20b43c### Upload fw_patch_8800d80_u02_ext0.bin firmware, @ = 20b43c size=16136aicbt_patch_table_load bt btmode[4]:5patch version - ...### Upload fmacfw_8800d80_u02.bin firmware, @ = 120000 size=358072usb 1-7: Product: AIC 8800D80usbcore: registered new interface driver btusb2. 三个坑的判据(症状几乎一样,根因完全不同)
三种故障都表现为”WiFi 能用、蓝牙没有”,只看结果无法区分,必须按下面顺序逐层看:
| 层 | 判据 | 根因 |
|---|---|---|
| ① 代码 | lsusb -t 只有 1 个接口(ff/ff/ff) | BT 上传那段 #if 0 没打开 |
| ② 固件 | 3 个接口,但日志没有 ext_patch / size=984 | patch table 是旧版,缺 ext_patch 描述项 |
| ③ 硬件变种 | 3 个接口 + ext0 已上传 + btusb 已绑,仍报 hci0: Opcode 0x0c03 failed: -110 | chip_mcu_id=1 未开 cache |
0x0c03 = HCI_Reset,-110 = ETIMEDOUT;此时 bluetoothctl 报 No default controller available。
3. 排障速查
| 现象 | 原因 / 处理 |
|---|---|
too many arguments / incompatible pointer type | cfg80211 签名版本边界,见 2.1 |
固件下载成功但无 wlan*,lsusb 显示新 PID 且 driver=none | USB 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_fdrvbluetoothctl 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)。
复现方法(宿主零改动):
git archive dev | tar -x -C /tmp/k17checkdocker 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 不受内核升级影响):
make -C drivers/aic8800 clean && make -C drivers/aic8800 && sudo make installsudo systemctl restart NetworkManager # 见第 6 节真要上 7.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_stationcfg80211_new_sta()/cfg80211_del_sta()首参改为struct wireless_dev *get_channel多一个参数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 modewlx...: 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 还是内核”的分水岭。
修法
sudo systemctl restart NetworkManager # ← 必须重启 NM,不只 supplicantnmcli connection up Hotspot只 systemctl restart wpa_supplicant 不够(实测失败,报 No suitable device found),因为 NM 自己也持有一层设备/能力状态。重启 NM 后立即可用:
内核: usb … AP started: ch=0, bcmc_idx=33 channel=2462 bw=1nmcli: wlx…:connected:Hotspot ip: <热点网关>/24运维规则:每次重装/重载该驱动(make install、拔插、换内核)后,重启一次 NetworkManager。
8. 未完成 / 后续
- 上游 PR:已向上游提两个(6.8 兼容、Tenda ID);BT 那套(含 18 个固件文件)未提,要提的话注意二进制固件体量大。
- DKMS:暂不做(见第 6 节)。
- 本文不写入 MAC / 主机名 / 局域网地址等环境信息。
9. 这次踩坑的方法论
- 症状相同不代表根因相同:三层问题都表现为“无 hci0”,只有按 ①接口数 → ②ext0 是否上传 → ③chip_mcu_id/cache 逐层取证才能定位。先建立判据,再动手。
- 日志的时间顺序会骗人:参考实现默认开 DEBUG,几百行 printk 拖慢了流程,一度让“代码完全相同却结果不同”看起来像时序竞争;用
aicwf_dbg_level=31复现同样的输出后,才能排除打印带来的影响。 - 代码相同 ≠ 二进制相同:最后是靠“函数级机器码大小差异”(
objdump -d --disassemble=<fn>)反查到system_config_8800d80少了 14 行,再回到源码定位 cache 使能。源码 diff 只比函数名/结构行是不够的。 - 不要相信单一判据:
hci0文件存在不等于控制器可用(曾据此误判 PASS),address为空也不等于没控制器。判据要能区分“设备存在”和“设备可用”。 - 一个症状先定位到层:热点走的是 userspace 栈(NM → supplicant → nl80211)。判断方法很简单——看内核日志有没有记录:没有记录说明请求没到内核,别去改驱动;有记录再看返回值。