简介:面向 Linux 与 Android 平台的 Realtek RTL8188FU、RTL8188FTV 芯片 USB 无线网卡驱动资料包,适合嵌入式开发、系统移植及无线网络调试人员解决驱动编译、设备识别与无线连接问题。压缩包共 56 个文件,约 24.32MB,包含 28 个 PDF 技术文档、12 个 gz 压缩源码包,以及 diff 补丁、conf 配置、tgz、txt 说明和 sh 安装脚本等,兼顾驱动源码、Android 各版本 SDK 与配套说明。已有 9603 人学习下载,说明其在同类驱动资料中具有较高参考热度。内容覆盖 Android 4.4 至 8.0 的参考代码与 SDK,并附驱动编译安装、Station/SoftAP/Bridge、Miracast、TDLS、省电、WOW、射频法规认证等文档,以及 wpa_supplicant/hostapd、wireless_tools 相关源码和配置,便于读者按模块查阅移植要点、排查编译与连接问题。
1. 一块十几块的 USB 小网卡,“插上没反应”多半是驱动问题
开发板上板载无线模块缺货,临时插一个 RTL8188FU 的 USB 无线网卡救急,lsusb里能看到设备,ip link里就是没有wlan0——这个场景做过 ARM 板子和 Android 定制的同行大概都遇到过。RTL8188FU 和 RTL8188FTV 属于同一套驱动代码覆盖的两颗 USB 接口 802.11n 芯片,1T1R 单频 2.4G,最高 150Mbps,主打便宜、功耗低、外围简单,常见于电视盒子、工控板、车载后装和一堆物联网网关。它们的问题不在硬件,而在于驱动是 out-of-tree 的:主流发行版内核树里没有,厂商只放源码包,能不能跑起来全看你交叉编译和内核对齐的功夫。这份资源就是那套源码包,Linux 和 Android 两条集成路径都在里面。
2. 驱动包目录与编译链路:从 Makefile 找出交叉编译入口
拿到压缩包先别急着make,这个包有接近十个顶层目录和几十个配置宏,直接编大概率在第一个error上卡半小时。先花十分钟把结构摸清楚,后面每一步都能少走弯路。
2.1 顶层目录分工与改动边界
不同时间放出来的包目录名会有细微差别,但主干是一致的。下面这张表按“改动频率”排序,越靠下越不建议动:
| 目录 / 文件 | 承担什么 | 是否要改 |
|---|---|---|
Makefile/Kconfig | 平台选择、编译参数、模块名 | 必改,选目标平台 |
include/autoconf.h | 功能开关总闸(AP 模式、省电、并发等) | 常改,按需求开关 |
os_dep/linux/ | 内核适配层:字符设备、proc、USB 接口、cfg80211 对接 | 内核版本不兼容时主要改这里 |
os_dep/android/ | Android 私有 ioctl(SIOCSIWPRIV)对接 | 走 Android 方案才需要 |
hal/rtl8188f/ | 芯片私有部分:PHY 表、RF 参数、电源序列、固件数组 | 基本不动 |
core/ | 802.11 管理面:扫描、关联、收发调度、加密 | 不动 |
platform/ | 平台相关的内存/IO 抽象 | 不动 |
一般来说,能跑的包和不能跑的包,差异全在os_dep/linux/那十几个文件里。hal/rtl8188f/hal8188f_fw.c值得单独说一句:默认情况下固件是以unsigned char数组的形式编进.ko里的,所以很多场景下你不需要额外往/lib/firmware塞 bin 文件。如果autoconf.h里打开了CONFIG_FILE_FWIMG,才会改成运行时从文件系统读固件,这时候路径拼错就会静默失败,日志里往往只有一行不起眼的fw img download fail。
2.2 Linux 侧交叉编译的最小命令集
第一步是把 Makefile 里的平台宏选对。这个包的 Makefile 用一堆CONFIG_PLATFORM_xxx做互斥开关,同一时刻只能有一个是y:
# 先看看包里都支持哪些平台,以及当前默认开的是哪个 grep -n "^CONFIG_PLATFORM_" Makefile | head -40输出里会看到类似CONFIG_PLATFORM_I386_PC = y、CONFIG_PLATFORM_ARM64_RPI = n这样的行。把默认那个改成n,把你目标板对应的改成y:
# Makefile 片段:只保留一个 y CONFIG_PLATFORM_I386_PC = n CONFIG_PLATFORM_ARM64_RPI = n CONFIG_PLATFORM_ARM_ROCKCHIP = y平台宏不只是一个名字,它会带出后面的EXTRA_CFLAGS,里面塞了-DCONFIG_LITTLE_ENDIAN、-DCONFIG_IOCTL_CFG80211、-DRTW_USE_CFG80211_STA_EVENT这类开关,还可能强行指定CONFIG_PLATFORM_ARM64_RPI对应的ARCH和CROSS_COMPILE。所以宁可自己选一个最接近的平台宏去改,也不要在命令行硬覆盖,否则很容易出现“命令行传了ARCH=arm64,Makefile 里又给你塞了个ARCH=arm”的打架现场。
选完平台再编,两条命令:
# 1. 清一遍,防止上一次不同 ARCH 的 .o 残留 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KSRC=/opt/kernel/linux-5.10 clean # 2. 正式编译,-j 按宿主机核数来 make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KSRC=/opt/kernel/linux-5.10KSRC这一项是整条链路里最容易出问题的地方,三个要求缺一不可:必须是目标板实际运行的那个内核源码树(版本号要能对上)、必须已经跑过一次make modules_prepare、最好还用目标板自己的.config。用宿主机/lib/modules/$(uname -r)/build去编交叉模块,十次有九次会栽在vermagic不匹配上,insmod时直接甩一句invalid module format,而且不告诉你到底差在哪。
2.3 Android 侧的两条集成路径
Android 上不要指望insmod一把梭,权限和服务启动都得提前安排。集成方式有两种:
路径 A,作为外部模块编进 vendor 分区。用 Android 源码树里的TARGET_OUT内核构建目录,走标准的M=外部模块编译,产物是一个独立的8188fu.ko:
# 在 Android 源码根目录下执行,先 source 环境 source build/envsetup.sh && lunch <你的产品> # 指向 Android 编好的内核 out 目录,而不是源码目录 make -C $(pwd)/out/target/product/<产品名>/obj/KERNEL_OBJ \ M=$(pwd)/hardware/wifi/rtl8188fu \ ARCH=arm64 CROSS_COMPILE=$(pwd)/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android- \ modules产物丢到vendor/lib/modules/(老平台是system/lib/modules/),再补两个文件:vendor/lib/modules/modules.load里加一行模块名,以及一个init.<平台>.rc里的insmod或者modprobe语句。这种方式的优点是改驱动不用重编整个内核,缺点是模块和内核的vermagic以及CONFIG_MODVERSIONS状态必须完全一致,OTA 换内核之后模块忘了一起换,开机就是没有 WiFi。
路径 B,编进内核树。把整个目录拷到driver/net/wireless/下面,在上一级Kconfig和Makefile里挂进去,make menuconfig里勾上。量产固件一般走这条,省掉模块加载时序和 SELinux 那一堆事。代价是每次改一行代码都要重编内核镜像,调试期不划算——我的习惯是调试期走 A,定版前一周切到 B 跑一遍全流程。
2.4 编完先做三项静态体检
.ko出来了不等于能用,插板子之前先验三样东西,这是省时间的关键:
# 1. 看 vermagic,必须和目标板 uname -r 完全一致 modinfo -F vermagic 8188fu.ko # 2. 看模块名和参数表,确认 module_param 都编进去了 modinfo 8188fu.ko | grep -E "^(filename|version|parm):" # 3. 看有没有未定义的符号,尤其 cfg80211 那一批 nm -u 8188fu.ko | grep -i cfg80211 | head第三步输出的未定义符号是正常的——它们会在insmod时由内核的cfg80211模块提供。但如果目标内核根本没开CONFIG_CFG80211,那这些符号就永远解析不了,insmod会一次报出一长串Unknown symbol。所以第二条命令里如果看到大量cfg80211_*,先去目标板zcat /proc/config.gz | grep CFG80211确认一下,比在板子上反复insmod高效得多。
3. 内核版本兼容:cfg80211、proc 与 net_device 的 API 迁移
这个包的原型代码年代比较早,直接扔到 5.x 甚至 6.x 内核上编,报错能刷满几屏。别慌,这些错误有很强的规律性,基本集中在内核几个固定版本的 API 变更点上。摸清规律之后,一个下午能把一个包从 4.9 一路迁到 6.x。
3.1 先定位不兼容点,而不是逐个报错硬改
编译器一次只报前几十个错,改完一批又冒一批,很容易改到怀疑人生。正确姿势是先拿到完整日志,按错误类型归堆:
# 全量日志落盘,别只看终端滚屏 make -j1 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KSRC=/opt/kernel/linux-6.1 2>&1 | tee build.log # 按错误类型归类,先看哪类最多 grep -oE "error: [^,]*" build.log | sort | uniq -c | sort -rn | head -20 # 隐式声明往往意味着头文件或 API 变体不一致,单独看 grep -n "implicit declaration of function" build.log | head -20用-j1单线程编是有意为之:并行编译时多个文件的报错会交错,error:后面跟的上下文容易串行,反而增加判断成本。等错误归完类,再切回-j$(nproc)提速。
错误大致分三类:结构体成员没了(比如net_device->dev_addr变成只读)、函数签名变了(比如kernel_read多一个loff_t *参数)、整个接口被移除(比如get_fs()/set_fs())。第一类和第二类改起来是体力活,第三类才需要动脑。
3.2 几个高频 API 变更点的对照与改法
下面这张表是我在不同板子上迁这个包时踩出来的,按内核版本排序,命中率很高:
| 内核版本 | 报错/警告现象 | 改法 |
|---|---|---|
| >= 4.14 | kernel_read/kernel_write参数数量不对 | 补loff_t *pos参数,初值传&pos |
| >= 4.15 | init_timer未定义 | 改timer_setup(&obj->timer, callback, 0),回调签名带struct timer_list * |
| >= 5.6 | struct file_operations赋给 proc 项报类型不匹配 | 改用struct proc_ops,成员名前缀变proc_ |
| >= 5.10 | get_fs/set_fs未定义 | 直接换kernel_read/kernel_write,去掉mm_segment_t |
| >= 5.15 | ndo_do_ioctl相关告警 | 无线扩展类 ioctl 迁到ndo_siocdevprivate,或直接关掉 WEXT |
| >= 5.17 | dev_addr赋值报只读 | 改用eth_hw_addr_set(dev, addr) |
| >= 6.1 | netif_napi_add参数数量不符 | 补第三个参数weight,一般传NAPI_POLL_WEIGHT |
| >= 6.4 | class_create参数数量不符 | 去掉THIS_MODULE,只留名字字符串 |
改法上有个通用套路:用LINUX_VERSION_CODE做条件编译,老版本路径保留,新版本走新分支。这样一份代码可以同时伺候几个不同内核的产品线,不用维护多个分支:
/* 场景:驱动里从文件系统读固件/配置的那段代码 * 5.10 之后 set_fs/get_fs 被彻底移除,必须换 kernel_read */ static int rtw_read_file(const char *path, u8 *buf, u32 size) { struct file *fp; loff_t pos = 0; int ret; fp = filp_open(path, O_RDONLY, 0); if (IS_ERR(fp)) return PTR_ERR(fp); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) /* 新接口:pos 由内核维护,不需要 mm_segment_t */ ret = kernel_read(fp, buf, size, &pos); #else { mm_segment_t old_fs = get_fs(); set_fs(KERNEL_DS); ret = kernel_read(fp, pos, buf, size); /* 注意老接口参数顺序不同 */ set_fs(old_fs); } #endif filp_close(fp, NULL); return ret; }这段代码有两个细节值得说。一是参数顺序在不同内核里真的会变,kernel_read在 4.14 之前的原型是(file, offset, buf, count),之后才统一成(file, buf, count, pos),所以老分支里我直接按老顺序写,不加额外的版本判断,因为这条分支本来只会被 5.10 以前的内核编到。二是filp_close的第二个参数传NULL,这个参数从 4.x 一直保留到现在,不用改。
proc 那一块改动稍大,因为成员名全变了:
/* proc 文件操作结构体:5.6 起从 file_operations 换成 proc_ops */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) static const struct proc_ops rtw_proc_fops = { .proc_open = rtw_proc_open, .proc_read = seq_read, .proc_write = rtw_proc_write, .proc_lseek = seq_lseek, .proc_release = single_release, }; #else static const struct file_operations rtw_proc_fops = { .owner = THIS_MODULE, .open = rtw_proc_open, .read = seq_read, .write = rtw_proc_write, .llseek = seq_lseek, .release = single_release, }; #endif注意proc_ops里没有.owner成员,硬写进去在新内核上会报unknown field。这一点很多人是从网上抄的代码里带过来的,抄完编译不过还以为是内核版本判断写错了。
3.3 改完之后用最小闭环验证,别直接上板
编译过只是第一关。我一般会先在宿主机上编一个 x86 版本出来,用modprobe加载一下,看看初始化流程走不走得通——虽然宿主机上没有那根 USB 设备,但模块的注册、proc 节点的创建、cfg80211 的注册这些流程都会跑一遍,能在不碰板子的情况下抓掉一半的逻辑错误:
# 宿主机上编一个 x86 版本,只为验证模块加载流程 make ARCH=x86_64 KSRC=/lib/modules/$(uname -r)/build clean make -j$(nproc) ARCH=x86_64 KSRC=/lib/modules/$(uname -r)/build # 加载并立刻看初始化日志,然后卸载 sudo insmod 8188fu.ko rtw_drv_log_level=3 sudo dmesg | tail -40 sudo rmmod 8188fu如果insmod之后日志里有usb_register相关行、cfg80211注册成功、proc 目录建出来了,说明适配层基本没问题,剩下的就是芯片私有部分的时序了。反过来,如果insmod直接崩在内核里,dmesg会留下BUG:或Oops的调用栈,第一行的函数名就是你要盯的地方——通常是某个结构体没初始化全,或者回调函数签名对不上被错误调用。
4. 模块加载与 rtw_* 参数:把默认省电先关掉
驱动能加载,和驱动好用,是两件事。这个包的默认配置明显偏向低功耗场景,直接拿来当桌面或网关用,会遇到一堆“能连上但过一会儿就不响应”的怪事。
4.1 加载前先确认 USB 侧真的认到了
insmod之前,先在系统层面确认设备枚举正常。这一步看着多余,实际能排掉相当比例的“驱动背锅”案件:
# 看 USB 拓扑和速度,Full-speed 还是 High-speed 一眼能看出 lsusb -t # 找厂商标识,具体 VID/PID 以你手上设备为准,用这条命令直接读 lsusb | grep -i -E "0bda|wireless|802" # 抓枚举瞬间的内核日志 dmesg | grep -i -E "usb [0-9]-|new .* device|8188" | tail -20如果lsusb里压根没有这个设备,那和驱动没关系,去查供电、查线、查 USB HUB 是不是把口挂死了。这类小网卡在满载发射时电流能到 300mA 以上,挂在没有外部供电的 HUB 上,很容易在关联或者大流量的时候掉线,日志里只有一行usb disconnect,非常有迷惑性。
确认设备在,再加载模块:
# 先把默认省电关掉,否则空闲几分钟后会进入深度省电,唤醒响应很慢 sudo insmod 8188fu.ko \ rtw_power_mgnt=0 \ rtw_ips_mode=0 \ rtw_drv_log_level=3 # 确认接口出来了 ip link show | grep wlan4.2 常用 rtw_* 参数的实际含义
这个包通过module_param暴露了几十个参数,但真正天天用到的就那么七八个。下面这几个是我每次上板都会先过一遍的:
| 参数 | 典型值 | 作用 | 什么时候必须改 |
|---|---|---|---|
rtw_power_mgnt | 0/1/2 | 省电等级,0 关闭、1 最小、2 最大 | 做网关、做 AP、做长时间在线的设备,一律 0 |
rtw_ips_mode | 0/1 | 空闲进休眠(IPS) | 空闲后不响应 ARP、唤醒延迟大的,关掉 |
rtw_lps_mode | 0/1 | 连接态省电(LPS) | 延迟敏感场景(音频、控制指令)关掉 |
rtw_scan_channel_time | 30~100 | 单信道扫描驻留时间(ms) | 扫不到某个 AP 时往上调 |
rtw_country_code | "CN"/"US"/"00" | 国家码,影响信道表和功率 | 只用 1~11 信道可设"00",避免 DFS 相关逻辑 |
rtw_channel_plan | 0xFF | 信道方案覆盖,0xFF表示跟随国家码 | 需要强制特定信道表时用 |
rtw_drv_log_level | 0~5 | 日志等级 | 排查问题时开到 3~4,量产关到 0 |
rtw_hi_queue_enable | 0/1 | 高优先级队列 | 有语音/控制类小包时开,但会多占内存 |
这些参数写成一份modprobe.d配置,比每次手敲insmod靠谱得多:
# /etc/modprobe.d/8188fu.conf options 8188fu rtw_power_mgnt=0 rtw_ips_mode=0 rtw_lps_mode=0 rtw_country_code=CN rtw_drv_log_level=1Android 上没有modprobe.d这一套,参数得写在init.rc的insmod行里,或者写进vendor/etc/modprobe.d/目录(新版本 Android 支持)。参数拼错不会报错,模块照样加载,只是那个参数被忽略——所以配完一定要用cat /sys/module/8188fu/parameters/rtw_power_mgnt回读确认,别信自己写的配置文件。
4.3 STA 和 AP 两种模式的实际操作
STA 模式最省事,wpa_supplicant一套配置搞定。前提是编译时CONFIG_IOCTL_CFG80211是开的,走nl80211路径:
# 生成最小 wpa_supplicant 配置 cat > /tmp/wpa_8188.conf <<'EOF' ctrl_interface=/var/run/wpa_supplicant update_config=1 country=CN network={ ssid="your_ssid" psk="your_passphrase" key_mgmt=WPA-PSK proto=RSN pairwise=CCMP } EOF ip link set wlan0 up wpa_supplicant -B -i wlan0 -c /tmp/wpa_8188.conf -D nl80211 dhclient wlan0AP 模式要求编译时打开CONFIG_AP_MODE,这个开关在autoconf.h里,默认很多时候是关的。开了之后用hostapd,配置文件里那几个字段值得逐一看清楚:
# /etc/hostapd/8188.conf interface=wlan0 driver=nl80211 ssid=test_ap hw_mode=g channel=6 ieee80211n=1 wmm_enabled=1 auth_algs=1 wpa=2 wpa_passphrase=12345678 wpa_key_mgmt=WPA-PSK rsn_pairwise=CCMP几个容易翻车的点:channel必须落在rtw_country_code允许的信道表里,设成 12、13 而国家码是"US"时,hostapd起来会直接报channel not allowed;ieee80211n=1要配合驱动的 HT 能力,如果iw list里没看到 HT 相关条目,说明 HT 没编进去或者 PHY 初始化失败了;wmm_enabled=1对这个小网卡的实际吞吐影响不小,关掉之后大包吞吐会明显掉一截。
Android 侧的 AP 走的是框架层的SoftApManager,最终落到驱动的私有 ioctl。这条路上最容易出的问题是 SELinux 拒绝,dmesg和logcat各看一半才能拼出真相——logcat里说failed to start ap,dmesg里其实早就有一行avc: denied。
5. 排查避坑:insmod 成功却扫不到 AP 的五种现场
下面五条都是真实踩过的,按“从加载到关联”的顺序排。每条按现象、原因、解决走一遍,遇到问题可以直接对号入座。
5.1 现象:insmod 返回 0,但 ip link 里没有 wlan 接口
原因:模块初始化函数在中途goto exit了,但错误码被吞掉,所以insmod看起来成功。最常见的是固件下载失败或者 USB 端点分配失败。
解决:把日志等级拉满再加载一次,重点看_rtw_usb_probe之后那几十行:
sudo rmmod 8188fu 2>/dev/null sudo insmod 8188fu.ko rtw_drv_log_level=4 sudo dmesg | tail -80 | grep -i -E "fw|firmware|probe|fail|err"如果日志里有fw download fail或者hw ready fail,先确认autoconf.h里CONFIG_FILE_FWIMG的状态。如果是y,就得把固件 bin 文件放到驱动期望的路径下;如果是n,固件在数组里,那就往 PHY 电源序列方向查,通常是供电或者复位时序问题。
5.2 现象:insmod 报一长串 Unknown symbol,全是 cfg80211_ 开头
原因:编译时用的内核.config和运行内核不一致,最常见的是编译内核开了CONFIG_CFG80211,运行的这个内核没开;或者反过来,模块依赖的cfg80211.ko没被先加载。
解决:在目标板上直接确认运行内核的配置:
# 大部分发行版和 Android 都能从这两处之一读到 zcat /proc/config.gz | grep -E "CONFIG_CFG80211|CONFIG_MAC80211" # 或者 cat /boot/config-$(uname -r) | grep CFG80211如果确实没开,重新编内核,把CFG80211打开(CFG80211_WEXT视需求)。如果已经开了,那就是模块加载顺序问题,用modprobe而不是insmod,让依赖自动解析。
5.3 现象:能扫到 AP,也能关联,但拿不到 IP,或者过一会儿就断
原因:八成是省电在作怪。默认参数下设备空闲一段时间进 IPS,唤醒路径上如果和 DHCP 续租撞上,表现就是拿不到地址;LPS 在连接态下同样会让小包延迟飙高。另一个可能是 USB 供电不足,表现为大流量或者发射功率拉高时掉线。
解决:先把三个省电开关全关,跑一轮对照:
sudo rmmod 8188fu sudo insmod 8188fu.ko rtw_power_mgnt=0 rtw_ips_mode=0 rtw_lps_mode=0如果关掉之后问题消失,那就是省电路径的问题,可以往回一个个开,定位到具体是 IPS 还是 LPS。如果关掉还断,换个带外部供电的 HUB 再试,或者量一下板子 USB 口的 5V 在发射瞬间有没有明显跌落。
5.4 现象:Android 上驱动加载成功,wlan0 也在,但 wpa_supplicant 起不来
原因:wpa_supplicant和驱动走的接口对不上。Android 新版本默认走nl80211,如果驱动编的时候只开了 WEXT 没开CONFIG_IOCTL_CFG80211,nl80211侧就看不到这个wiphy,wpa_supplicant会一直停在CTRL-EVENT-DISCONNECTED。也有可能是 SELinux 拦了wpa_supplicant访问节点。
解决:先确认nl80211侧能看到设备:
# 能列出 phy 编号和接口,说明 cfg80211 注册成功 iw dev iw list | head -30 # 起 wpa_supplicant 时手动指定驱动,并看完整日志 wpa_supplicant -i wlan0 -D nl80211 -c /vendor/etc/wifi/wpa_supplicant.conf -ddiw dev里没有wlan0,回头查编译开关;有的话去看logcat,avc: denied一类的行删掉就好,别用全局 permissive 糊过去,量产出事。
5.5 现象:热插拔一次之后就再也认不到设备
原因:卸载路径没走干净,usb_deregister或者cfg80211注销有残留,第二次枚举时probe直接返回 -EBUSY。淘汰的包里这种情况不少见,尤其是自己加过调试代码、中间return了一堆的地方。
解决:先在卸载之后看一眼残留:
sudo rmmod 8188fu dmesg | tail -20 ls /sys/class/net/ ls /sys/kernel/debug/ieee80211/ 2>/dev/null如果接口目录还在,说明注销不完整。这种情况最稳的办法是不要热插拔,把模块在开机阶段加载好、设备常驻。确实需要支持热插拔的,得去os_dep/linux/usb_intf.c里把disconnect路径逐个资源核对一遍,kfree之后把指针置NULL,这种低级错误在赶工期的代码里非常常见。
6. 进阶:把换内核验证固化成一条脚本
迁这个驱动最耗时间的环节是“换内核 → 重编 → 上板 → 发现某处又不兼容 → 再改”。真正做上两三轮就会发现,报错就那么几类,完全可以提前拦住。我后来把这套流程写成了一个脚本,放在源码目录里,每次换KSRC之后先跑一遍:
#!/usr/bin/env bash # verify_8188fu.sh —— 换内核后的最小验证闭环 # 用法: ./verify_8188fu.sh <ko路径> [接口名] set -u KO=${1:-./8188fu.ko} DEV=${2:-wlan0} FAIL=0 fail() { echo "[FAIL] $*"; FAIL=1; } pass() { echo "[ OK ] $*"; } # 1. 产物存在性与 vermagic 校验 [ -f "$KO" ] || { fail "找不到 $KO"; exit 1; } VERMAGIC=$(modinfo -F vermagic "$KO" 2>/dev/null | awk '{print $1}') RUNNING=$(uname -r) [ "$VERMAGIC" = "$RUNNING" ] \ && pass "vermagic 匹配: $RUNNING" \ || fail "vermagic 不匹配: 模块=$VERMAGIC 内核=$RUNNING" # 2. 卸载旧模块,清干净再加载 rmmod 8188fu 2>/dev/null sleep 1 if lsmod | grep -q "^8188fu"; then fail "旧模块卸载失败,可能有引用未释放" fi # 3. 带完整日志加载,三个省电开关一律关闭 insmod "$KO" rtw_power_mgnt=0 rtw_ips_mode=0 rtw_lps_mode=0 rtw_drv_log_level=4 \ || fail "insmod 返回非零" # 4. 等接口出现,最多等 5 秒 for i in $(seq 1 10); do ip link show "$DEV" >/dev/null 2>&1 && break sleep 0.5 done ip link show "$DEV" >/dev/null 2>&1 \ && pass "接口 $DEV 已创建" \ || fail "接口 $DEV 未出现" # 5. 日志关键词扫描,命中任何一个都要人工看 KEYS="fw download fail|hw ready fail|Unknown symbol|BUG:|Oops|avc: denied" if dmesg | tail -120 | grep -qiE "$KEYS"; then fail "日志命中异常关键词:" dmesg | tail -120 | grep -iE "$KEYS" | head -10 else pass "日志未见异常关键词" fi # 6. cfg80211 侧确认 wiphy 注册成功 if command -v iw >/dev/null 2>&1; then iw dev | grep -q "$DEV" && pass "iw dev 可见 $DEV" || fail "iw dev 看不到 $DEV" iw list 2>/dev/null | grep -qi "HT20" && pass "HT 能力存在" || fail "未见 HT 能力" fi # 7. 扫描一次,确认射频链路真的能收到包 ip link set "$DEV" up CNT=$(iw dev "$DEV" scan 2>/dev/null | grep -c "^BSS") [ "${CNT:-0}" -gt 0 ] && pass "扫描到 $CNT 个 AP" || fail "扫描结果为空,检查天线与射频初始化" [ "$FAIL" -eq 0 ] && echo "==> 全部通过,可以进入关联测试" || echo "==> 有项目未通过,逐条处理后再上板" exit "$FAIL"这个脚本里第 5 步的日志扫描是最值钱的。fw download fail和hw ready fail这两条在正常日志里几乎不出现,一旦出现,基本就等于宣告射频没起来;Unknown symbol说明编译侧和运行侧的内核对不上;BUG:和Oops是内核态崩了,必须看调用栈。把这几个关键词固化成自动检查,比每次手工dmesg | tail翻要可靠得多,尤其是在 CI 里跑的时候。
第 7 步的扫描计数容易被忽略,但它其实是区分“软件全通”和“射频真通”的分水岭。我遇到过接口建出来了、iw list也有 HT 能力、但扫不到任何 AP 的情况,最后查出来是板子上那根 PCB 天线没焊好。驱动层所有指标都正常,射频侧一点信号没有,这种情况只靠看日志是看不出来的。
最后说一个使用上的建议:把这个脚本和第 2 章那三条modinfo、nm -u的静态检查串起来,做成一个pre-flight阶段,编译产物一出来就自动跑,跑不过就不往板子上推。这套流程我用了两年多,省下来的时间主要是省在“少上一次板、少看一次黑屏”上。从那以后我每次换KSRC,不管只是升了个小版本号还是整个平台换了,都强制先把这个脚本跑一遍,跑完再看人肉日志——血泪经验,跳过这一步省下的十分钟,最后往往要花两小时还回去。希望帮到你。
本文还有配套的精品资源,点击获取