简介:移远EC20 4G模块驱动源码包,面向Android/Linux嵌入式开发者,解决EC20模块在系统层面的识别、联网与语音通道复用问题,适配LTE/UMTS/GSM多模网络。包内共59个文件、约43.63MB:25个PDF涵盖硬件规格与软件设计规范,11个C与8个H构成USB、GobiNet、CMUX接口的核心驱动,另有zip/rar工程包、makefile编译脚本和txt说明文档,便于直接移植与交叉编译。已有1520人学习下载。源码演示了从设备枚举、中断处理到数据通道建立的完整流程,并给出Linux内核模块加载、Android HAL层集成及dmesg/adb logcat日志定位技巧。配套API定义和配置指南能帮助开发者快速上手,在不同嵌入式平台上实现稳定高效的4G通信能力。
1. 先从驱动框架说起:EC20在Linux里到底驱动谁
拿到移远EC20模块,很多人的第一反应是去下载一个官方提供的驱动源码,觉得"装上这个驱动就能用了"。这个思路不能说错,但容易把你带进坑里。EC20本质上是一个USB接口的4G通信模块,它的主控芯片遵循的是Qualcomm的MDM9x07方案,数据通道走的是USB,所以真正在Linux内核里干活的,其实是内核自带的 USB 网络驱动框架,而不是某一个单独属于"移远"的神秘驱动模块。
先理清EC20在系统里长什么样。插上USB线后,它会被枚举成一个USB复合设备,里面同时暴露了好几类接口,常见的有:
- QMI/ECM/RNDIS 网络接口,用来跑数据业务
- AT命令通道,通常表现为 /dev/ttyUSB2 或 /dev/ttyUSB3
- 诊断口、GPS口,分别对应 /dev/ttyUSB0、/dev/ttyUSB1 这类节点
- 还原/升级用的QDL端口,这个只有在模块进入下载模式时才会出现
这里有一个关键选择点:EC20支持多种数据通道模式,QMI模式、ECM模式、RNDIS模式、甚至老式的PPP拨号模式。不同的模式对应着内核里不同的驱动。现在的主流方案是QMI,因为它的吞吐量、稳定性以及对多PDN(PDP上下文)的支持都明显优于老式PPP。对应的内核驱动就是qmi_wwan,它属于内核标准驱动,并不需要你去改什么源码,只需要在内核配置里把它打开。
那"移远EC20的4G模块驱动程序源码"这个主题下,我们到底需要研究什么?我认为核心有三件事:
- 理解内核里
qmi_wwan、option等驱动与EC20的绑定关系,搞清楚VID/PID是怎么匹配的 - 学会源码级别的调试手段:当设备节点不出现、数据通道起不来时,如何在内核日志里定位问题
- 掌握把驱动编译进内核或编译成模块,并根据实际硬件平台做裁剪和适配的能力
换句话说,这份"源码"更像是一张地图,你要学会看、学会走,而不是把它背下来。
顺便说一个很多新手容易误判的点:EC20在出厂时,默认的VID/PID可能是2c7c:0125,但如果你之前用QTool工具切过模块的工作模式,或者烧过不同的固件,VID/PID可能会变化,变成2c7c:0121之类。也就是说,驱动能不能匹配成功,和你手上模块的固件版本强相关,排查问题时要先确认这一项。
2. 源码阅读主线:从USB枚举到Modem注册的完整流程
假设你决定从内核源码角度去追踪EC20从插入到可用的完整流程,我建议你按下述主线去读,这条线是我自己梳理过的,比从头平铺直叙看代码效率高很多。
2.1 入口:usb_device_id匹配表
内核里几乎每一个USB驱动都会声明一张"我支持哪些设备"的匹配表。qmi_wwan.c里的匹配表长这样:
static const struct usb_device_id products[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x0125, USB_CLASS_VENDOR_SPEC, 0xff, 0xff), .driver_info = (unsigned long)&qmi_wwan_info, }, ... };可以看到0x2c7c就是移远的厂商ID。当你把EC20插入USB口后,内核的USB core会遍历所有驱动,把设备的VID/PID、接口信息跟这张表做匹配。匹配上之后,就会调用.probe回调函数。
建议你在这个文件里搜一下自己手头模块的PID,如果找不到,那么大概率是两种原因:驱动版本太老,或者模块固件改动过接口描述。找不到就自己按格式补一条记录,重新编译,这就是最常见的"改源码"场景。
2.2 数据通道:QMI接口与qmi_wwan_probe
qmi_wwan_probe做的事情比较清晰:
- 读取接口描述,判断当前接口是不是QMI控制口
- 为这个接口分配一个
struct net_device,名字一般是wwan0 - 注册一个
cdc-wdm设备,用于承载QMI控制信令,对应/dev/cdc-wdm0 - 注册
netdev_ops,里面实现了ndo_open、ndo_stop、ndo_start_xmit等底层数据收发函数
这里有个概念要捋一下:QMI协议的数据分两路,控制消息走cdc-wdm字符设备,IP数据包走wwan0网络设备。你在应用层可以不用关心cdc-wdm0,因为像libqmi、quectel-CM这些工具会自己打开它;但如果你的应用需要直接下发自定义QMI请求,那就得了解cdc-wdm的读写格式。
2.3 数据通道:qmi_wwan如何把IP包塞进USB
在qmi_wwan驱动里,真正干脏活累活的是qmi_wwan_netdev_ops。它做的事情是:
static netdev_tx_t qmi_wwan_ndo_start_xmit(struct sk_buff *skb, struct net_device *dev) { // 取出控制结构体 struct usbnet *dev = netdev_priv(net); // 可选的QMAP多路复用封装 if (qmap) { // 加4字节QMAP头,支持多PDN } // 交给usbnet框架的tx流程 return usbnet_start_xmit(skb, net); }这里有一个重要概念叫QMAP(Qualcomm MSM/Modem Multiplexing Protocol)。通俗讲,它允许在一条USB物理链路上同时跑多路IP数据流,每一路用不同的流ID区分。如果你的业务需要同时走多个APN(比如一个走公网、一个走专网),QMAP就非常有用。启用方式是在驱动模块加载参数里带上qmap,或者用Quectel提供的工具去配置,而不是改源码。
2.4 控制通道:qmi_wwan与cdc-wdm
在qmi_wwan.c源码里,你会看到它调用usbnet_cdc_bind、alloc_netdev,但它本身并不直接处理QMI控制消息的格式。控制消息是由drivers/net/usb/qmi_wwan.c通过注册一个wdm子设备来间接处理的:
/* 注册一个 cdc-wdm 设备 */ rv = usbnet_probe(intf, id); if (rv < 0) return rv; ... usb_driver_set_configuration(dev, 1);具体的控制通路实际上会落到drivers/usb/class/cdc-wdm.c这个文件中去处理。读到这里的人很容易懵:明明我查的是qmi_wwan,怎么又冒出cdc-wdm?其实好记:qmi_wwan负责网络数据包,cdc-wdm负责控制信令,两者相互配合,这就是"复合设备"的典型分工。
2.5 老式PPP通道:option驱动
如果模块配置成使用老式PPP拨号(例如在EC20上跑pppd call gprs),那么它需要的就不是qmi_wwan,而是drivers/usb/serial/option.c。这个驱动把USB串口转换成多个ttyUSB*节点,拨号时pppd会通过这些串口节点发送AT命令并承载PPP帧。
当前移远官方早就转向QMI了,EC20也用不到PPP这种低效模式了。但你在源码里依然能看到这些历史遗留的匹配条目,因为内核要保证兼容市面上还在用的老模块。
3. 适配一台具体设备时,必须打开的内核选项与设备树配置
源码读得再熟,最终还是要落到你的实际板子上。这里我直接给出一份基于内核5.10 LTS的适用配置建议,并说明每一项目的是什么,方便你自己做取舍。
3.1 内核配置项的取舍清单
| 配置项 | 建议状态 | 作用说明 |
|---|---|---|
| CONFIG_USB_NET_DRIVERS | y/m | USB网络驱动总开关,qmi_wwan依赖它 |
| CONFIG_USB_NET_QMI_WWAN | y/m | QMI上网核心驱动,EC20必须 |
| CONFIG_WWAN | y | 内核5.13以后需要,旧内核可忽略 |
| CONFIG_USB_SERIAL_OPTION | y/m | AT口和串口通道,建议打开 |
| CONFIG_USB_SERIAL_QUALCOMM | y/m | 部分固件下会用到这类绑定 |
| CONFIG_USB_ACM | y/m | CDC-ACM设备支持,部分AT口走这个类 |
| CONFIG_USB_SERIAL_GENERIC | y | 通用串口驱动,防止枚举失败 |
建议你把QMI_WWAN和OPTION直接编译进内核而不是编成模块。原因很简单:模块如果放在根文件系统里,而根文件系统又必须经过网络才能挂载,就会陷入鸡生蛋的困境。嵌入式设备调试时,编进内核可以少掉一大半启动阶段的麻烦。
3.2 设备树里的USB接口描述
如果你用的是带USB Host控制器的ARM平台,还需要确认设备树里USB Host节点状态正常,并且有独立的供电引脚和复位引脚控制。EC20常用的一招是:用GPIO控制模块的RESET和PWRKEY引脚。设备树里通常不直接描述EC20本身(因为它是USB枚举设备),但会定义USB口和GPIO:
&usbotg1 { status = "okay"; dr_mode = "host"; /* 必须host模式 */ vbus-supply = <®_ec20_power>; /* 模块供电 */ }; pinctrl_ec20: ec20grp { fsl,pins = < /* RESET引脚 */ MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x80000000 /* PWRKEY引脚 */ MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x80000000 >; }; ec20_power: gpio-regulator { compatible = "regulator-fixed"; regulator-name = "ec20_3v8"; regulator-min-microvolt = <3800000>; regulator-max-microvolt = <3800000>; gpio = <&gpio1 3 GPIO_ACTIVE_HIGH>; enable-active-high; };这里有个容易踩的坑:EC20的典型工作电压是3.3V到3.8V,部分核心板在启动瞬间会产生较大的电流跌落。如果供电设计不够稳,最直接的表现是模块能枚举成功,但一拨号就死掉或重启。别急着去改驱动源码,先测量模块VCC引脚的纹波和压降,很多"驱动问题"其实是硬件供电问题。
3.3 如何给新PID添加源码头条记录
有时候你的模块固件升级后,PID变了,内核源码里没有对应记录。改源码的方式就是在qmi_wwan.c的products[]表里追加一条:
{ USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x1255, USB_CLASS_VENDOR_SPEC, 0xff, 0xff), .driver_info = (unsigned long)&qmi_wwan_info, },在option.c里也要记得加:
{ USB_DEVICE(0x2c7c, 0x1255) },这两处都改好,驱动重新编译,新的接口才会被正确识别。别只改一个地方,那种"AT口能出,网络口不出"的怪问题,往往就是只改了option表、漏了qmi_wwan表导致的。
4. 编译、加载与常见排错:设备节点不出现的根因定位
这一节是实战环节,也是出问题最多的地方。我按排查顺序来写,方便你复现。
4.1 第一步:先确认USB枚举是否成功
插上模块,执行:
lsusb如果看到ID 2c7c:0125 Quectel Wireless Solutions Co., Ltd. EC20,说明USB枚举正常。这一步看不到设备,后面的驱动工作都无从谈起。
如果设备出现但VID/PID不正常,比如只有一个原始的Qualcomm下载口05c6:9008,说明模块被误触发了下载模式。解决办法是用QFlash工具重新刷正常固件,或者在启动阶段把模块的BOOT_MODE引脚拉回正常工作电平。
4.2 第二步:查看内核日志
执行:
dmesg | grep -i qmi dmesg | grep -i option dmesg | grep -i wwan dmesg | tail -n 50正常日志里应该能看到类似:
usb 1-1: new high-speed USB device number 2 using ci_hdrc usb 1-1: New USB device found, idVendor=2c7c, idProduct=0125 qmi_wwan 1-1:1.4: cdc-wdm0: USB WDM device qmi_wwan 1-1:1.4 wwan0: register 'qmi_wwan' at usb-ci_hdrc.0-1, Qualcomm WWAN modem如果日志只到 "New USB device found" 就断了,后面没有qmi_wwan相关行,那么基本可以断定驱动没匹配上。这时再回头看内核配置里CONFIG_USB_NET_QMI_WWAN是否打开,以及PID是否在匹配表中。
4.3 第三步:对比 /dev/ 节点出现的规律
正常稳定状态下,EC20在/dev下会有一组设备节点:
/dev/ttyUSB0 -> 诊断口(DIAG) /dev/ttyUSB1 -> GPS 口 /dev/ttyUSB2 -> AT 口 /dev/ttyUSB3 -> AT/PPP 口 /dev/cdc-wdm0 -> QMI控制口但这里有个时序问题你心里要有数:模块开机后需要用PWRKEY引脚触发上电,或者通过AT指令重启。如果你的板子上电后直接给模块供了电,模块自己就会开机,节点出现的时机可能在系统起来之后几百毫秒到几秒不等,这取决于固件。做开机脚本时,千万别在系统起来后立即去操作这些节点,加一个等待和探测机制更稳妥。
4.4 第四步:确认工作模式是否处于QMI数据模式
如果AT口是正常的,你可以用串口工具发AT指令确认模块当前工作模式:
AT+QCFG="usbnet"返回0表示ECM模式,返回2表示QMI模式。如果你想用QMI上网,需要返回2。切换方法:
AT+QCFG="usbnet",2 AT+CFUN=1,1 # 重启射频栈改完后模块需要重启,重启后PID或接口描述可能会变化,重新dmesg确认一次。
4.5 第五步:分析驱动源码匹配表
到这一步依然没出来的话,就回到了上一节的操作:对照源码里的usb_device_id,手动把当前接口信息补进去。怎么获取接口信息?
cat /sys/bus/usb/devices/1-1/1-1:1.4/bInterfaceClass cat /sys/bus/usb/devices/1-1/1-1:1.4/bInterfaceSubClass cat /sys/bus/usb/devices/1-1/1-1:1.4/bInterfaceProtocol这三个值决定驱动匹配走的是哪个判断分支。QMI接口的信息通常是ff/ff/ff,AT串口走的是02/02/01。如果签名信息不对,也会匹配不上。
再往下就是更细的调试手段了,比如挂上USB总线trace、打开内核CONFIG_USB_DEBUG、用usbmon抓包。不过一般情况下,前面四步已经能覆盖九成以上的问题场景。
5. 拨号不止发ATD:协议栈、拨号工具以及保活机制
驱动层通了,模块枚举出来了,wwan0也出现了,但这仅仅代表"路修好了",真正要让网通,还需要在协议栈层面拨号。EC20的典型拨号方式有三种,我按推荐顺序列出来。
5.1 方式一:使用官方quectel-CM工具(最推荐)
quectel-CM是移远官方开源的一个连接管理工具,专门处理QMI拨号、IP地址获取、DNS配置、数据模板设置等动作,免去你直接裸调QMI协议。编译方式简单:
git clone https://github.com/QuectelOpenSourcelib/quectel-CM cd quectel-CM make ./quectel-CM -s cmet.3gpp # cmet.3gpp替换成实际APN工具运行后,正常情况下wwan0就会拿到IP地址:
ip addr show wwan0它做的事情总结起来就几步:打开/dev/cdc-wdm0,发送QMI请求设置IP模式和数据模板,再向模块发起拨号,最后配置本地IP、路由和DNS。细节上它会自己处理qmi接口上的WDS(Wireless Data Service)Set Up Packet,不需要你再额外跑DHCP。
5.2 方式二:使用ModemManager+NetworkManager(适合桌面或通用Linux)
ModemManager 是Linux系统级调制解调器管理框架,它对QMI的支持也很好。在Yocto或Debian系系统里,开启ModemManager后它能自动识别EC20,并以"移动宽带"连接的形式出现在NetworkManager里。好处是标准化,坏处是调试不容易,出问题时你看到的只是上层连接失败,不知道底层的真正原因。
5.3 方式三:AT指令 + PPP(老方法,不推荐)
pppd call ec20-ppp这种方式在EC20上性能差、稳定性差且配置繁琐。尤其是EC20对PPP模式的支持现在已经很弱了,很多人配了半天出现 "LCP: timeout sending Config-Requests" 就直接放弃。所以不用再走这条老路。
5.4 拨号链路的保活与断线重连
实网环境下,模块偶尔会掉线,原因可能是信号不稳、运营商侧释放资源、网络长时间无流量导致空闲断开。我建议你在应用层做这样几件事:
- 定期监测
wwan0是否仍有IP地址 - 周期性 ping 一个公网地址,判断链路是否真实可用
- 检测到链路异常时,先发AT指令查询
AT+QENG="servingcell"看驻网状态 - 如果模块没驻网,执行
AT+CFUN=1,1做射频重启 - 如果驻网正常但还是不通,把
quectel-CM进程拉起来重新拨号
另外提示一下:如果你用的是带锁卡的物联网卡,APN必须写对,很多云平台物联网卡需要使用专属的APN,这个可以在移远工具里用AT+CGDCONT=1,"IP","apn_name"预设,也可以在quectel-CM启动参数里动态传入。
6. 实测中遇到的坑与规避:天线、供电、散热与USB信号完整性
写到这里,我要从"源码和配置"角度跳出来,聊聊驱动真正跑起来之后容易踩的物理层坑。这些常见问题不是源码能解决的,但往往比源码问题更致命。
6.1 天线布局对驱动稳定性的影响
我调试过一个设备,现象很诡异:模块能拨号成功,但一放在金属外壳里,几分钟内必然掉线。后来发现板载天线调试不到位,驻波比偏高,导致射频灵敏度下降。这个和驱动完全无关,纯粹是天线问题。所以如果你发现"驱动都正常了,网络还是很差",先怀疑天线和馈线,别折腾源码。
天线测试的简单方法:在开阔无遮挡环境,用AT指令AT+CSQ看信号值,数值范围0到31,越小信号越差。如果贴近底板和离开底板时信号值差很多,就去处理天线区域的地铜和净空吧。
6.2 供电纹波:模块重启类问题的最常见根因
EC20在发射瞬间电流容易冲到2A以上,如果供电链路内阻偏大或电容储备不足,VCC上的电压会瞬间跌落,超出模块要求的电压下限,导致模块自动重启。重启后USB口重新枚举,驱动重新匹配,这时你会发现系统日志里多了一大段重新枚举的日志。如果这时你正好在连续拨号,就会产生"拨号拨一半就死了"的假象。
解决办法:VBAT引脚附近至少布局几十微法的大电容,确保从主供电到模块的走线足够粗、接触阻抗低;有条件的情况下用独立的开关电源芯片给模块供电,不要和其他大电流外设共用一个LDO。
6.3 散热问题会折寿,也影响驱动行为
EC20在满载收发数据时发热明显,如果整机散热不好,模块内部温度保护启动后会强制降频甚至断网。表面看起来像是"驱动挂了",实际上回一条AT+QTEMP就能看到温度值异常。量产设备一定要做好散热,或者在业务层做温控逻辑,在温度过高时主动降速。
6.4 USB信号完整性的坑
EC20的USB是高速USB 2.0,信号完整性很敏感。USB D+/D-走线要尽量短、等长、差分,并且做好包地。如果你的板子USB走线过长或参考地不连续,会出现一种很磨人的问题:模块在低温或稍微振动后掉USB枚举,重新插拔才能恢复。这种问题是原理图上看不出来的,只有上示波器看USB信号和测试。
6.5 量产测试脚本的参考做法
如果你打算把这个驱动方案做成产品,一定要设计一个量产测试流程,避免每一片板子都要人工折腾。我自己的做法是:
- 上电等待5秒,检查
/dev/cdc-wdm0是否存在 - 用AT指令检查模块固件版本和IMEI
- 用
quectel-CM拨号,等待获取到IP - 连续ping 20次公网地址,确认丢包率小于5%
- 跑完之后杀掉连接,记录日志
这套流程里,驱动相关的问题会集中在第1步暴露,网络相关的问题集中在第3和第4步暴露,哪一环报错就直接锁定排查方向。
关于驱动源码层面的内容,能写的实操细节基本都在上面了。EC20这个模块本身不算复杂,真正难的是把USB枚举、内核配置、拨号流程、射频硬件几个环节串起来理解。我个人的体会是,在嵌入式Linux里调这类4G模块,大多数时间不是在改源码,而是在做设备树配置、内核裁剪和硬件排查。源码读明白能帮你快速定位"是不是驱动不匹配",但最终问题能不能解决,往往取决于你有没有能力把从USB口到射频天线的整条链路都盘一遍。
本文还有配套的精品资源,点击获取