简介:在嵌入式Linux平台的无线网卡适配场景中,这套基于MT7663芯片的USB转WiFi驱动源码包尤其适合海思3531等ARM64环境下的工程师,适合具备基础Linux内核编译经验、希望缩短网卡适配周期的开发者。压缩包内含已配置好的Makefile.aarch,省去手工搭建工具链和内核目录的步骤,可直接修改CROSS_COMPILE、LINUX_SRC、DRIVER_DIR三个变量后执行make -f Makefile.aarch生成驱动。包内共453个文件、约7.62MB,以h头文件与c源码为主体,附带预编译的o对象文件、bin固件、ko驱动模块及Makefile构建脚本,覆盖编译、链接与固件加载链路;其中针对无线扩展与OID管理的核心实现可支撑驱动二次开发与模块裁剪。附带的bin固件配合文档可用于芯片patch加载,pdf/xlsx说明文档可辅助完成参数核对与编译前检查。已有424人学习下载,对需要在不同ARM平台间移植驱动、快速产出可加载ko的开发者有直接参考价值。
1. 项目概述与方案选型
1.1 核心需求解析
我一直觉得,搞嵌入式Linux的人手里如果没有几块USB WiFi模块,出门都不好意思跟人打招呼。这次要分享的项目比较直接:MT7663芯片的USB转WiFi驱动源码,以及一套已经配置好、能直接用来做交叉编译的makefile。
MT7663是联发科推出的一款支持WiFi 6的芯片方案,虽然现在是WiFi 6普及期,但在嵌入式设备上能用上WiFi 6的还不多见。市面上主流的USB WiFi方案还是以Realtek的RTL8188、RTL8812系列以及MT7601、MT7612为主,MT7663算是比较新的选择。它同时支持USB接口和SDIO接口两种连接方式,不过这里我们只聊USB版本。
这个项目的核心价值在于:驱动源码已经帮你整理好,makefile也已经针对交叉编译做了预处理,你不用再去翻联发科那堆乱糟糟的官方代码仓库,也不用在makefile里自己折腾交叉编译工具链的路径变量。拿来就能用,改几个变量就能编出目标平台的内核模块。
适合谁来参考?正在做嵌入式Linux产品的工程师,尤其是用RK、全志、飞腾或者其它ARM平台做主控,需要给设备增加WiFi能力的朋友。做物联网网关、智能终端、工业平板这类产品的同学,也应该能从这套配置里省下不少时间。
1.2 为什么选MT7663而不是其它方案
实话说,如果不是特别在意WiFi 6的支持,市面上很多廉价方案也能凑合。但MT7663有比较明显的几个优势:
吞吐性能有保障。MT7663双频并发,2.4GHz和5GHz都支持,理论速率能跑到600Mbps左右。对于嵌入式设备来说,这个带宽做视频传输、大文件同步都够用了。相比之下,MT7601只能跑2.4GHz单频,最高才150Mbps,场景受限比较明显。
Linux内核社区支持度不错。联发科在MT7663的驱动适配方面比前几代产品用心一些,代码质量和提交频率都算在线。虽然官方还是以out-of-tree方式发布驱动,但有总比没有强。
SDIO和USB两栖。如果你的产品设计上需要灵活切换接口形态,同样是这颗芯片,改一下硬件接口和对应驱动文件就行,软件层面的改动成本很低。
不过有个点要提前说明:MT7663的USB版本驱动,联发科官方在GitHub上有开源仓库,但这颗芯片的固件是专有的,firmware文件需要单独获取。我在项目里已经处理好了固件的路径和加载方式,具体细节后面会讲。
2. 交叉编译环境搭建
2.1 工具链的选择与配置
交叉编译Linux内核模块,第一步肯定是搞定工具链。我这次用的环境是Ubuntu 20.04 LTS主机,目标平台是ARM Cortex-A7架构的板子。选择工具链时有几个注意事项值得展开说说。
首先是版本匹配问题。不要图省事随手装个最新的GCC版本就开干,内核模块编译对编译器版本敏感度挺高,尤其是Linux 4.x以上的内核版本,编译时经常会因为GCC版本过高导致一些历史遗留代码报错。经验法则是:目标内核用什么版本的GCC编出来的,你就尽量用相同或者相近的版本去做模块编译。
我在项目里用的是Linaro提供的交叉编译工具链,版本是gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf,这是一套在嵌入式领域用得比较多的ARM 32位工具链。如果你的目标平台是ARM 64位,选aarch64-linux-gnu那套就行。
安装方式很简单:
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz sudo mv gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf /opt/解压之后把bin目录加进PATH环境变量,或者更稳妥的方式是在makefile里直接写死工具链的绝对路径,避免因为环境变量问题导致找不到编译器。
2.2 内核源码树准备
交叉编译内核模块,必须有对应的内核源码树。这里说的"对应"包含两层含义:第一层是内核版本要一致,比如你的板子跑的是Linux 4.19.71,那最好找到这个版本的内核源码;第二层是内核配置要匹配,编译模块时需要用到内核源码树中的头文件和Makefile,而这些文件的生成依赖于内核的配置文件。
操作方法分成两种情况:
如果你的板子BSP包里有完整的内核源码,那么先交叉编译一遍内核,确保内核源码树是完整的:
cd kernel-source make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- xxx_defconfig make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8编译完内核后,源码树里会生成Module.symvers、arch/arm/include/generated等目录和文件,这些是编译外部模块时必须要用到的。
但也有一种情况是板厂只给你了内核镜像,没给完整源码,这种情况下可以尝试用/proc/config.gz导出内核配置,然后找对应版本的内核源码树重新生成一份可用的源码目录。不过这条路比较曲折,最好还是找板厂要源码。
我在makefile里通过KDIR变量指向了交叉编译好的内核源码树路径,这样编译模块时就能正确引用到目标平台的配置和头文件了。
3. Makefile核心逻辑与配置详解
3.1 整体结构解读
这套makefile的骨架其实是联发科官方驱动里自带的,但官方版本做得很粗糙,默认配置是给x86平台编译用的。我做的改造主要有三块:交叉编译变量的引入、目标平台内核源码路径的适配,以及固件安装路径的处理。
makefile的核心结构如下:
# 交叉编译工具链前缀 CROSS_COMPILE ?= /opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- # 目标平台内核源码目录 KDIR ?= /home/user/kernel-source # 模块安装目录 MODULE_INSTALL_PATH ?= /lib/modules/$(shell ls $(KDIR)/include/config/kernel.release 2>/dev/null)/kernel/drivers/net/wireless # 默认构建目标 all: $(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules install: $(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules_install install -d $(DESTDIR)/lib/firmware/mediatek install -m 644 $(PWD)/firmware/MT7663* $(DESTDIR)/lib/firmware/mediatek/ clean: $(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) clean这里有几个值得注意的细节。
ARCH=arm这个变量告诉内核构建系统当前编译的是ARM架构的代码。CROSS_COMPILE指定了交叉编译工具链的前缀,内核构建系统会自动在调用gcc时加上这个前缀。
KDIR指向的是内核源码树所在位置。M=$(PWD)告诉内核构建系统,我要编译的模块代码在外部路径,编译完成后把生成的.ko文件放到M指定的目录下。
3.2 源码文件的组织方式
MT7663驱动源码下载下来后,文件结构有点乱,我重新整理了目录。建议你也按照这种方式维护,不然编译时经常会因为文件不在预期位置导致依赖关系断裂。
整理后的目录结构:
mt7663_usb_wifi/ ├── Makefile ├── firmware/ # 固件存放目录 │ ├── MT7663_EEPROM.bin │ ├── MT7663_WIFI_RAM_CODE.bin │ └── mt7663_firmware.bin ├── src/ │ ├── mt7663_usb.c # USB接口层驱动 │ ├── mt7663_core.c # 核心协议栈逻辑 │ ├── mt7663_mac.c # MAC层实现 │ ├── mt7663_phy.c # PHY层配置 │ ├── mt7663_chan.c # 信道管理 │ ├── mt7663_efuse.c # eFuse读取与校验 │ ├── mt7663_reg.h # 寄存器定义 │ ├── mt7663_dbg.c # 调试接口 │ └── mt7663.h # 公共头文件 ├── include/ │ └── ... # 内核接口头文件 └── Kconfig这里特别提醒,firmware目录里的文件是板级平台能否正常工作的关键。我遇到过一次忘记拷贝固件的情况,模块明明insmod成功了,但网卡死活起不来,dmesg里报firmware load failed,最后排查半天发现是固件路径没匹配上内核的firmware搜索路径。这个坑后面还会细说。
3.3 依赖的编译选项解析
除了基本的目标编译规则,makefile里其实还藏了不少具体的驱动代码条件编译选项。比如:
EXTRA_CFLAGS += -DMT7663_USB -DMT76_LED_ACT_SUSPEND EXTRA_CFLAGS += -I$(PWD)/include-DMT7663_USB是为了让源码编译时走USB接口的分支逻辑,因为同一份驱动代码同时支持USB和SDIO两种接口方式,编译时通过宏来区分。这个宏对应到c文件里就是#ifdef MT7663_USB这样的条件编译用法。
-DMT76_LED_ACT_SUSPEND是控制LED行为的一个开关。联发科的驱动源码里不同平台对于LED状态的默认设定不太一样,跟产品定义的LED指示逻辑有关。如果你的板子上LED不亮或者乱闪,可以考虑调整这个宏。
-I$(PWD)/include是添加头文件搜索路径。这个必须加上,因为驱动源码中的#include "mt7663.h"这类语句,会先在当前目录找,然后再去-I指定的路径找,如果路径不对直接编译报错。
另外一个值得关注的参数是:
# 如果目标内核的编译标志中包含CFG80211或MAC80211支持,需要显式声明 ifeq ($(shell grep -c "CONFIG_CFG80211=y" $(KDIR)/.config),1) EXTRA_CFLAGS += -DCFG80211 endif这段逻辑的作用是自动检测内核配置中是否开启了CFG80211,CFG80211是内核标准的无线配置管理框架,驱动代码里通过DCFG80211这个宏来适配不同的内核无线框架版本。这个问题在换内核版本的时候尤其明显,所以我才在makefile里加了这段自动检测逻辑,不然换内核版本后还得手动改。
4. 编译过程实操记录
4.1 一路踩坑的编译过程
讲一下实际操作过程中踩过的坑,这些比看官方文档有用得多。
第一次直接编译,报了无数个undefined reference错误,罪魁祸首是内核源码树里的Module.symvers不完整。这个文件记录着内核导出符号的CRC校验值,驱动模块编译时需要用它做符号匹配。解决办法很简单,回到内核源码目录再完整编一次:
cd /home/user/kernel-source make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modulesmodules_prepare会生成模块编译所需的基础文件,这一步做完之后Module.symvers就正常了。
第二次编译时遇到的是头文件路径问题,报错信息是找不到linux/vermagic.h。这个文件是内核版本魔法字符串定义,用于校验模块与内核版本是否一致。出现这个问题的原因可能是内核源码树没有完整展开。在我的环境里,是因为之前编译内核时用过O=指定独立的输出目录,源码目录里缺少了生成的文件。
解决方式是去源码目录执行一次make ARCH=arm CROSS_COMPILE=... prepare,这个目标会触发生成包括vermagic.h在内的编译所需头文件。
4.2 编出来的模块长啥样
当makefile终于一路顺畅跑完,你会得到类似这样的输出:
CC [M] src/mt7663_usb.o CC [M] src/mt7663_core.o CC [M] src/mt7663_mac.o CC [M] src/mt7663_phy.o CC [M] src/mt7663_chan.o CC [M] src/mt7663_efuse.o CC [M] src/mt7663_dbg.o LD [M] src/mt7663u.ko最终生成的模块文件是mt7663u.ko。注意名字这里有个u后缀,其实是USB的意思,用来区分SDIO版本(mt7663s.ko)。
用file命令可以确认模块的平台属性:
$ file mt7663u.ko mt7663u.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), BuildID[sha1]=...输出里明确标注了ARM和EABI5,说明交叉编译是正确的。如果你看到的是"Intel 80386"或者"x86-64",说明CROSS_COMPILE没生效或者的设置不对。
模块的版本魔法字符串也要检查一下:
$ modinfo mt7663u.ko | grep vermagic vermagic: 4.19.71 SMP preempt mod_unload modversions ARM这个vermagic字符串必须和你目标板子上跑的内核版本完全匹配。mod_unload和modversions这些后缀也必须一致,不然insmod时会报"version magic mismatch"错误。
4.3 部署到目标板的完整流程
编译完成之后,部署也有讲究。推荐的做法是先把.ko文件和固件都推到开发板,然后按顺序执行:
# 拷贝模块 scp mt7663u.ko root@target_ip:/lib/modules/$(uname -r)/kernel/drivers/net/wireless/ # 拷贝固件 scp firmware/MT7663_EEPROM.bin root@target_ip:/lib/firmware/mediatek/ scp firmware/MT7663_WIFI_RAM_CODE.bin root@target_ip:/lib/firmware/mediatek/ # 加载依赖模块 modprobe cfg80211 # 加载驱动 insmod mt7663u.ko之所以强调先用insmod,是因为如果insmod阶段发生问题,你还能轻松回滚。直接用modprobe虽然能自动处理依赖,但排查问题的时候就不太方便了。
加载成功后,dmesg里应该能看到类似这样的输出:
usbcore: registered new interface driver mt7663u mt7663u 1-1:1.0: HW/SW Version: 0x8a8a0000, Build Time: 20220328175428a mt7663u 1-1:1.0: WM Firmware Version: 0x2022032817如果固件加载失败,会看到firmware load failed的报错。
加载完驱动后,用ifconfig -a或者ip link命令可以看到新增的无线网卡接口,通常是wlan0。之后正常用wpa_supplicant配网流程就行。
实际使用中,USB wifi还有一个比较常见的坑是供电问题。MT7663在5GHz频段满功率工作时的功耗在1.2W左右,如果板子USB口供电能力不足,会出现掉线、驱逐或者速率不稳的情况。这个问题很难从代码层面解决,只能从硬件层面优化,比如加大USB口的滤波电容。
5. 调试心得与FAQ速查
5.1 驱动加载失败排查步骤
如果insmod之后网卡没正常出现,我一般按照下面的顺序排查:
首先是dmesg看内核日志,有没有版本魔法字符串不匹配、固件加载失败、或者USB设备枚举异常的消息。USB设备枚举异常比较常见的原因是设备地址冲突或者供电不足,这时可以用lsusb来看设备是否被正常识别。
其次是确认USB PID/VID是否匹配。MT7663 USB版本的VID是0x0E8D,PID有多种,常见的包括0x7663和0x7616。如果你的设备在lsusb输出里显示的PID不在驱动支持的范围内,要么确认设备是否是真的MT7663芯片,要么需要看驱动的USB设备表是否需要调整。
然后可以检查一下firmware目录是否存在且权限正确。很多文件系统都要求固件文件与内核搜索路径一致,并且对root用户可见。如果路径不一致,你会在dmesg中看到类似"mt7663u: Failed to load firmware"的信息。
最后,如果以上都不能解决,试着用usb抓包工具分析一下USB层的通信过程。usbmon是内核自带的USB抓包工具,把抓到的数据包和驱动源码里对USB请求的预期格式做比对,往往能定位到问题。
5.2 速率低、性能差怎么调优
模块正常加载只是第一步。实测下来,MT7663跑不出理想吞吐,最常见的原因是Firmware版本太老。联发科不定期会更新固件,解决一些性能和稳定性问题,所以建议经常看看官方仓库的firmware目录有没有新版本。
第二个常见的性能问题是Tx/Rx队列长度和中断协调参数的默认值不适合你的应用。你可以通过调整驱动中对应队列参数来优化吞吐。网上很多关于MTK WiFi速率慢的问题都跟这个有关,但调整时最好用iperf做基准测试,逐步调整,不要一次改动太大。
第三点要注意的是天线布局。MT7663是支持2x2 MIMO的,两颗天线如果放置不好,或者连接器的匹配电路设计有问题,很容易出现5GHz速率上不去的情况。这类问题已经超出了软件调优的范畴,必要时得用频谱仪分析一下天线性能。
5.3 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| insmod时报version magic mismatch | 内核版本或编译选项不一致 | 检查modinfo的vermagic与板上uname -r是否匹配 |
| dmesg报firmware load failed | 固件缺失或路径不对 | 确认固件在/lib/firmware/mediatek/下 |
| 网卡起不来,驱动挂在mmc/usb枚举阶段 | USB供电不足或链路异常 | 单独给USB口供电,用lsusb确认设备枚举 |
| 连接5GHz AP不掉,但速率极低 | 天线匹配或固件问题 | 升级固件,检查MIMO配置 |
| 同一个板子换内核版本后编译报错 | 内核API变化 | 同步更新驱动源码,检查CONFIG宏 |
| ifconfig看到接口但没有扫描结果 | cfg80211依赖问题 | modprobe cfg80211后重新insmod |
说到内核API变化,这里有个真实经历。有一次我把同一个驱动从4.19内核迁移到5.10内核,驱动代码里用了很多老版本的API接口,结果编译时直接报错。做这类适配工作时务必对内核源码中的相关结构体定义多做对比分析,不能只靠搜索替换。
5.4 后续可以做的扩展
这套驱动工程做稳定之后,还可以往几个方向扩展。
一个是做成WiFi热点的模式,也就是SoftAP。MT7663在AP模式下最高可以支持16个station接入,做小型的移动热点或者演示Demo都够用。使用方法是在内核配置里开启CONFIG_NL80211_TESTMODE,然后用hostapd配一个AP模式的配置文件就行。
另一个方向是加进你产品的buildroot或者Yocto构建系统里。我的做法是写了一个MT7663.mk文件,把交叉编译、固件拷贝和内核模块安装的步骤都集成进构建流程,这样每次CI构建出来的固件镜像里就已经带好驱动了,不需要在目标板上再做手工部署。
如果你用的平台比较特殊,比如飞腾系列的ARM平台,需要注意的是工具链选择。飞腾一般用的是aarch64架构,工具链前缀通常是aarch64-linux-gnu-,makefile里改一下CROSS_COMPILE变量,KDIR指向飞腾平台对应的内核源码树就行。这套驱动的核心逻辑是不变的,变的只有工具链路径和内核源码路径。
最后再分享一个小技巧:如果你需要在多台主机上维护这个项目,建议把交叉编译工具链路径做成环境变量或者配置文件的方式,用条件判断去选择工具链,而不是在makefile里硬编码。这样换一台开发机或者换一个目标平台时,改动量能小很多。我在项目里是支持通过make CROSS_COMPILE=/path/to/your/toolchain/bin/xxx-这样临时指定的方式来覆盖默认值的,灵活度会高一些。
我自己的实际体会是,搞定MT7663这套驱动,心里对USB WiFi驱动的整体架构就有了比较清晰的认识。以后再遇到RTL8822、AX88179这类的网卡驱动,上手速度会快很多。驱动开发虽然前期调试比较磨人,但把问题一个个解决掉之后,对整个系统的理解会深入很多。
本文还有配套的精品资源,点击获取