简介:这是一份面向嵌入式Linux开发者与系统集成人员的AP6212无线芯片驱动资料,围绕WiFi与蓝牙功能在Linux平台上的适配、移植和调试展开。压缩包共14个文件,以txt移植说明、pdf用户指南、c源码以及bz2、tgz、rar等压缩包为主,整体约5.31MB,内容涉及驱动源码、固件与nvram、蓝牙补丁工具、IMX6平台移植记录和常见问题汇总。现有2038人学习,说明其在AP6212驱动适配场景中具有较高参考价值。读者可从中了解内核接口配置、设备树处理、驱动加载与硬件识别排错思路,也能参考版本差异说明和WiFi/蓝牙功能验证资料;对初学者可借此理解内核模块编译流程,对集成者则可用于排查固件加载失败、连接不稳定等实际问题,适合需要定制驱动或深入学习Linux驱动开发的中高级开发者。
1. 板子回来了,wlan0 却没回来
新板子从贴片厂回来,串口一上电,dmesg滚到最后停在mmc1: card never left busy state,ip link里翻了三遍也找不到 wlan0,只有一颗 sdio 设备孤零零挂在总线上。这是我第一次接 AP6212 时的真实现场。AP6212 是一颗 SDIO WiFi 加 UART 蓝牙的二合一模组,在工控主板、国产 ARM 核心板、车载中控上出现频率极高,做 AP6212 linux 驱动移植几乎是绕不过去的一关。它不算难,但极其依赖板级细节:供电时序差 10ms、NVRAM 里少一行参数、固件文件名差一个数字,表现都是“没网”。这篇就按我自己的调试顺序,把驱动栈、设备树、固件打包和几个高频翻车点一次讲清,适合刚接手板级 bring-up 的驱动工程师,也适合要做多板兼容的产品团队。
2. 驱动栈全景:从 SDIO 枚举到 wlan0 的四层结构
拿到一颗模组,别急着一行行改代码,先把“数据从哪来、到哪去”画清楚。AP6212 在系统里其实是两条完全独立的路:WiFi 走 SDIO 总线,蓝牙走 UART,它们共享的只有电源和一颗 32.768kHz 的低功耗时钟。两条路在驱动层几乎不碰面,所以排查时必须先确定问题在哪条路上,否则很容易在蓝牙节点里找一个 WiFi 的 bug。
2.1 硬件接口:SDIO 走 WiFi,UART 走蓝牙
WiFi 侧是标准的 SDIO 2.0 接口,CLK、CMD 加 D0~D3 四根数据线,模组内部实现的是 SDIO function 1。注意这个“function 1”——设备树里reg = <1>写的不是片选号,而是 SDIO 功能号,写错驱动根本不会去 bind。SDIO 时钟上限一般在 50MHz,实际能不能跑到要看 PCB 走线长度和是否做了等长,我见过为了省两层板把时钟降到 25MHz 才稳定的案子,吞吐掉一点但换来零 CRC。
供电是另一个重灾区。模组通常要两路电:VBAT(3.3V 左右,给射频功放)和 VDDIO(必须和主控 SDIO 的 IO 电压一致,1.8V 或 3.3V)。这两路电压一旦不匹配,现象是“卡能认到,但一发数据就 timeout”,日志里全是brcmf_sdio_bus_txdata: ... failed,看半天以为是驱动 bug,其实量一下 VDDIO 就明白了。
控制信号有四根,记住它们的名字后面 dts 会反复出现:WL_REG_ON拉高给 WiFi 上电,BT_REG_ON给蓝牙上电,WL_HOST_WAKE是模组唤醒主控的中断脚(OOB),BT_HOST_WAKE是蓝牙的。前两根是必须接的,不接就永远枚举不到;后两根不接也能跑,代价是低功耗唤醒失效,进 suspend 之后就得靠应用层定时器醒过来重连。
2.2 驱动分层:mmc core 到 brcmfmac 到 cfg80211
Linux 主线里这颗芯片的 WiFi 驱动叫 brcmfmac,源码在内核树的drivers/net/wireless/下,和brcm80211目录同级。它的分层很清晰:
最下面是 mmc core,负责 SDIO 枚举、上电、时钟配置,它枚举成功后会在/sys/bus/sdio/devices/下挂一个设备节点;往上一层是sdio.c,这是总线适配层,负责把 SDIO 读写封装成 brcmfmac 内部的总线抽象;再往上是core.c、cfg80211.c,这一层是芯片无关的公共逻辑,负责固件下载、wiphy 注册、netdev 创建。
关键在于 brcmfmac 是 FullMAC 架构——MAC 层的大部分工作由芯片固件完成,内核这边只做配置和转发,所以它不用 mac80211,而是直接调 cfg80211 的 fullmac 接口注册 wiphy。这解释了一个常见困惑:为什么lsmod里只看到brcmfmac和cfg80211,找不到mac80211?因为压根不需要。理解了这一点,你就知道用户态能改的东西很有限,iw能配的基本只有信道、功率、省电模式,剩下全靠固件和 NVRAM。
蓝牙侧完全不同,走的是标准 HCI over UART,内核里是hci_uart加协议驱动btbcm。老内核要在用户态用工具把固件推下去,新内核(serdev 框架之后)在设备树里挂一个bluetooth子节点,驱动自己完成上电、下载补丁固件、注册 hci0。两条路唯一的交集是:如果BT_REG_ON和WL_REG_ON共用一颗 LDO,那 WiFi 的电源管理会顺带影响蓝牙,这种板子我建议直接拆成两路。
2.3 固件三件套:bin、txt、clm_blob 各管什么
brcmfmac 是“驱动在核内、智能在固件”的典型,光编好驱动没用,rootfs 里必须放对三个文件。
第一个是.bin,固件镜像本体。文件名里带芯片 ID,比如brcmfmac43430-sdio.bin。这里有个高频陷阱:AP6212 的芯片实际是 43438,但驱动上报的 chip id 常常是 BCM43430/1,所以固件名要用 43430 而不是 43438。到底用哪个,最靠谱的办法是看dmesg里那行using brcm/brcmfmacXXXX-sdio for chip BCMXXXX/X,驱动会告诉你它想要什么名字,照着改就行,别猜。
第二个是.txt,也就是 NVRAM 参数文件。这是板级校准文件,里面存了 MAC 地址、天线配置、功率表、晶体频偏补偿、各国信道限制等一堆东西。同一个模组换一块 PCB,这个文件就得重新调,因为它和天线布局、匹配电路强相关。很多人从别的板子上直接拷一份过来,能起来,但吞吐只有一半、或者远距离丢包严重,问题往往就出在这。
第三个是.clm_blob,监管域数据,决定哪些信道能用、发射功率上限多少。它不加载也能工作,但驱动会打印CLM blob file not found之类的告警,并且在部分国家码下行为不规范。产品要出货的话这个必须补上。
提示:三个文件的路径都在
/lib/firmware/brcm/下,.bin和.txt的文件名前缀必须完全一致,只是后缀不同。驱动是按前缀去找的,少一个就直接 probe 失败。
3. 工程落地:内核配置、固件打包与设备树接线
知道了结构,接下来是把它变成能编译、能烧录的东西。这一段按构建顺序走:先配内核,再放固件,最后写设备树。顺序错了会浪费很多时间,比如设备树写对了但内核没开CONFIG_BRCMFMAC_SDIO,日志里连一行报错都没有,只是 wlan0 不出现。
3.1 内核选项:把 brcmfmac 和 btbcm 编进去
最省事的做法是编成模块,调试期改完insmod就行,不用重烧内核。关键的几个选项如下:
# WiFi 侧 CONFIG_CFG80211=m # 配置面,brcmfmac 依赖它 CONFIG_CFG80211_WEXT=y # 老工具(iwconfig)兼容,产品里建议留 CONFIG_BRCMFMAC=m # 驱动主体 CONFIG_BRCMFMAC_SDIO=y # 总线适配,选 m 也可以,但要保证先加载 CONFIG_BRCMFMAC_PROTO_MSGBUF=y # 新内核走 msgbuf 协议,老内核可能是 BCDC CONFIG_FW_LOADER=y # 固件加载框架 # 蓝牙侧 CONFIG_BT=m CONFIG_BT_HCIUART=m CONFIG_BT_HCIUART_SERDEV=y # 新内核走设备树方式 CONFIG_BT_HCIUART_BCM=y # 芯片专用协议,负责下载补丁固件 # 基础设施 CONFIG_MMC=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y CONFIG_RFKILL=y # 没有它 iw 里看不到 rfkill 状态BRCMFMAC_PROTO_MSGBUF和老的 BCDC 协议不要混,它们对应不同的固件接口,编错了固件能下载但通信会挂。判断依据是内核版本和厂商给的 BSP,别自己拍脑袋改。
编译完加载,用lsmod | grep brcm确认两个模块都在。如果只有cfg80211,说明brcmfmac加载失败,dmesg | tail -30会告诉你缺哪个符号。常见的是cfg80211版本不匹配——BSP 里的驱动和你在编译的 kernel 版本对不上。
3.2 固件与 NVRAM 进 rootfs 的几种做法
手工调试阶段,直接把三个文件scp到板子的/lib/firmware/brcm/最快。做产品就得走构建系统。
用 Buildroot 的话,在package/下建一个自定义包或者直接用BR2_ROOTFS_OVERLAY挂一个 overlay 目录,把固件放进去,构建时自动拷到 rootfs。这个方式最直观,缺点是固件更新要重新烧整个镜像。
用 Yocto 的话,写一个.bb配方,用do_install把文件装到${nonarch_base_libdir}/firmware/brcm/,再把包塞进MACHINE_EXTRA_RRECOMMENDS。好处是固件可以单独出一个 ipk,产线上用opkg install就能升级,不用重刷系统。
如果什么构建系统都不用,纯 shell 脚本打包,记得在打包清单里显式列出这三个文件,并且校验文件大小非零。我踩过一次坑:源文件是个 Git LFS 指针,构建出来的镜像是 130 字节的文本,烧上去之后驱动报firmware file is invalid,查了半天以为是固件版本问题。
NVRAM 文件还有个细节:它必须是纯文本,每行一个key=value,结尾不能有 Windows 换行符。用 git 管理时记得配.gitattributes,否则在 Windows 上编辑过的文件会带\r,驱动解析时会把最后一行参数读错,典型表现是 MAC 地址变成乱码。
3.3 设备树与上电时序:WL_REG_ON、LPO、HOST_WAKE
设备树这块,核心是把三件事说清楚:SDIO 走哪个控制器、模组的电从哪来、中断接到哪个 GPIO。下面是能跑的最小骨架(基于常见的 ARM SoC 写法,节点名按你自己的 SoC 改):
/* 用 fixed-regulator 给 WL_REG_ON 建模,绑到 GPIO */ wl_reg_on: regulator-wl { compatible = "regulator-fixed"; regulator-name = "wl_reg_on"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; gpio = <&gpio1 12 GPIO_ACTIVE_HIGH>; enable-active-high; startup-delay-us = <20000>; /* 模组手册要求的上电稳定时间 */ regulator-always-on; /* 调试期先常开,避免 runtime PM 提前关电 */ regulator-boot-on; }; &mmc1 { status = "okay"; bus-width = <4>; /* AP6212 的 SDIO 是 4 线 */ non-removable; /* 模组焊死在板上,没有插拔检测脚 */ keep-power-in-suspend; /* 进 suspend 不关电,否则唤醒后要重新枚举 */ cap-power-off-card; max-frequency = <50000000>; /* 不稳定就降到 25000000 */ vmmc-supply = <&vcc_3v3_wl>; /* 对应模组 VBAT */ #address-cells = <1>; #size-cells = <0>; brcmf: wifi@1 { reg = <1>; /* SDIO function 1 = WiFi */ compatible = "brcm,bcm43438-fmac"; /* 匹配 brcmfmac 的 of 表 */ interrupt-parent = <&gpio1>; interrupts = <7 IRQ_TYPE_LEVEL_HIGH>; /* WL_HOST_WAKE,没接就删掉这行 */ interrupt-names = "host-wake"; }; };逐项解释几个容易写错的。reg = <1>必须是 1,写 0 匹配不到任何功能。compatible的字符串要和驱动of_device_id表里的完全一致,不同内核版本可能有brcm,bcm43430-fmac之类的别名,写上多个字符串用逗号分隔是最稳的,驱动会按顺序匹配。
vmmc-supply指向模组的主供电,这里的关键是这颗 regulator 必须在 SDIO 枚举之前就上电。如果你把WL_REG_ON挂在vmmc上,那 mmc core 会在上电后立刻发 CMD0、CMD5 探测,而模组从拉高WL_REG_ON到 SDIO 接口就绪通常要 10~20ms,时序不够就会看到card never left busy state。
keep-power-in-suspend和cap-power-off-card这两个属性要一起看。前者保证 suspend 期间不掉电,后者告诉 mmc core 支持断电。产品要做深度休眠的话,反而是要允许断电、靠WL_HOST_WAKE唤醒,这时候就别加keep-power-in-suspend,但要确保中断脚真的接了。
max-frequency是个玄学参数。板子走线长、过孔多、或者 VDDIO 是 1.8V,50MHz 很容易 CRC 报错,日志里是brcmf_sdio_bus_rxctl: ... CRC error。降到 25MHz 一般就干净了,代价是峰值吞吐从 40Mbps 掉到 20Mbps 左右。我一般先在 25MHz 调通功能,最后再往上顶。
注意:不要在 dts 里加
sd-uhs-sdr104之类的 UHS-I 模式。AP6212 不支持,加了会导致电压切换失败,运气好是降速,运气差直接枚举不上。
4. 核心代码拆解:一份能跑的设备树与 probe 关键路径
功能调通之后,回头看代码会发现真正要改的地方其实很少,绝大部分“改驱动”的需求,最后都是改设备树和 NVRAM。这一章把蓝牙节点、上电时序和内核里的 probe 链路拆开看,顺便给一个上电后自检的脚本,可以直接塞进产线测试。
4.1 蓝牙 serdev 节点与固件自动下载
WiFi 搞定之后蓝牙通常很顺,因为btbcm会自动做补丁固件下载。前提是设备树写对:
&uart1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart1_cts_rts>; /* 一定要带硬件流控脚 */ bluetooth { compatible = "brcm,bcm43438-bt"; shutdown-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; /* BT_REG_ON */ max-speed = <1500000>; /* 芯片支持的最高波特率 */ clocks = <&clk_32768>; /* LPO,没有就删掉 */ clock-names = "lpo"; }; };compatible决定了hci_uart会不会加载btbcm协议,写错了 hci0 就不会出现,日志里只有一行很不起眼的hci_uart: unknown protocol。shutdown-gpios的极性很重要,GPIO_ACTIVE_HIGH表示拉高上电,如果你的板子通过三极管反相了,就得写GPIO_ACTIVE_LOW,写错了的现象是上电瞬间蓝牙那一块发烫——因为一直处于异常供电状态。
max-speed配到 1500000 能明显降低蓝牙音频的延迟,但前提是 UART 控制器支持这个波特率且时钟分频误差够小。分不出来就用 921600 或者 115200,先用保守值验证通路,再往上提。
不需要硬件流控的小板子可以去掉pinctrl-0里的流控脚,但代价是高吞吐场景(比如传文件)容易丢包。我一般建议留着,多两根线的事。
老内核没有 serdev,需要在启动脚本里手动下载固件:
# 老内核方案:先给 BT 上电,再用用户态工具推固件 echo 1 > /sys/class/gpio/gpio45/value # BT_REG_ON sleep 0.2 btattach -B /dev/ttyS1 -S 1500000 -P bcm & sleep 1 hciconfig hci0 upbtattach的-P参数指定协议名,-S是速度。这两条命令放到 systemd service 里,After=依赖电源相关的 target,别让它比 regulator 初始化还早跑。
4.2 从 brcmf_sdio_probe 到 wlan0 出现的调用链
内核侧的执行路径值得过一遍,出问题时才知道该在哪打断点。函数名在不同内核版本略有出入,但主干是一致的:
/* drivers/net/wireless/.../brcmfmac/sdio.c,节选,保留原函数名便于对照 */ static int brcmf_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { struct brcmf_sdio *bus; int ret; bus = kzalloc(sizeof(*bus), GFP_KERNEL); if (!bus) return -ENOMEM; /* 第一步:初始化 SDIO 传输层、使能 function、申请中断 */ ret = brcmf_sdio_probe_attach(bus, func); if (ret) { brcmf_err("brcmf_sdio_probe_attach failed: %d\n", ret); goto fail; } /* 第二步:识别芯片,读 chip id / 版本,决定用哪个固件 */ ret = brcmf_chip_recognition(bus->ci, ...); if (ret) { brcmf_err("chip recognition failed\n"); goto fail; } /* 第三步:异步请求固件,回调里才真正注册 wiphy、创建 wlan0 */ ret = brcmf_sdio_firmware_cb(...); if (ret) goto fail; return 0; fail: brcmf_sdio_remove(func); return ret; }这段代码解释了两件事。第一,brcmf_sdio_probe_attach失败意味着总线层就没通,问题在 SDIO 枚举、供电、时钟,跟固件无关,这时候去看/sys/bus/sdio/devices/有没有节点最直接。第二,固件下载是异步的,所以 probe 返回成功不代表 wlan0 已经出现,中间差几十到几百毫秒。写自动化脚本时如果insmod完立刻检查wlan0,会误判成失败,必须轮询等。
固件下载完成后的回调链大致是brcmf_sdio_firmware_cb→brcmf_bus_started→brcmf_cfg80211_attach→register_wiphy→ 创建 netdev。任何一步失败,dmesg里都有对应的 error 行,按行号往回找就行。
4.3 上电自检脚本:把状态一次打全
调试期最烦的是“信息分散在四五个地方”,一会儿看 dmesg,一会儿看 sysfs,一会儿看 rfkill。写个脚本一次打全,能省掉大量来回:
#!/bin/sh # wifi-bt-smoke.sh:上电后跑一遍,输出关键状态 set -u echo "=== 1. SDIO 设备 ===" ls /sys/bus/sdio/devices/ 2>/dev/null || echo "无 SDIO 设备,检查 WL_REG_ON 和 mmc 节点" echo "=== 2. 固件加载记录 ===" dmesg | grep -iE 'brcmfmac|firmware' | tail -20 echo "=== 3. 网卡 ===" if [ -e /sys/class/net/wlan0 ]; then ip link show wlan0 iw dev wlan0 info 2>/dev/null else echo "wlan0 不存在" fi echo "=== 4. rfkill 状态 ===" rfkill list echo "=== 5. MAC 地址 ===" cat /sys/class/net/wlan0/address 2>/dev/null echo "=== 6. 蓝牙 ===" hciconfig -a 2>/dev/null || echo "hci0 未注册"脚本本身没什么技术含量,价值在于把排查顺序固化了:先看总线有没有枚举(第 1 段),再看固件有没有加载(第 2 段),再看网卡(第 3 段),最后看射频是否被软阻塞(第 4 段)。这个顺序不能颠倒,因为后面的状态都依赖前面的成立。
第 4 段经常被忽略。rfkill list里如果Soft blocked: yes,ip link set wlan0 up会直接失败,日志里只给一句Operation not permitted,很容易误判成权限问题。解开就是rfkill unblock wifi,但根因可能是某个电源管理服务在开机时把无线关了,得去查对应的 service。
第 5 段看 MAC 地址,如果全是00:00:00:00:00:00或者一批板子都一样,后面 DHCP 阶段必然出问题,这个放到第 5 章细说。
5. 避坑与排查:五个真实翻车现场
以下五条,按我遇到的频率排序。每条都给出“现象、原因、解决”,遇到问题时对着现象找就行。
5.1 SDIO 认不到卡:card never left busy state
现象:上电后dmesg最后一行停在mmc1: card never left busy state或者mmc1: error -110 whilst initialising SD card,/sys/bus/sdio/devices/是空的。
原因:模组没上电,或者上电时序不够。最常见的是WL_REG_ON这根线压根没在设备树里配,或者配了但startup-delay-us只有几毫秒。另一种是WL_REG_ON被接到了 mmc 控制器的vmmc上,但vmmc是常开的,模组在 mmc core 探测时还没准备好。
解决:先用万用表量WL_REG_ON在上电瞬间有没有拉高,没有就是 dts 或 GPIO 复用的问题。有拉高但时序不够,把startup-delay-us加到 20000 以上,或者给 regulator 加regulator-boot-on让它在上电时就打开。还不行就把max-frequency降到 25MHz 试试,排除走线问题。
5.2 能枚举到 SDIO,但 probe 报固件找不到
现象:/sys/bus/sdio/devices/下有节点,但dmesg里是brcmfmac: brcmf_fw_alloc_request: ... failed或者direct firmware load for brcm/brcmfmac43438-sdio.bin failed with error -2。
原因:固件文件名不对,或者文件没进 rootfs。AP6212 这类模组驱动上报的 chip id 常常是 BCM43430 系列,但很多人按芯片丝印写成 43438,名字对不上就找不到。
解决:看dmesg里那行using brcm/brcmfmacXXXX-sdio for chip BCMXXXX/X,照着驱动要的名字建软链接。例如ln -sf brcmfmac43430-sdio.bin brcmfmac43438-sdio.bin,两个名字都留着,换模块不用改 dts。再用ls -l /lib/firmware/brcm/确认文件大小不是 0、不是 Git LFS 指针。
5.3 wlan0 出来了,但一扫描就崩
现象:iw dev wlan0 scan能跑,但扫不到任何 AP,或者扫几次之后dmesg出现firmware trap、brcmf_fw_crashed,网卡直接消失。
原因:NVRAM 参数和实际硬件不匹配。常见的是从别的板子直接拷的 NVRAM,里面的功率表和天线配置对不上,或者缺少boardtype、boardflags这类关键项。加载了错误的天线配置,射频前端会工作在异常状态,扫频时功率超标触发固件自我保护。
解决:找模组供应商要对应模块批次的参考 NVRAM,只保留其中有意义的部分(MAC、晶体补偿、天线配置),不要整份照抄。改完 NVRAM 后必须重新下载固件才生效,rmmod brcmfmac && insmod brcmfmac或者重启。
5.4 一批板子 MAC 地址全一样,DHCP 拿不到 IP
现象:单板测试一切正常,产线上十块板子一起上电,路由器分配的 IP 互相打架,dmesg里是eth0: link up但udhcpc一直超时。
原因:NVRAM 里没有写 MAC 地址,驱动用了默认值,而默认值在同一批固件里是固定的。也可能写了一个模板 MAC 但没做产线烧录。
解决:优先用芯片内置的 OTP 或者从主控的 efuse、CPU ID 派生一个稳定的 MAC,在厂商提供的接口里写死。如果不行,就在产线工装里做一次烧录,把 MAC 写进 NVRAM 或者单独的分区,启动脚本里读出来覆盖。brcmfmac也支持模块参数brcmfmac.mac_addr,但那是全局的,多板场景没法用。
提示:MAC 地址第一个字节的最低位是单播/组播标志,第二个最低位是本地管理标志。自己派生的时候记得把本地管理位置 1,避免和厂商 OUI 冲突。
5.5 蓝牙 hci0 不出现,或者出现后立刻掉线
现象:hciconfig -a里没有 hci0,或者有 hci0 但状态是DOWN,dmesg里有Bluetooth: hci0: command 0x0c03 tx timeout。
原因:三个可能。一是BT_REG_ON没上电或时序不对;二是 UART 的流控脚没配,芯片在等 CTS 一直不发;三是btbcm没编进去,协议不匹配,固件没下下去。
解决:先量BT_REG_ON,再用cat /sys/kernel/debug/gpio看这根脚的状态。流控引脚检查 dts 里的pinctrl-0有没有包含 CTS/RTS。最后确认内核配置里CONFIG_BT_HCIUART_BCM=y,并且dmesg里有Bluetooth: hci0: BCM: chip id XX这类下载记录,没有就说明固件没走通。
6. 二次开发:多板兼容、MAC 策略与自动化回归
功能跑通只是起点。真正花时间的是把一套代码维护到十几块不同板子上,同时保证产线能自动测、售后能远程判故障。这几件事我踩过的坑比驱动本身还多。
第一件是多板兼容。不要给每块板子复制一份完整 dts,那样改一个参数要改十几处,早晚出错。正确做法是把 AP6212 相关的节点抽成一个.dtsi,板级 dts 只 include 它,然后用&wl_reg_on这种引用去改 GPIO。差异特别大的板子(比如有的接了WL_HOST_WAKE、有的没接),用设备树 overlay 在启动时套上去,编译期不用区分。这样主 dtsi 能保持干净,出了问题也只需要在一个文件里查。
第二件是 MAC 策略。上面说过不能靠默认值,但也不能简单用随机数——每次重启 MAC 变化,路由器上的静态绑定就废了。我的习惯是从主控的 efuse 或者 SoC 唯一 ID 派生:取 6 字节,第一字节置为本地管理地址(0x02),后面接 ID 的低 40 位。这样既保证全局唯一,又不需要额外的烧录工序。派生逻辑放在启动脚本里,用ip link set wlan0 address在下一次up之前生效,比改 NVRAM 灵活。要注意的是,全志、瑞芯微这类平台读唯一 ID 的节点路径不一样,得做成按 SoC 分支的配置项。
第三件是回归。我现在的做法是把这个自检脚本接进产线测试工装:上电、跑脚本、检查六项输出是否符合预期、把结果写进测试记录。测试项里最容易被跳过的是“扫描到至少 3 个 AP”,但它恰恰能抓到 NVRAM 参数不对这种隐蔽问题——网卡能起来、能拿 IP,但灵敏度比正常板子低 10dB,用户用一周才发现。产线多花 5 秒,能省掉后面一堆返修。
还有一个技巧值得提:把固件版本、NVRAM 校验和、驱动模块版本在启动时打进dmesg或者一个固定文件里。售后拿到日志,一眼就能判断是固件旧了还是 NVRAM 被改过。我以前吃过亏,客户反馈“同一批板子有的能连有的不能”,查了两天才发现是产线中途换过一次 NVRAM,而系统里没有任何记录能证明这一点。从那以后我每次做板级 bring-up,都强制把这三个值打进日志再交付,宁可多占几行输出。
这套流程跑顺之后,接一颗新模组的周期能从两周压到两三天。真正麻烦的从来不是写代码,而是把“这台能跑”变成“每台都能跑”。希望帮到你。
本文还有配套的精品资源,点击获取