简介这是一份移远通信出品的GobiNet驱动程序适用于Linux与Android平台版本为V1.6.2.9。该驱动面向嵌入式开发工程师、系统集成人员及运维管理员用于解决Linux系统无法识别移远Gobi系列3G/4G/LTE无线模块、无法建立移动数据连接的问题已在海思平台实测可用。压缩包共9个文件以C源码与头文件为主包含核心驱动实现及接口定义另有Makefile与说明文档整体仅61KB属于轻量级内核驱动源码包。驱动通过编译生成Gobinet.ko模块并加载进内核即可让系统正常管理模块并完成网络通信对于Android平台还可结合Android构建系统将驱动集成至系统镜像开发者按说明文档执行编译加载即可快速验证。目前已有893人学习下载适合需要在Linux设备或嵌入式项目中快速集成移远Gobi模块的技术人员参考使用。1. 移植 Quectel 模块的 Linux 驱动GobiNet 驱动包到底怎么用拿到 Quectel_LinuxAndroid_GobiNet_Driver_V1.6.2.9.zip 这份资源大多数人的第一反应是找 README 然后照着编结果往往卡在编译报错或者 Modem 不上线。这个 zip 里装的是高通平台 GobiNet 协议栈的驱动源码专门用来让 Linux 和 Android 系统识别 Quectel 的 4G/5G 模块并把标准 AT 口、数据口、控制口通过 USB 枚举出来。它不是普通字符设备驱动而是基于 USB CDC-NCM 思路做的一套网络接口驱动核心逻辑是把模块一侧的 QMI 消息封装成网络包在主机侧虚拟出一个 eth 网卡。换句话说只要你能在 dmesg 里看到驱动加载成功、usb0接口出现、并且能 ping 通运营商网关这套驱动就算真正落地了。适合的对象是搞嵌入式 Linux BSP、Android 系统集成、工业路由器或者车载盒子的工程师对已经用 NetworkManager 或者 ModemManager 的用户来说这份资源的价值在于给了你一个不依赖用户态协议栈的底层通道。本文我会拆开这个包从驱动版本差异一直说到具体编译、加载串口节点出现、拨号脚本常见坑。2. 驱动资源定位GobiNet 与 MBIM、QMI 的关系以及 v1.6.2.9 的版本边界2.1 为什么选 GobiNet 而不是直接上 QMI 用户态工具Quectel 模块在 Linux 主机的接入方式业界大致有四条路标准 ECM/RNDIS、GobiNet 内核驱动、MHI 驱动主要用于 5G、以及用户态的 QMI 库如 libqmi 配合 ModemManager。这份驱动包里的 GobiNet 走的是第二条路它把 QMI 协议的内核态实现和网络数据通路绑定在一起驱动加载完模块的/dev/ttyUSB*口照常出现数据口则表现为一个 eth 网卡。相比在用户态用 libqmi 去发 QMI_WDA_SET_DATA_FORMATGobiNet 由内核自己处理 802.3 封装和 QMI 复用省了一次用户态—内核态切换在高频小包场景优势更明显。具体到 v1.6.2.9 这个版本它对应的模块范围大体覆盖了从 EC200、EG915 到带 5G 的 RG500Q 这些常用型号。需要注意版本边界老旧的 3G 模组比如 UC15、UC20不在 GobiNet 支持名单里这些模组要用旧的串口驱动或者直接走 PPP。如果你发现自己的模块对应内核版本是 5.10 以上同时模块本身是较新批次那 v1.6.2.9 的 GobiNet 往往没有后续大版本更新但胜在稳定很多方案的量产基线就一直锁在它。2.2 压缩包结构解读源码树、补丁、文档分别解决什么问题解开 zip 之后你会看到大概这样一个顶层结构以资源实际内容为准Quectel_LinuxAndroid_GobiNet_Driver_V1.6.2.9/ ├── GobiNet/ │ ├── GobiUSBNet.c │ ├── GobiUSBNet.h │ ├── QMIDevice.c │ ├── QMIDevice.h │ └── Makefile ├── quectel-CM/ │ ├── quectel-CM.c │ ├── quectel-CM.h │ └── Makefile ├── Patch/ │ ├── 0001-GobiNet-xxxx.patch │ └── ... └── Doc/ ├── Quectel_GobiNet_USB_Driver_User_Guide.pdf └── Quectel_QMI_Windows_Driver_User_Guide.pdfGobiNet 目录是核心驱动源码QMIDevice.c负责模块的 QMI 通道管理和设备枚举GobiUSBNet.c负责把 QMI 数据封装成网络包quectel-CM 目录是一个拨号小程序用于直接控制模块入网Patch 目录里是一些针对特定内核版本的补丁。文档目录里的 PDF 操作指引建议第一时间打开看一遍特别是里面提到的模块配置要求——有不少人漏了 ATQCFGusbnet导致驱动怎么都拉不起网卡。2.3 内核版本与编译环境的兼容判断驱动本质上是对内核 USB 子系统的扩展因此内核头文件版本必须和当前运行内核一致。一个常见做法是# 先确认目标板上内核版本 uname -r # 确认当前内核源码或头文件路径 ls /lib/modules/$(uname -r)/build如果build目录不存在说明缺少内核头文件需要先安装。GobiNet 源码里大量使用了struct usb_driver、struct net_device_ops这类相对稳定的 API因此从 3.x 到 6.x 内核基本都能编译过但 5.15 之后部分内核把devmem权限收紧、module_param类型检查更严偶尔会报incompatible pointer type错误——这种就是需要打 Patch 目录里对应补丁的信号。我一般会在实际交叉编译之前先在本机 x86 Linux 上跑一次原生编译用它快速验证源码有没有语法级错误。等本机编译通过再去动交叉编译链能省掉至少一半的排查时间。3. 编译与加载从 Makefile 到usb0接口出现的完整操作3.1 内核源码树的准备和 Makefile 修改拿到驱动源码后第一步是把 GobiNet 目录拷贝到内核源码树的drivers/net/usb/下或者在外部单独编译。单独编译更干净也方便后续换内核时重新编。这里以外编为例先看懂 Makefile 里的变量# GobiNet Makefile 关键段落 obj-m GobiNet.o GobiNet-objs : GobiUSBNet.o QMIDevice.o KDIR ? /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) default: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) cleanobj-m表示以模块方式编译GobiNet-objs是模块依赖的源文件列表。交叉编译时只需要覆盖KDIR变量指向目标板内核源码路径。注意这里没有用到EXTRA_CFLAGS如果内核开启了 secure boot模块加载会有签名校验这种场景下建议直接编进内核而不是用 insmod。编译命令和预期产物如下# 本机原生编译快速验证 make clean make # 预期产物 ls -lh GobiNet.ko # 交叉编译示例环境变量按实际工具链路径改 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- KDIR/path/to/kernel/source交叉编译时如果报arch/arm/include找不到通常是KDIR路径不对要确保指向源码树根目录而不是kernel/子目录。编译通过后GobiNet.ko就要拷到目标板的/lib/modules/$(uname -r)/extra/下。3.2 模块加载参数为什么拨号前必须先看 vlan_idGobiNet 驱动加载支持若干模块参数insmod时如果省略它们驱动会按默认值运行。其中最重要的参数是vlan_id它决定网络数据走 VLAN 子接口还是走主接口。Quectel 模块出厂配置的 usbnet 模式通常是0对应不带 VLAN 标签的普通网卡此时把vlan_id设成0即可# 加载驱动vlan_id 设为 0与模块 ATQCFGusbnet,0 对应 insmod GobiNet.ko vlan_id0 # 确认加载成功 dmesg | grep -i gobi ls /sys/class/net/如果模块侧被配置成了ATQCFGusbnet,2QMI VLAN 模式那么主机侧必须把vlan_id设成2否则内核收不到数据包。加载成功后ls /sys/class/net/能看到新增的usb0同时/dev/ttyUSB*出现 4 个节点DIAG、NMEA、AT、PPP。看不到usb0时先别急着拔插用lsusb确认模块 USB PID 是否被识别。3.3 与 Android 内核集成的差异不再用 insmod 而是内置在 Android 平台上驱动一般不直接 insmod而是编译进内核或者通过 vendor 模块机制加载。操作思路和 Linux 有区别# 以高通老平台为例在 kernel/msm-4.9/drivers/net/usb/ 下放置源码 # 在对应 defconfig 中开启 CONFIG_USB_NET_GOBINETy # 或者 m 并加入 PRODUCT_COPY_FILES 拷贝 ko编译进内核后开机时无需手动加载但是模块参数只能在 dts 或内核 cmdline 中传。Android 系统里还要注意 SELinux 策略如果usb0起不来优先看avc: denied日志一般是sysfs写入或者网络接口操作权限不够。在 Android 13 之后netd的权限模型更严格非默认网络的接口需要显式加入netd的权限白名单否则即使usb0存在也无法通过dhcpcd拿到地址。3.4 快速验证模块枚举、网卡挂起、AT 口连通性驱动加载完验证是否正确不能只看usb0存在还要走一遍连通性链路# 查看 USB 枚举状态 lsusb | grep -i quectel # 检查网络接口状态 ip link show usb0 # 用 AT 指令确认模块注册状态ttyUSB2 一般是 AT 口 echo -e ATQENG\servingcell\\r /dev/ttyUSB2ip link show usb0输出的state DOWN是正常的因为此时还没有拨号但NO-CARRIER状态通常意味着模块没有注册网络需要先检查天线和 SIM 卡。AT 口连通性测试要确认/dev/ttyUSB2存在并且能收到OK如果这里就无响应后面 quectel-CM 或者脚本拨号都没有意义。每一个参数变化后务必按这个顺序复查lsusb确认 PID →dmesg看驱动 probe 是否成功 →ip link看网卡是否创建。顺序乱跳很容易把问题带偏。4. 业务对接quectel-CM 拨号、网卡配置与模块 AT 参数联动4.1 quectel-CM 编译与使用不只是拨号还要管路由表驱动包自带的 quectel-CM 是官方拨号客户端用来完成从 QMI 设置到 IP 地址获取的整个流程。编译它同样简单# 进入 quectel-CM 目录 cd quectel-CM make # 运行-s 指定 APN-f 保留前台输出 ./quectel-CM -s cmnet -f -s cmnet是接入点名称国内公网卡一般填cmnet、ctnet或者3gnet具体以运营商为准-f让日志打到屏幕便于观察拨号流程。quectel-CM 会依次发ATCGDCONT设置 APN、通过 QMI 请求建立数据连接、再调用ioctl获取内核分配的 IP 地址并配置到usb0。运行后看到日志里出现get client WDS succeed和DHCP: IP address xxx就说明拨号成功。quectel-CM 还会顺手设置默认路由但这在多网卡设备上会带来路由冲突比如同时有以太网和 LTE 的时候默认路由会被 quectel-CM 抢占。此时要用-n参数禁用路由修改自己手动配策略路由。4.2 不用 quectel-CM 的裸拨号QMI 指令序列与文件操作有些项目不想引入额外二进制那就直接用 QMI 原始指令通过/dev/qmi0操作。这里顺带提一条可以抄的路径具体指令格式以模块文档为准# 切换到 QMI 模式部分模块需要 echo -e ATQCFG\usbnet\,0\r /dev/ttyUSB2 # 设置 APN echo -e ATCGDCONT1,\IP\,\cmnet\\r /dev/ttyUSB2 # 触发网络附着 echo -e ATCOPS0\r /dev/ttyUSB2上述 AT 只是准备工作真正建立数据连接还是要用 QMI 的 WDS 接口发Start Network请求。如果不想写代码可以用现成的uqmi工具补全这步。这里要强调ATCGDCONT 只改写模块侧的 PDP 上下文不激活数据通路QMI 拨号才是真正让网络接口拿到 IP 的动作。很多人卡在 AT 设置完但网卡起不来原因就是漏了 QMI 这一环。4.3 DHCP 与静态 IP 配置谁负责下发地址、谁决定 MTU拨号完成后usb0需要配置 IP。两种模式默认 DHCP或者 QMI 直接下发 IP然后手动设置。DHCP 方式要注意模块内嵌的 DHCP 服务器在某些固件版本里有缺陷——IP 地址池固定但租约时间异常导致客户端反复续租失败。稳妥做法是用 quectel-CM 的-n参数禁止它改路由再用udhcpc指定接口# 手动 DHCP适用于 quectel-CM 不接管网络配置的场景 udhcpc -i usb0 -n -q -s /etc/udhcpc.script # 静态 IPQMI 拨号后手动配置 ip addr add 10.166.218.46/32 dev usb0 ip link set usb0 up ip route add default dev usb0MTU 的问题更隐蔽。LTE 链路的 MTU 通常要小于以太网标准值因为 PPP 或 QMI 封装有额外开销。模块默认 MTU 是 1500但运营商 SGSN 侧可能实际支持 1400 左右在网页打不开或者大包 ping 不通时优先把usb0的 MTU 调到1400验证。包括 quectel-CM 在内的拨号工具不一定会自动下探 MTU这个参数必须自己做取舍。4.4 模块 AT 参数与驱动状态联动usbnet、APN、PDP 的匹配关系整条链路上模块侧有三个 AT 参数必须和驱动侧状态匹配缺一个都会出事。AT 参数参数含义驱动侧关联ATQCFGusbnet,0/2USB 数据封装模式vlan_id 必须对应0 对应无 VLAN2 对应 VLANATCGDCONT1,IP,cmnetAPN 与 PDP 类型必须与 SIM 卡签约网络一致否则附着失败ATQCFGband频段配置不影响驱动但影响注册速度5G 模块建议先锁 4G 频段当usb0存在但无法 ping 通内网时优先核对这三项。特别注意usbnet参数模块恢复出厂后不会保留这个设置因此在编译驱动前应当先通过 AT 指令确认模块当前值。5. 常见问题排查驱动加载失败、枚举异常与拨号断线的五个典型案例5.1 dmesg 报probe failed with error -ENODEVUSB PID 表没覆盖新批次现象模块插入后lsusb能看到新的 VID/PID但 dmesg 出现GobiNet: probe of 2-1:1.0 failed with error -22或者-ENODEV。原因驱动源码里的 PID 表GobiUSBNet.c中的static const struct usb_device_id products[]没有包含该批次模块的 PID。Quectel 会不定期给模块更新 PID 而不改型号名比如某型号 5G 模块新固件的 PID 换了但固件文档里没显著说明。解决查看当前 PID然后用usb_release_id方式绕过 PID 表——加载驱动时不检查 PID 表里的具体值# 查看模块 PID lsusb -vv -d 2c7c: # 方案A临时在 products[] 中替换 PID 重新编译 # 方案B推荐在调试阶段用在模块 probe 函数前打印 PID确认确实是识别问题 dmesg | grep -i new.*device确认是 PID 问题后把新 PID 追加到驱动源码的 PID 数组中即可。调试完成后不要保留通配 PID 的做法量产时要防止其它 USB 设备误绑定。5.2 编译报错unknown field ndo_set_vlan内核 API 版本差异现象在 5.15 以上内核编译 GobiNet 源码报error: struct net_device_ops has no member named ndo_set_vlan或者ndo_do_ioctl不存在。原因内核社区在 5.15 左右把ndo_do_ioctl接口更换为ndo_siocdev_ifreq而 GobiNet 源码比较老没有做宏适配。解决在源码前部加上内核版本判断条件编译#include linux/version.h #if LINUX_VERSION_CODE KERNEL_VERSION(5, 15, 0) .ndo_siocdev_ifreq gobi_ioctl, #else .ndo_do_ioctl gobi_ioctl, #endif还有一种场景是struct net_device_ops初始化时用了旧字段名直接注释掉也可行但会丢失 ioctl 能力quectel-CM 后续可能调不到必要接口。这里推荐条件编译而不是直接删功能。5.3 系统休眠唤醒后usb0消失USB 自动挂起策略现象设备运行正常休眠唤醒后usb0消失lsusb还是能看到模块但/sys/class/net/下面没有接口。原因USB 自动挂起autosuspend把模块的 USB 端点暂停了唤醒后 GobiNet 驱动的reset_resume回调缺失或者返回失败导致网络接口没有重新注册。解决关闭该接口的 USB 自动挂起# 找到模块所在 USB 设备路径 ls /sys/bus/usb/devices/2-1/power/ # 关闭自动挂起 echo -1 /sys/bus/usb/devices/2-1/power/autosuspend echo on /sys/bus/usb/devices/2-1/power/control量产设备上要把这个动作放到 init 脚本里。注意2-1是总线地址不同板卡可能不同需要用脚本动态解析绑定到固定 USB 路径比如通过 VID:PID 或设备的 DEVPATH而不是写死地址。5.4 拨号成功但 ping 不通外网路由策略与 iptables 干扰现象quectel-CM 日志显示拿到 IPusb0是 UP 状态但 ping 外网不通ping 内网网关也不通。原因一是多网卡场景下系统默认路由没有走到usb0二是防火墙规则把 FORWARD 链默认策略设成了 DROP常见于有 eth0 和 usb0 双网卡的网关设备三是 RP_filter 反向路径过滤把从usb0进来的响应包丢了。解决# 先检查默认路由 ip route show # 追加默认路由按需 ip route add default dev usb0 table 100 ip rule add from all iif usb0 lookup 100 # 关闭反向路径过滤 sysctl -w net.ipv4.conf.usb0.rp_filter0rp_filter的问题在双网卡设备上很典型默认值 1 是严格模式只要回程路由不对称就丢包。这个问题折磨过我好几个通宵后来每次配置 LTE 拨号都会先确认rp_filter状态。5.5 吞吐量远低于标称值驱动侧 skb 线性化与 GRO 开关现象模块和信号都正常但 iperf3 测速只能跑到标称值的 60% 左右。原因GobiNet 驱动在收包路径上没有对 skb 做线性化处理小包场景下skb_shinfo(skb)-frag_list的链表过长导致 CPU 软中断开销大同时 GROGeneric Receive Offload没有开启每个包都走一遍协议栈。解决在驱动收包函数gobi_net_rx或对应入口处确认是否调用了skb_linearize。开启 GRO 的做法是在网卡初始化时设置NETIF_F_GRO/* 网卡特征位设置 */ netdev-features | NETIF_F_GRO;实测中开启 GRO 后吞吐量通常能提升 10%~20%skb_linearize对混合大小包的提升更明显。但这两个操作都会增加 CPU 占用在低配设备上需要权衡。如果驱动源码里没有预留 GRO 支持直接改内核网络的gro_normal_batch参数也能缓解一部分。6. 进阶技巧让 GobiNet 驱动在断电重启后自动恢复完整链路这一章分享一个量产设备上的实战技巧把 GobiNet 驱动从「能用」变成「断了能自己爬起来」。嵌入式 Linux 设备最怕的不是拨号失败而是某次重启之后链路口不对、AT 口没出来、或者模块没注册上导致终端设备成了砖头。我的做法是封装一个modem_guard.sh守护脚本把它挂在 init 或者 systemd 的 service 里。先说思路脚本逻辑分四层第一层检查/sys/class/net/usb0是否存在不存在就重新加载驱动第二层检查usb0是否存在但状态异常比如NO-CARRIER先重置 USBunbind/rebind第三层检查 AT 口是否响应用AT指令探活第四层在拨号断开时重新拉起 quectel-CM。#!/bin/sh # modem_guard.sh —— 守护 GobiNet 链路 USB_DEV2-1:1.0 VID_PID2c7c:030a check_usb0() { [ -e /sys/class/net/usb0 ] return 0 # 重新 bind 驱动 echo $USB_DEV /sys/bus/usb/drivers/GobiNet/unbind 2/dev/null echo $USB_DEV /sys/bus/usb/drivers/GobiNet/bind 2/dev/null sleep 2 [ -e /sys/class/net/usb0 ] return 0 # bind 不成功就做 usb reset echo 0 /sys/bus/usb/devices/2-1/authorized echo 1 /sys/bus/usb/devices/2-1/authorized } check_at() { # AT 口探活出错则重置 USB echo -e AT\r /dev/ttyUSB2 timeout 3 cat /dev/ttyUSB2 2/dev/null | grep -q OK || reset_usb } reset_usb() { # 去掉模块再重新枚举 echo 2-1 /sys/bus/usb/drivers/usb/unbind sleep 1 echo 2-1 /sys/bus/usb/drivers/usb/bind sleep 5 } # 主循环每 30 秒检查一次 while true; do check_usb0 check_at sleep 30 done这里unbind/bind的操作路径和authorized的开关是内核 USB 子系统提供的标准接口。如果 bind 失败说明驱动内部状态已经崩了单纯 bind 不会重新触发 probe所以要先把 USB 设备从usb总线上解绑再重新 bind这样内核会重新走一遍枚举流程。脚本里有两个值得说清的参数sleep 2不能太长也不能太短太短驱动 probe 还没完成就进入下一次检查变成死循环timeout 3在 AT 口无响应时兜底避免脚本卡死在 cat 上。如果设备上有硬件看门狗我一般还会把脚本运行状态喂给看门狗做到链路真死了、脚本也死了的情况下还有最后一道重启机制。在那之后我每次做驱动移植都会强制把模块从 USB 层面做一次下电重枚举的测试因为它能暴露驱动中所有偷懒的处理分支——比如只处理probe不处理disconnect、只处理bind不处理reset_resume的代码在真实掉电场景里都会翻车。希望这份操作笔记帮你少走一段我走过的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?