ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MT7663 USB WiFi驱动交叉编译实战:从makefile到固件部署

MT7663 USB WiFi驱动交叉编译实战:从makefile到固件部署 简介面向嵌入式 Linux 平台开发者的 MT7663 USB 转 Wi-Fi 驱动源码包已经在海思3531硬件平台上完成交叉编译验证可直接生成大小约 3MB 的内核驱动模块适合正在移植无线网卡驱动或者调试 Wi-Fi 相关功能的开发人员直接使用。该源码包内部的核心文件是已经配置完成的 Makefile 文件里面明确指定了 aarch64-linux-gnu- 交叉编译工具链并将工具链前缀、内核源码所在目录、驱动源码所在目录集中整合方便使用者根据不同的目标平台自行调整路径和架构参数从而快速集成到现有嵌入式系统项目中。整个压缩包共包含 453 个文件体积约为 7.62MB文件类型以 C 语言源码与头文件、命令脚本、编译中间文件、固件和配置文件为主同时还有 makefile、说明文档、内核符号表和可装载模块等基本覆盖了从源码修改、交叉编译生成到固件配置验证的完整过程。目前已有 424 人学习下载额外附带的补丁固件及无线配置设置文件对排查驱动模块加载失败、交叉编译环境异常和无线功能配置错误等问题具有直接参考价值。 上个月帮客户调一块基于ARM Cortex-A7的工业网关硬件上要增加一路USB WiFi做无线管理通道。客户丢给我一个网盘链接说这是mt7663 USB转WiFi的源码包makefile已经配好了直接交叉编译就行。我解压一看所谓配好了离能编过还差着好几段弯路。MT7663这颗料在嵌入式圈子里出现频率很高。联发科的老一代WiFi 5方案支持USB和SDIO两种接口形态2.4G/5G双频蓝牙5.0共存很多工控板、机顶盒、边缘网关都拿它做无线接入。但官方驱动源码散落各处GitHub上fork版本一大堆每个版本的内核适配、编译配置、固件依赖都不一样。这篇把mt7663 USB驱动交叉编译这条路上的关键点串一遍包括makefile里到底改了什么、为什么这样改、编译不过时怎么定位给后面要踩这坑的人一个完整参考。1. USB接口的mt7663到底解决什么问题1.1 为什么不用板载SDIO而是USB转WiFi先聊一下这芯片在嵌入式产品里的定位。MT7663本身是支持SDIO和USB两种接口的WiFi/BT combo芯片。板载SDIO的好处是走总线带宽高、延迟低但代价是主控SoC必须预留SDIO控制器引脚PCB布局要走差分线天线匹配也要一并设计整个硬件改版周期拖得很长。USB转WiFi的思路完全不一样。主板只要引出一路USB Host接口插一个USB WiFi模块驱动程序加载之后就能工作。对于那些存量设备、已经定型的主板、或者只打算小批量出货验证功能的项目来说这是性价比极高的方案。模块坏了直接换不用返修主板。很多工业平板和边缘计算盒子就是这么干的一个USB WiFi dongle搞定无线接入剩下几个USB口还能挂4G模组、U盘或者调试串口。1.2 源码包里通常有哪些东西从网上拿到的mt7663驱动源码包解压之后一般长这样mt7663_usb/ ├── Makefile ├── Makefile.6.x ├── common/ │ ├── mt76_chip.c │ ├── mt76_phy.c │ └── ... ├── include/ │ ├── mt7663_reg.h │ └── ... ├── os/ │ ├── linux/ │ │ ├── mt76_usb.c │ │ └── ... │ └── ... ├── firmware/ │ ├── mt7663_patch_e2_hdr.bin │ ├── mt7663_rom_patch.bin │ └── ... └── patch/ ├── 0001-mt7663-support.patch └── ...注意firmware目录mt7663不是一颗纯软件MAC的芯片而是一个半开源的FullMAC方案。所谓FullMAC是指大部分MAC层管理功能在芯片固件里完成主机侧驱动只负责把配置命令和网络数据送进去。但固件二进制是闭源的官方只提供编译好的.bin文件不提供固件源码。这就是mt7663驱动编译和部署绕不开的第一道坎——驱动和固件必须配套版本对不上模块能装上但WiFi接口根本起不来。2. 交叉编译环境搭建三个关键决策2.1 工具链用哪个交叉编译器差别很大热搜词里出现linaro交叉编译工具链最新版本下载说明不少人卡在工具链选型上。mt7663这类老芯片的驱动源码对工具链版本其实很敏感。我这次用的是ARM Cortex-A7平台glibc版本是2.28内核版本4.19选的工具链是gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf。为什么不用最新的gcc 12/13因为驱动源码里有些老的函数调用写法在高版本gcc下会直接报warning甚至error。比如在结构体初始化时用了隐式类型转换老gcc睁一只眼闭一只眼新gcc按C11标准严格校验直接不给过。交叉编译工具链不是越新越好老内核最好配合老工具链这个经验在嵌入式Linux里几乎通用。2.2 工具链安装和PATH配置我习惯把工具链解压到/opt/toolchains/下然后软链成固定路径sudo mkdir -p /opt/toolchains sudo tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/toolchains/ sudo ln -sf /opt/toolchains/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf /opt/toolchains/arm-linux-gnueabihf export PATH/opt/toolchains/arm-linux-gnueabihf/bin:$PATH arm-linux-gnueabihf-gcc --version能用软链固定路径是很有用的习惯因为多个项目会共用同一套工具链路径被写死在makefile里之后一旦工具链升级或换目录改一个入口就行了不用到处改。2.3 内核源码路径整个编译体系的锚点mt7663驱动不是一个完全独立的应用它编译时要引用内核源码树里的头文件、Kbuild规则和模块符号。所以必须在开发机上准备一份和板子上内核版本完全一致的内核源码并先完成基础配置生成Module.symvers和include/generated/autoconf.h。我第一次偷懒直接拿一份没配置过的内核源码树来编结果报了一堆linux/version.h: No such file or directory。这类错不是真的没装内核头文件而是内核源码没执行过make ARCHarm modules_prepare。正确姿势是export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- # 如果是原厂提供的内核一般自带defconfig make board_defconfig make modules_preparemodules_prepare这个目标会生成编译模块所需的一系列中间文件不做这一步后面编译必挂。这个教训写下来给所有第一次碰内核模块编译的人拿到内核源码先别急着进驱动目录编驱动先把内核源码的状态整理好。3. makefile里已配置好到底配置了什么3.1 Kbuild结构下的模块挂载方式MT7663驱动的makefile核心机制是Linux内核的Kbuild模块编译系统。也就是说驱动源码本身不是一个独立的工程它必须插入到内核源码树的构建流程里。常用做法是在驱动目录里执行make -C /path/to/kernel M$(pwd) modules这里的-C是切换到内核源码目录M是指定外部模块源码位置。在这个框架下makefile写什么就非常关键。我摘一段典型内容# 指定交叉编译工具链前缀 CROSS_COMPILE ? arm-linux-gnueabihf- # 指定目标架构 ARCH ? arm # 指向板子对应的内核源码根目录 KDIR ? /home/user/kernel/4.19-imx6ull obj-m : mt7663_usb.o mt7663_usb-objs : \ common/mt76_chip.o \ common/mt76_phy.o \ os/linux/mt76_usb.o EXTRA_CFLAGS -DMT7663_USB -DCONFIG_MT76_LED all: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) clean不要小看这个结构。obj-m : mt7663_usb.o是Kbuild识别我要编哪些模块的唯一入口。mt7663_usb-objs列出这个模块由哪些分散在不同子目录下的.o组成这在驱动代码分散在common、os等多个目录时是标准做法。3.2 最容易踩的路径硬编码问题网上所谓的已配置好绝大多数指的是CROSS_COMPILE和KDIR这两个变量已经写好了。但坑也在这里因为别人板子的内核路径和你不一样直接make大概率报错Makefile: XXX: *** KDIR 路径不存在。Stop.解决办法是在makefile里把两个变量拆开允许命令行覆盖KDIR ? /home/user/kernel/4.19-imx6ull加一个?号表示如果没有在命令行指定就用默认值。这样你拿到手之后不需要改文件直接执行make KDIR/实际的内核源码路径建议每个动手编译的人拿到源码先做这一步而不是闷头直接make否则第一行输出就会让你抓狂。3.3 针对不同内核版本的兼容层mt7663驱动的源码包往往同时提供Makefile和Makefile.6.x或者用ifeq ($(shell uname -r), ...)做版本判断。这是因为Linux内核在5.x之后cfg80211、mac80211的API变动比较大。比如老版本里注册无线接口用的是cfg80211_ops的某些回调新内核里字段名变了、参数类型变了直接编会报struct成员冲突。遇到这种兼容多版本的设计我的做法是先在makefile里加一行版本打印$(info 编译目标内核版本 from KDIR: $(shell grep VERSION $(KDIR)/Makefile | head -1))然后先搞清楚板子内核到底是哪个大版本再决定用哪个makefile入口。用错文件的话编译报错根本没有头绪。4. 实测中的完整排查链路从报错到加载成功4.1 第一次编译linux/version.h失踪案我在一个干净环境里第一次执行make第一屏就蹦出来fatal error: linux/version.h: No such file or directory排查思路要清晰。这个头的来源实际上是内核源码树的include/generated/uapi/linux/version.h它是在内核配置阶段生成的。解决办法是回到内核源码目录执行make modules_prepare。这一步会生成version.h、autoconf.h等关键中间文件。这里我多解释一句很多人以为装了kernel-devel包或交叉工具链就够了忽略modules_prepare这一步直接编外部模块就会卡在这里。内核源码树不是一个纯静态目录它需要先被激活才能用于模块编译。4.2 第二次编译结构体成员不兼容version.h解决之后紧接着来了一堆类型错误报错集中在sk_buff、net_device相关操作。这是因为驱动源码里某些API是按旧内核写的但板子内核已经是4.19接口早改了。具体报错长这样error: ‘struct sk_buff’ has no member named ‘next’这个场景很典型。解决方式有两个方向一是打官方release里的patchpatch目录里的0001-mt7663-support.patch就是干这个的二是手动改源码把老API换成新API。对于4.19内核我实际处理中改了两处一处是无线数据包的发送函数签名一处是cfg80211_ops里start_ap和stop_ap的结构体填充。官方patch一般是用git apply打的cd 内核源码目录 git apply /path/to/mt7663/patch/0001-mt7663-support.patch如果patch报错可以先git apply --check看冲突再用patch -p1 xxx.patch强制打。真到手动改源码这一步一定要备份原文件逐项对内核文档不要凭记忆改。4.3 编译过了但模块加载报unknown symbol编译终于通过用adb push或NFS挂载把mt7663_usb.ko拷到板子上insmod的时候却报mt7663_usb: Unknown symbol cfg80211_xxx (err 0)这个问题的根源不是一个符号而是依赖关系没理清。内核模块不是独立的它依赖内核导出的其他符号。MT7663驱动依赖cfg80211和mac80211这两个内核模块而这两个模块在板子内核config里可能压根没编或者编成了模块但没有事先加载。排查方法grep cfg80211 /lib/modules/$(uname -r)/modules.dep正确加载顺序是modprobe mac80211 modprobe cfg80211 insmod mt7663_usb.ko很多人在这一步反复卡死以为是驱动编译错了其实是内核config里CONFIG_CFG80211、CONFIG_MAC80211没开成模块。需要在板子内核的defconfig里确认CONFIG_CFG80211m CONFIG_MAC80211m重新编译内核并烧录后再按顺序加载模块链路就通了。4.4 固定IP和无线网卡名称的不确定性还有一个很容易被忽略的问题驱动加载成功后无线网卡接口名不一定是wlan0。有些官方驱动会把它注册成ra0有些是wlan0。这取决于驱动里struct wireless_dev的注册名和内核的命名规则。在init脚本里不要写死网卡名建议用udev规则固定SUBSYSTEMnet, ACTIONadd, DRIVERSmt7663_usb, NAMEwlan0或者写脚本动态获取WLAN_IFACE$(ls /sys/class/net | grep -E wlan|ra | head -n1) ifconfig $WLAN_IFACE up我当时就是没注意这茬把所有脚本都按wlan0写了结果设备上显示的是ra0排查了大半天。这类接口名不一致的问题在嵌入式Linux里非常常见。5. 部署验证从firmware到USB枚举的全链路确认5.1 固件文件放哪、怎么放前面说过mt7663是FullMAC方案固件必须被正确加载无线接口才会工作。固件文件默认查找路径是/lib/firmware/mediatek/代码里写死的话不能乱改建议直接按默认路径放mkdir -p /lib/firmware/mediatek cp mt7663_patch_e2_hdr.bin /lib/firmware/mediatek/ cp mt7663_rom_patch.bin /lib/firmware/mediatek/ cp mt7663_wifi_soc.bin /lib/firmware/mediatek/固件放好之后再加载驱动。判断固件是否被正确读取看dmesg输出mt7663_usb 1-1:1.0: Firmware Version: 6_0_1_01 mt7663_usb 1-1:1.0: Firmware download success如果出现Firmware download failed或者patch file not found几乎可以断定是固件路径不对或者驱动和固件版本不匹配。注意有的源码包里固件文件是分散放的需要按源码目录里的install脚本去拷贝不要只拷一个bin文件。5.2 用dmesg和usb抓包确认硬件枚举驱动加载前先插上USB WiFi模块查看usb设备是否识别到lsusb正常会看到类似Bus 001 Device 003: ID 0e8d:7663 MediaTek Inc. MT7663如果没有先确认USB Host口能不能枚举其他设备排除硬件供电问题。MT7663模块的供电电流比普通键鼠大很多USB口的供电不足也会导致枚举失败。我实际测过部分模块在弱供电下能枚举但加载驱动时会掉线最后加了一路独立的5V供电才稳定。如果枚举到了但驱动报错可以用USB抓包工具看USB控制传输的返回包。用Linux主机做USB抓包不需要额外硬件加载usbmon模块即可modprobe usbmon cat /sys/kernel/debug/usb/usbmon/1u抓包能看到URB提交和撤销的时机重点观察加载驱动后固件下载阶段是否有URB -ESHUTDOWN之类的错误。这个信号基本指向USB通信链路不稳定多半是供电或USB信号质量问题而不是驱动问题。5.3 无线接口能否扫描到AP驱动加载、固件下载都成功之后最后一步验证无线功能ip link set wlan0 up iw dev wlan0 scan | head -30如果可以scan出周围的AP说明mt7663 USB转WiFi的收发链路已经通了。再走一步用wpa_supplicant接入测试网络如果260Mbps的连接速率能打满整个编译和部署流程就算是彻底闭环了。6. 后续扩展方向驱动调通之后这个USB WiFi口就可以纳入整个嵌入式系统做业务了。一是接Qt应用做无线配置界面配合Qt 5.12.10交叉编译环境在触摸屏上做SSID扫描和密码输入这块我已经在另一个项目里跑通了后续有机会单独写一篇二是配合RK3576这类性能更强的ARM平台USB WiFi可以作为热点模式使用让设备反向广播一个AP手机直接连上来做本地调试。不过这需要驱动支持AP模式mt7663固件是支持的但需要把NL80211的接口模式配置改一下涉及的东西又不一样了。我个人的习惯是每次拿到一颗新芯片的驱动源码先把编译环境、内核源码树、固件依赖这三个基础问题解决再碰代码逻辑。mt7663这套流程走完后面编其他MTK芯片的驱动基本都是同一套打法KDIR指对、config开对、firmware放对剩下的就是API level的排雷。如果只是编译驱动这一个目标这篇文章应该能帮你省下至少两天的折腾时间。本文还有配套的精品资源点击获取
返回列表