做RK平台Android整机方案的朋友,几乎没有不碰WiFi模组适配的。AIC8800这颗芯片近两年在商显、网关、工业平板上出现频率很高,双频WiFi 6加BLE 5.0,走SDIO接口,性价比确实能打。但我见过太多团队从画板子到量产的周期里,卡得最久的不是天线射频,而是“系统跑起来了但wlan0死活不出来”这类系统集成问题。从DTS设备树描述,到内核驱动probe、SDIO固件下载,再到Android HAL把WiFi开关拉起来,中间任何一环出错,用户在设置里看到的结果都一样:搜不到WiFi,或者开关一直打不开。
这篇文章就把这条启动链完整拆开讲透。AIC8800在RK Android系统上从设备树配置、驱动加载、固件下载、HAL启动到Framework能正常扫描SSID,每个环节要做什么、为什么这么做、踩坑点在哪里,一次性说清楚。适合正在做RK平台BSP、驱动、系统集成的工程师参考,也适合刚接手WiFi适配任务、想快速建立全局观的同行。
1. AIC8800在RK Android系统里的位置
1.1 一颗WiFi 6 Combo芯片的基本盘
AIC8800是爱科微(AICSemi)推出的WiFi 6 + BLE 5.0 Combo芯片,支持802.11a/b/g/n/ac/ax,2.4GHz和5.8GHz双频,WiFi部分走SDIO 3.0接口,蓝牙部分走UART/PCM接口。相比瑞昱RTL8821、正基AP6256这些老方案,AIC8800代际更新,支持HE80,理论吞吐更高,在RK3568、RK3588这类中高端平台上跑WiFi 6速率没有瓶颈。很多模组厂做成邮票孔或LCC封装,板上设计并不复杂。
选这颗芯片做整机,主要看中的是三点:一是双频WiFi 6满足产品spec需求,二是SDIO接口在RK Linux/Android生态里非常成熟,三是成本比同规格大厂方案低一截。但便宜有便宜的代价,它的驱动、固件、文档完善程度不如一线大厂,遇到问题很多地方要自己趟。这也是我写这篇文章的原因——把这几年在AIC8800上踩过的坑集中整理出来。
1.2 从芯片到系统:一条跨越四层的启动链
很多人以为WiFi模组适配就是“把驱动编进内核,开机就有wlan0”,实际完全不是这么回事。AIC8800从硬件上电到Android界面出现WiFi开关,中间要经过至少四个层次:
- 设备树层(DTS):告诉内核WiFi模组挂在哪个SDIO控制器上、供电怎么控制、复位脚是哪个、唤醒脚是哪个。
- 内核驱动层:SDIO总线枚举到这颗芯片后,驱动负责下电时序、加载固件、注册cfg80211和netdev,最终生成wlan0。
- Android HAL层:系统通过HAL服务管理WiFi的生命周期,拉起wpa_supplicant,转发Framework下发的扫描、连接指令。
- Framework/应用层:WifiService负责策略和UI,把“打开WiFi”这个动作翻译成上面几层的联动。
这四层里任何一层出问题,现象几乎都一样——WiFi不可用。所以排查的时候必须有全局视角,不能只盯着驱动代码看。我以前带过几个刚入行的同事,一上来就翻驱动源码,翻了两天也定位不到问题,最后发现是DTS里一个GPIO和别的模块冲突了。这就是典型的“把系统问题当成驱动问题来查”。
2. DTS设备树:硬件信息的第一道关卡
2.1 电源、复位与唤醒:GPIO怎么配才不翻车
DTS(Device Tree Source)本质上就是一块硬件的“体检表”,内核启动时靠它知道板上接了什么东西、每个引脚怎么用。AIC8800这种外置SDIO WiFi芯片,在DTS里最核心的就是三组GPIO:使能脚(enable/power)、复位脚(reset)、唤醒脚(host_wake)。
使能脚控制模组的供电开关,一般接一个MOS管电路,GPIO拉高后模组才上电。复位脚在初始化时要有一个完整的低电平→高电平过程,确保芯片从复位状态正常启动。这两个脚经常放在sdio_pwrseq节点里,由内核的mmc-pwrseq-simple框架统一管理。为什么要单独做pwrseq而不是在驱动里控制?因为SDIO控制器在总线枚举之前就必须保证设备已经上电,这个时序比驱动probe更早,内核框架在sdhci初始化时会自动执行pwrseq的上电序列。
唤醒脚是WiFi芯片通知SoC的“我有数据要处理”的中断脚,一般接到SoC的GPIO,配置为输入、上下拉根据模组手册来。这里有个最常见的坑:如果唤醒脚的电平极性配反了,或者这个GPIO刚好被其他模块复用,那么WiFi驱动可以正常probe,但系统进入休眠后就再也醒不过来了,或者WiFi扫描时驱动等不到中断,表现为扫描超时。
我之前在一个项目上遇到过:WiFi开起来能连上AP,但只要系统深度休眠一次,醒来之后WiFi必然是“已保存但无法连接”状态。查了三天,最后发现是唤醒GPIO没有配置wakeup-source属性,导致这个中断不能把CPU从深度睡眠中唤醒。加上属性、确认中断号没问题后,休眠唤醒功能才算正常。
2.2 SDIO控制器节点:几个属性决定模组能否被枚举
DTS里SDIO控制器的配置有四个属性,几乎每个都会出问题,我一个个说。
第一个是bus-width = <4>,WiFi模组一般用SDIO 4-bit模式,4条数据线。有人图省事不写,内核默认按1-bit枚举,能识别但速度惨不忍睹,WiFi跑起来也就几十Mbps。第二个是non-removable,SDIO WiFi是焊死在板子上的,不是可插拔的SD卡,不加这个属性的话,内核会把它当成可移动设备处理,系统休眠时可能会触发移除逻辑,醒来后设备直接消失。
第三个是broken-cd,这个属性特别容易被忽略。SD卡有卡检测引脚(CD),WiFi模组没有。如果控制器自带卡检测逻辑而板子上没有对应的检测电路,内核就会一直认为“没有卡插入”,SDIO总线上设备也不会被枚举。加上broken-cd相当于告诉内核:不要检测卡,直接尝试枚举。
第四个是keep-power-in-suspend,这个跟休眠相关。不加的话,系统suspend时SDIO控制器会切断电源,WiFi芯片直接掉电,唤醒后内核再枚举一次。听起来没什么,但重新枚举意味着WiFi要重新下载固件、重新连接AP,用户体验就是“休眠一次WiFi断一次”,连上还得等好几秒。加了keep-power-in-suspend,休眠时保持供电,WiFi连接不断,体验好很多。
还有一个sd-uhs-sdr104,开启SDIO 3.0的高速模式。很多模组标称支持UHS-I SDR104,但DTS里没开,实际跑在默认的25MHz时钟下,结果就是WiFi内网测速怎么都上不去,跟百兆网口差不多。这个可以按模组支持的最高模式配,一般RK平台配sdr104或者sdr50都没问题。
2.3 RK3568上的一份DTS参考片段
RK平台不同芯片的SDIO控制器名称略有差异,RK3568上一般用sdmmc1挂SDIO WiFi。下面是一份可参考的节点配置,覆盖了上面说的关键点:
&sdio_pwrseq { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&wifi_enable_h>; reset-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_LOW>; post-power-on-delay-ms = <50>; }; &sdmmc1 { status = "okay"; bus-width = <4>; non-removable; broken-cd; cap-sdio-irq; keep-power-in-suspend; sd-uhs-sdr104; pinctrl-names = "default"; pinctrl-0 = <&sdmmc1_clk &sdmmc1_cmd &sdmmc1_bus4>; vmmc-supply = <&vcc_wifi>; vqmmc-supply = <&vcc_wifi_io>; };注意vmmc-supply和vqmmc-supply,一个管主电源,一个管IO电平。AIC8800的IO电平一般是1.8V或3.3V,具体看模组原理图。这两个电源没配好,轻则WiFi掉卡,重则无法枚举,而且这种问题用示波器查电平很难发现,因为有时候静态电平看着是对的,一跑高速数据就出错。
还有一个点,pinctrl-0里的sdmmc1_clk、sdmmc1_cmd、sdmmc1_bus4这些引脚,必须在SoC的pinctrl里确认没有被复用成其他功能。特别是RK3568这种引脚复用特别灵活的芯片,一个bank的引脚可能同时支持SDIO、UART、I2C、PWM,上层的其他驱动如果在DTS里先声明了这些引脚,SDIO这边就会配置失败,内核日志里会出现pin already requested之类的报错,很容易排查。
3. 内核驱动与固件下载:wlan0从哪里来
3.1 驱动probe流程:从request_firmware到chip boot
DTS配好之后,系统启动时SDIO控制器会把总线上枚举到的设备信息和内核里的sdio_driver匹配。AIC8800的驱动一般由模组厂提供源码,编译成aic_wifi模块或者直接编进内核。这个驱动的主要工作,比很多人想象中要多得多。
probe函数的第一步是确认芯片型号。驱动通过SDIO命令读取芯片内部寄存器,拿到chip id,然后和驱动支持的列表比对。对不上就直接返回失败,常见原因是固件版本和驱动版本不匹配,或者芯片被识别成另一个型号。第二步是请求电源域和GPIO,这一部其实很多工作DTS里的pwrseq已经做掉了,驱动在这里主要是拿到电源句柄和中断号,注册中断处理函数。
第三步是固件下载。AIC8800的固件不是烧录在芯片内部Flash里的,而是在每次上电时由驱动从文件系统读取,然后通过SDIO命令逐段写入芯片的SRAM里执行。这也是为什么"固件放哪个目录"特别重要,放错地方驱动就加载不到,芯片就一直停在boot状态,后面所有流程都不会发生。
固件下载完成后,驱动会等芯片发出boot完成信号,之后才能继续注册系统接口。这一步有个经典问题:如果板上的电源纹波太大,或者上电时序不稳定,芯片经常会boot一半就死掉,驱动一直等不到完成信号。表现就是dmesg里反复打印下载失败,然后驱动给你来个probe failed,wlan0自然就不存在。
3.2 固件文件、路径与版本匹配
AIC8800的固件一般由多个文件组成,常见的有fw.bin、fw_data.bin、fw_patch.bin等。驱动通过request_firmware接口加载,这个接口在内核里有一套标准的搜索路径。
在Android系统上,设备厂商的固件一般放在/vendor/etc/firmware/下面,如果驱动代码里写的路径是相对路径aic8800/fw.bin,那么完整路径就是/vendor/etc/firmware/aic8800/fw.bin。这个目录在编译vendor image时由PRODUCT_COPY_FILES机制打包进去。
实际项目中我见过太多次“WiFi起不来”是因为固件根本不在系统里。有时候开发阶段改错了mk文件,固件没有打包进vendor.img,烧录后系统里找遍整个文件系统都找不到fw.bin。这种问题排查起来其实最快:先看/vendor/etc/firmware/下有没有文件,再看驱动日志里报的路径是什么。
固件版本和驱动版本必须配套。芯片厂一般会给出驱动和固件的版本对应关系。如果只升级了驱动而固件没跟着换,或者反过来,chip boot阶段就会出现各种诡异问题,有的直接拒绝启动,有的能启动但WiFi扫描到AP后连接不上,还有的会频繁断流。AIC8800的固件和驱动配套信息通常在模组厂发的release notes里,升级固件前一定先确认匹配关系。
3.3 cfg80211、netdev与rfkill的注册顺序
固件下载完成、芯片正常启动后,驱动开始注册上层的网络接口。这块做的事情包括:注册cfg80211的wiphy设备、创建netdev接口wlan0、初始化rfkill设备。
cfg80211是Linux内核的无线配置管理框架,负责上层和驱动之间的命令通道,比如扫描、连接、断开这些操作,最终都是通过cfg80211的ops回调到AIC驱动里。wiphy注册成功之后,系统里才真正出现“无线设备”这个概念。wlan0这个网络接口是由驱动创建的,通常在注册wiphy之后调用alloc_netdev和register_netdev生成。
rfkill是内核的无线开关机制。Android上层通过HAL调用rfkill接口来“拉开关”。如果rfkill初始化失败,或者rfkill状态和实际状态不一致,就会出现一个很经典的现象:ip link set wlan0 up能执行成功,但Android设置里的WiFi开关怎么都打不开。因为这个开关状态不对,Framework认为WiFi被硬件禁用了。
驱动加载成功、wlan0出现之后,在内核层面整个WiFi设备就算ready了。你可以通过ifconfig -a或者ls /sys/class/net/看到wlan0,通过iw dev看到无线设备信息。到这个阶段,DTS和内核的部分就结束了,后面是Android用户空间的事。
4. Android HAL与Framework:打通“最后一公里”
4.1 WiFi HAL的架构演进与vendor侧实现
Android系统的WiFi架构经历了多次演进。早期是直接由Framework通过socket连wpa_supplicant,后来为了分离硬件厂商代码,Google在Treble化之后把WiFi相关的硬件接口抽象成了HAL(Hardware Abstraction Layer)。Android 8到11这一段主要使用HIDL版本,接口包名类似android.hardware.wifi@1.0,到了Android 12之后,新平台开始迁移到AIDL版本android.hardware.wifi.aidl。
RK平台的SDK里通常同时保留了HIDL和AIDL两套兼容代码,具体启用哪套由manifest里的VINTF配置决定。对于AIC8800这种外置WiFi芯片,HAL的核心任务其实并不复杂:一是加载/确保内核驱动已加载,二是管理wpa_supplicant进程的启停,三是向Framework暴露WiFi相关的接口服务。
很多厂商在实际做的时候,直接把HAL实现得很薄,甚至通过一个init.rc服务把wpa_supplicant拉起来,HAL只是作为一个状态机去协调。这样做的优点是简单直接,缺点是SELinux和权限问题比较多,因为Framework对HAL的调用路径越长,越容易出现权限拒绝。我在RK3568项目上就遇到过:HAL服务起不来,原因是SELinux策略没有给vendor进程对应的socket访问权限,logcat里能看到avc denied报错。
4.2 wpa_supplicant与HAL的分工协作
搞清楚HAL和wpa_supplicant的关系,是理解Android WiFi架构的关键。
wpa_supplicant是一个用户空间的守护进程,负责处理WPA/WPA2/WPA3认证、密钥协商、扫描结果缓存这些具体WiFi协议栈工作。它通过nl80211 socket和内核里的cfg80211通信,nl80211再调用到AIC驱动。可以简单理解成:wpa_supplicant是内核和Framework之间的协议翻译器。
HAL在这里的角色是“服务总管”。以RK平台常见的实现为例,WiFi HAL服务启动时会做这几件事:检查wlan0是否存在,加载wpa_supplicant配置文件,然后fork并启动wpa_supplicant进程。Framework下发“打开WiFi”请求时,HAL把命令通过wpa_supplicant的控制接口(ctrl_iface)转达过去。wpa_supplicant完成扫描或者连接状态变化时,又通过事件回调通知HAL,HAL再上报给Framework。
这套机制里最常出问题的是控制接口的socket路径和权限。wpa_supplicant的ctrl_interface默认指向/data/misc/wifi/sockets,这个目录的owner必须是wifi用户,权限要正确。如果目录权限不对,HAL去连接socket就会失败,现象是Framework上报WiFi已开启,但实际扫描不到任何网络,因为命令根本没到wpa_supplicant那里。
4.3 MAC地址、射频校准与SELinux的隐藏坑
除了启动流程,还有三个隐藏问题值得单独拿出来说。
第一个是MAC地址来源。WiFi模组本身可能没有烧录MAC地址,或者MAC地址存在模组内部的OTP区域,但驱动不一定去读。RK平台的常见做法包括:从DTS的某个节点里读MAC地址,从/persist分区读,或者在系统启动时通过属性系统传递。如果MAC地址为空或者非法,Android系统会拒绝启动WiFi,或者每次启动都生成随机MAC。AIC8800的MAC读取逻辑要看驱动实现,我曾经遇到过驱动把全零MAC当成有效值传给上层,然后上层所有关于MAC的策略全部失灵。
第二个是射频校准。WiFi芯片出厂时会有功率校准数据,一般存放在模组内部Flash或者单独的分区里。驱动probe时会去读这个校准数据,如果读不到或者数据损坏,WiFi能扫描到AP但发射功率不对,表现为距离近了信号很好,隔一堵墙就基本没信号,而且功耗明显偏高。AIC8800的校准数据管理方式需要问模组厂确认,有的模组是把校准数据作为固件的一部分一起打包,有的则需要单独烧录。
第三个是SELinux。Android的SELinux enforcing模式下,所有进程都被限制在白名单策略里。AIC8800的HAL服务、wpa_supplicant、wificond这几个进程,都要有对应的te文件和allow规则。新增一个vendor服务时最容易忘记写SELinux策略,结果服务能手动启动,但系统开机时被SELinux拦下来。排查方法很直接:dmesg和logcat里搜avc: denied关键字,能看到是哪个进程访问了不该访问的资源。RK官方SDK里一般有wifi相关的SELinux模板,直接改包名就行。
5. 从按下电源键到连上WiFi:全链路串联与排障手册
5.1 完整启动时间线:每个阶段该看到什么
把前面几章的内容串起来,AIC8800在RK Android系统上的完整启动链大概是这样一条时间线,每一步都有对应的验证手段,方便你确认到底卡在哪。
- 电源键按下,U-Boot阶段:U-Boot初始化SDIO控制器,部分方案会在这里就给WiFi模组上电,以便支持网络启动。这个阶段可以看串口日志里有没有SDIO相关的初始化打印。
- Kernel启动早期:DTS被解析,SDIO控制器注册,pwrseq执行上电序列,GPIO拉高、延时、复位释放。这个阶段如果复位时序有问题,后面SDIO枚举就会失败。
- Kernel驱动probe:SDIO总线枚举到AIC8800,驱动匹配成功,开始请求固件并下载。dmesg里应该能看到驱动打印的chip id和固件下载信息。
- netdev注册:wlan0出现。此时可以用
ls /sys/class/net/验证。 - Android init阶段:init进程根据init.rc启动wifi HAL服务和wpa_supplicant。用
ps -A | grep wpa和ps -A | grep wifi验证进程是否存在。 - Framework启动:WifiService初始化,通过HAL查询WiFi能力,设置WiFi状态为available。此时设置里的WiFi开关应该能打开。
- 扫描与连接:Framework下发扫描命令,HAL转给wpa_supplicant,wpa_supplicant通过nl80211下发给驱动。UI上能看到SSID列表,点击连接后经过四次握手完成连接。
每一步都有自己独立的日志输出。串口log主要看kernel部分,logcat看Framework和HAL部分,这两个日志要放一起看才能把问题定位完整。
5.2 高频问题与排查速查表
我在多个AIC8800项目上遇到过的问题,大部分可以归结到下面这几类,做成一张表方便查阅。
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 开机后没有wlan0,dmesg里没有AIC驱动probe信息 | DTS里GTXPIN冲突、SDIO控制器没enable。pwrseq上电时序不对 | 查dmesg里sdhci和pinctrl报错,检查DTS节点status,量硬件供电波形 |
| 有wlan0但无法扫描到AP,或者扫描列表为空 | 固件和驱动版本不匹配、天线没接好、cfg80211状态异常 | 用iw dev wlan0 scan手动扫描,看dmesg固件下载是否有报错 |
| WiFi能连接但速度极慢,内网测速上不去 | SDIO没有跑高速模式,DTS缺少sd-uhs-sdr104,或vqmmc电平不对 | 检查SDIO时序配置,查看链路速率iw dev wlan0 link |
| 休眠唤醒后WiFi断开且无法重连 | 缺少keep-power-in-suspend,wakeup GPIO配置不正确,固件掉电 | 查DTS电源属性,确认wakeup中断是否有触发 |
| Android设置里WiFi开关是灰色/打不开 | rfkill状态异常,SELinux拦截,HAL没起来 | 看logcat里的avc denied,手动ip link set wlan0 up,检查rfkill状态 |
| wpa_supplicant进程反复重启 | config文件路径错误、socket目录权限不对 | 看logcat中wpa_supplicant启动参数和报错信息,检查/data/misc/wifi目录权限 |
这个表看着简单,实际排查的时候要按顺序来:先硬件后软件,先kernel后Android,先Driver后HAL。我以前经验不足的时候,遇到WiFi问题先去看Framework代码,浪费了很多时间。后来养成一个习惯:不管现象多复杂,永远从dmesg看起,确认内核这一层是干净的,再往上查。这个习惯帮我少走了很多弯路。
AIC8800在RK平台的启动链,说到底就是“先让芯片活过来,再让系统认识它,最后让Android用得起来”。每一步都有明确的验证点,只要按层拆解、逐层确认,大多数问题都能在半小时内定位到具体环节。我现在做项目,WiFi适配这块基本不会再被卡住了,因为这条链路上每个阶段的日志特征、常见坑、排查入口都已经形成了一套固定的流程。希望这篇文章能把这套流程完整地传递给你,下次遇到AIC8800或者其他外置SDIO WiFi芯片,可以少踩几个坑。