0%

[OpenWrt]挂载移远EC20、EC21、EC25、AG35等4G模块

1、开发环境

开发板:AR9331 Newifi3等
源码版本:Openwrt(源码地址:https://git.lede-project.org/source.git)
源码编译宿主系统:ubuntu12.04 ubuntu14.04版本或者以上都可以
4G模块:移远EC20 EC20 EC25 AG35等
参考文档:Quectel_WCDMA&LTE_Linux_USB_Driver_User_Guide_V1.7.pdf

2、操作步骤

【说明】虽然大家开发板型号不一,有AR、RT、MT版本;源码也有CC、LEDE、潘多拉、老毛子的不同;但是挂载4G驱动的方法都一样。

目前EC20和EC25对路由器最兼容,现在我是Openwrt最新版本的,兼容性完美,QMI拨号非常稳定。

2.1 USB Serial驱动

当4G模块连接到USB串行驱动时,驱动程序将在目录/dev中创建设备文件,
ttyUSB0/ttyUSB1/ttyUSB2/ttyUSB3/ttyUSB4
20190106112009894.png

下图为各个4G模块 /ttyUSB的功能

20190106112110400.png

我们的目的是需要cdc-wdm0 这个设备文件

20190106112435618.png

接下来就是讲解如何移植USB Serial。

2.1.1 增加PID&VID

要想识别模块,客户应该在下面添加模块维和PID信息(支持EC20、EC20、EC25、AG35等4G模块)

File: [KERNEL]/drivers/usb/serial/option.c

比如AR9331的KERNEL目录在build_dir/target-mips_24kc_musl/linux-ar71xx_generic/linux-4.4.79

1
2
3
4
5
6
7
8
9
10
11
12
13
static const struct usb_device_id option_ids[] = {
#if 1 //Added by Quectel
{ USB_DEVICE(0x05C6, 0x9090) }, /* Quectel UC15 */
{ USB_DEVICE(0x05C6, 0x9003) }, /* Quectel UC20 */
{ USB_DEVICE(0x2C7C, 0x0125) }, /* Quectel EC25 */
{ USB_DEVICE(0x2C7C, 0x0121) }, /* Quectel EC21 */
{ USB_DEVICE(0x05C6, 0x9215) }, /* Quectel EC20 */
{ USB_DEVICE(0x2C7C, 0x0191) }, /* Quectel EG91 */
{ USB_DEVICE(0x2C7C, 0x0195) }, /* Quectel EG95 */
{ USB_DEVICE(0x2C7C, 0x0306) }, /* Quectel EG06/EP06/EM06 */
{ USB_DEVICE(0x2C7C, 0x0296) }, /* Quectel BG96 */
{ USB_DEVICE(0x2C7C, 0x0435) }, /* Quectel AG35 */
#endif

【注】只添加#if 1 到 #endif的内容,具体位置添加位置需要读者自己注意。

2.1.2 添加零包处理

根据USB协议的要求,客户需要添加处理零数据包的机制。
For Linux Kernel Version newer than 2.6.34:
File: [KERNEL]/drivers/usb/serial/usb_wwan.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint,
int dir, void *ctx, char *buf, int len,void (*callback) (struct urb *))
{
……
usb_fill_bulk_urb(urb, serial->dev,
usb_sndbulkpipe(serial->dev, endpoint) | dir,
buf, len, callback, ctx);
#if 1 //Added by Quectel for zero packet
if (dir == USB_DIR_OUT) {
struct usb_device_descriptor *desc = &serial->dev->descriptor;
if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9090))
urb->transfer_flags |= URB_ZERO_PACKET;
if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9003))
urb->transfer_flags |= URB_ZERO_PACKET;
if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9215))
urb->transfer_flags |= URB_ZERO_PACKET;
if (desc->idVendor == cpu_to_le16(0x2C7C))
urb->transfer_flags |= URB_ZERO_PACKET;
}
#endif
return urb;
}

2.1.3 增加休眠后唤醒接口

当MCU进入暂停/休眠模式时,一些USB主机控制器/USB集线器将失去电源或重新设置,并且在MCU退出暂停/休眠模式后,它们不能恢复USB设备。请添加以下语句以启用重新设置恢复过程。
For Linux kernel version higher than 3.4:
File: [KERNEL]/drivers/usb/serial/option.c

1
2
3
4
5
6
7
8
9
10
static struct usb_serial_driver option_1port_device = {
……
#ifdef CONFIG_PM
.suspend = usb_wwan_suspend,
.resume = usb_wwan_resume,
#if 1 //Added by Quectel
.reset_resume = usb_wwan_resume,
#endif
#endif
};

2.1.4 使用 GobiNet or QMI WWAN

如果客户使用ucxx/ec2x/egxx/EP06/EM06/BG96/AG35,并要求GobiNet或QMI WWAN,请添加以下语句,以防止这些模块接口4被用作USB串行设备。
For Linux Kernel Version newer than 2.6.30:
File: [KERNEL]/drivers/usb/serial/option.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int option_probe(struct usb_serial *serial, const struct usb_device_id *id) {
struct usb_wwan_intf_private *data;
……
#if 1 //Added by Quectel
//Quectel UC20's interface 4 can be used as USB network device
if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
serial->dev->descriptor.idProduct == cpu_to_le16(0x9003)
&& serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
return -ENODEV;
//Quectel EC20's interface 4 can be used as USB network device
if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
serial->dev->descriptor.idProduct == cpu_to_le16(0x9215)
&& serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
return -ENODEV;
//Quectel EC25&EC21&EG91&EG95&EG06&EP06&EM06&BG96/AG35's interface 4 can be used as USB network device
if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)
&& serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
return -ENODEV;
#endif
/* Store device id so we can use it during attach. */
usb_set_serial_data(serial, (void *)id);
return 0;
}

2.2 QMI WWAN驱动

2.2.1 Add VID and PID

QMI WWAN driver source file is [KERNEL]/drivers/net/usb/qmi_wwan.c.
File: [KERNEL]/drivers/net/usb/qmi_wwan.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static const struct usb_device_id products[] = {
#if 1 //Added by Quectel
#ifndef QMI_FIXED_INTF
/* map QMI/wwan function by a fixed interface number */
#define QMI_FIXED_INTF(vend, prod, num) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE |
USB_DEVICE_ID_MATCH_INT_INFO, \
.idVendor = vend, \
.idProduct = prod, \
.bInterfaceClass = 0xff, \
.bInterfaceSubClass = 0xff, \
.bInterfaceProtocol = 0xff, \
.driver_info = (unsigned long)&qmi_wwan_force_int##num,
#endif
{ QMI_FIXED_INTF(0x05C6, 0x9003, 4) }, /* Quectel UC20 */
{ QMI_FIXED_INTF(0x2C7C, 0x0125, 4) }, /* Quectel EC25 */
{ QMI_FIXED_INTF(0x2C7C, 0x0121, 4) }, /* Quectel EC21 */
{ QMI_FIXED_INTF(0x05C6, 0x9215, 4) }, /* Quectel EC20 */
{ QMI_FIXED_INTF(0x2C7C, 0x0191, 4) }, /* Quectel EG91 */
{ QMI_FIXED_INTF(0x2C7C, 0x0195, 4) }, /* Quectel EG95 */
{ QMI_FIXED_INTF(0x2C7C, 0x0306, 4) }, /* Quectel EG06/EP06/EM06 */
{ QMI_FIXED_INTF(0x2C7C, 0x0296, 4) }, /* Quectel BG96 */
{ QMI_FIXED_INTF(0x2C7C, 0x0435, 4) }, /* Quectel AG35 */
#endif

2.2.2 Add Support for Raw IP Mode

File: [KERNEL]/drivers/net/usb/qmi_wwan.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <linux/usb/usbnet.h>
#include <linux/usb/cdc-wdm.h>
#if 1 //Added by Quectel
#include <linux/etherdevice.h>
struct sk_buff *qmi_wwan_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
{
if (dev->udev->descriptor.idVendor != cpu_to_le16(0x2C7C))
return skb;
// Skip Ethernet header from message
if (skb_pull(skb, ETH_HLEN)) {
return skb;
} else {
dev_err(&dev->intf->dev, "Packet Dropped ");
}
// Filter the packet out, release it
dev_kfree_skb_any(skb);
return NULL;
}
#include <linux/version.h>
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
__be16 proto;
if (dev->udev->descriptor.idVendor != cpu_to_le16(0x2C7C))
return 1;
/* This check is no longer done by usbnet */
if (skb->len < dev->net->hard_header_len)
return 0;
switch (skb->data[0] & 0xf0) {
case 0x40:
proto = htons(ETH_P_IP);
break;
case 0x60:
proto = htons(ETH_P_IPV6);
break;
case 0x00:
if (is_multicast_ether_addr(skb->data))
return 1;
/* possibly bogus destination - rewrite just in case */
skb_reset_mac_header(skb);
goto fix_dest;
default:
/* pass along other packets without modifications */
return 1;
}
if (skb_headroom(skb) < ETH_HLEN)
return 0;
skb_push(skb, ETH_HLEN);
skb_reset_mac_header(skb);
eth_hdr(skb)->h_proto = proto;
memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
fix_dest:
memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
return 1;
}
/* very simplistic detection of IPv4 or IPv6 headers */
static bool possibly_iphdr(const char *data)
{
return (data[0] & 0xd0) == 0x40;
}
#endif
#endif
……
/* if follow function exist, modify it as below */
static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
{
……
#if 1 //Added by Quectel
if (dev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
dev_info(&intf->dev, "Quectel
EC25&EC21&EG91&EG95&EG06&EP06&EM06&BG96&AG35 work on RawIP mode\n");
dev->net->flags |= IFF_NOARP;
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
/* make MAC addr easily distinguishable from an IP header */
if (possibly_iphdr(dev->net->dev_addr)) {
dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
}
#endif
usb_control_msg(
interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE
0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
1, //active CDC DTR
intf->cur_altsetting->desc.bInterfaceNumber,
NULL, 0, 100);
}
#endif
err:
return status;
}
……
/* if follow function exist, modify it as below */
static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf)
{
……
#if 1 //Added by Quectel
if (dev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
dev_info(&intf->dev, "Quectel EC25&EC21&
EG91&EG95&EG06&EP06&EM06&BG96&AG35 work on RawIP mode\n");
dev->net->flags |= IFF_NOARP;
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
/* make MAC addr easily distinguishable from an IP header */
if (possibly_iphdr(dev->net->dev_addr)) {
dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
}
#endif
usb_control_msg(
interface_to_usbdev(intf),
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE
0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
1, //active CDC DTR
intf->cur_altsetting->desc.bInterfaceNumber,
NULL, 0, 100);
}
#endif
err:
return status;
}
……
/* if follow struct exist, modify it as below */
static const struct driver_info qmi_wwan_info =
{
……
#if 1 //Added by Quectel
.tx_fixup = qmi_wwan_tx_fixup,
.rx_fixup = qmi_wwan_rx_fixup,
#endif
}

……
/* if follow struct exist, modify it as below */
static const struct driver_info qmi_wwan_force_int4 = {
……
#if 1 //Added by Quectel
.tx_fixup = qmi_wwan_tx_fixup,
.rx_fixup = qmi_wwan_rx_fixup,
#endif
};
/* if follow struct exist, modify it as below */
static const struct driver_info qmi_wwan_shared = {
……
#if 1 //Added by Quectel
.tx_fixup = qmi_wwan_tx_fixup,
.rx_fixup = qmi_wwan_rx_fixup,
#endif
};

2.3 修改配置

第一步:进入配置环境

1
$make menuconfig

第二步:配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Kernel modules >>
USB Support >>
<*> Kmod -usb-core
-*-Kmod -usb-net
-*- kmod-usb-net-cdc-ether
<*> kmod-usb-net-cdc-mbim
-*- kmod-usb-net-cdc-ncm
<*> kmod-usb-net-cdc-subset
<*>kmod-usb-net-qmi-wwan
<*>Kmod-usb-ohci //这个选项一定要勾选,否则可能无法在系统中查看设备
<*>Kmod-usb-serial
<*>Kmod-usb-serial-option
<*>Kmod-usb-serial-wwan
<*>kmod-usb-uhci
<*>Kmod-usb2
1
2
3
4
5
NetWork   >>
<*> wwan
<*>chat
<*>ppp
<*>uqmi
1
2
3
4
Utilities
-*- comgt
<*> comgt-ncm
<*>usb-modeswitch
1
2
3
4
5
6
7
8
9
Luci
1. Collections
<*> luci
2. Applications
<*> luci-app-multiwan (optional to support multiple 3g dongles)
<*> luci-app-qos (optional to provide QOS support)
3. Protocols
<*> luci-proto-3g
-*- luci-proto-ppp

2.4 编译测试

以上操作完成后就是编译源码了。

1
$ make V=s

将编译好固件烧写进入板子中,插入4G模块,在dev目录下查看,就会出现cdc-wdm0 这个设备文件
20190106112435618.png

有以上信息表示驱动配置成功,接下来就拨号了。

2.5 拨号上网

2.5.1 安装交叉编译工具

进入源码目录,执行mkae menuconfig

1
2
cd lede_source
make menuconfig

选中Toolchain

20190217123720531.png

保存,编译

1
make V=99

找到交叉工具链的位置:
20190217124144431.png
这里我们演示将交叉工具链安装到 ubuntu 的“/opt”目录下。 首先切换到 openwrt 源码的根目录下,输入如下命令:

1
2
cd ~/lede_source/bin/targets/ramips/mt7621
sudo tar xjvf openwrt-toolchain-ramips-mt7621_gcc-7.4.0_musl.Linux-x86_64.tar.bz2 -C /opt

最后,设置环境变量

1
sudo vi /etc/bash.bashrc

在最后一行添加
1
2
export PATH=/opt/openwrt-toolchain-ramips-mt7621_gcc-7.4.0_musl.Linux-x86_64/toolchain-mipsel_24kc_gcc-7.4.0_musl/bin:$PATH
export STAGING_DIR=/your_openwrt_path/staging_dir

《注意》上面这个“STAGING_DIR”变量中的“your_openwrt_path”是读者实际放 openwrt源码的根目录,如果“STAGING_DIR”变量不设置的话,会在用交叉工具链编译文件时有警告,但是不影响编译结果。最后保存退出。接着在终端执行以下命令:
1
source /etc/bash.bashrc

测试是否安装成功:
1
mipsel-openwrt-linux-gcc -v

2.5.2 编译拨号应用程序

拨号程序仍然使用移远提供的quectel-CM(下载地址在文章末尾),这是一个4G连接管理程序,这里没什么说的,执行:

1
$mipsel-openwrt-linux-musl-gcc *.c -o quectel-CM -lpthread –ldl

【注】也可将quectel-CM下的Makefile文件修改成如下,然后 make
1
2
3
4
5
6
7
8
9
CROSS-COMPILE:= mips-openwrt-linux-   #AR9331开发板的交叉编译工具(其他的自行修改)
CC:=$(CROSS-COMPILE)gcc
LD:=$(CROSS-COMPILE)ld
release: clean
$(CC) -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread -ldl
debug: clean
$(CC) -Wall -g QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread -ldl
clean:
rm -rf quectel-CM *~

【注】移远提供的拨号程序下载地址在文章末尾(包含参考文档)。

交叉编译后得到可执行文件:“quectel-CM” ,然后将quectel-CM可执行文件用wincp软件拷贝到AR9331开发板中,执行:

1
2
chmod 777 quectel-CM   #先改变权限
./quectel-CM &

出现如下效果,说明拨号成功:
20190106115143893.png
接下来ping一下百度首页地址,看是否可以联网。
20190106115422780.png
说明测试成功,开发板可以上网了。

2.6 后续问题

问题1:能ping通百度IP地址,无法ping通其域名www.baidu.com

这里需要添加DNS解析服务器的地址,在/etc目录配置resolv.conf文件添加DNS客户,它包含了主机的域名搜索顺序和DNS服务器的地址。开发板执行:

1
vim /etc/resolv.conf

编辑内容:
1
2
nameserver 114.114.114.114  #国内的DNS
nameserver 8.8.8.8 #国外的DNS

接下来,ping www.baidu.com

20190106120306699.png

问题2:路由器能上网后,其他设备连接此路由器不能上网

ifconfig -a 看是否有wwan0这个端口
20190106120605223.png
然后修改配置文件

1
vim /etc/config/network

添加如下内容:

1
2
3
4
5
6
7
config interface 'wan'
option device '/dev/cdc-wdm0'
option proto 'qmi'
option apn 'cnnet'
option username 'card'
option password 'card'
option ifname 'wwan0'

20190106120731175.png
登录到路由器web界面,点击网路下的接口,也就是network下的interface,会发现有这么一个设备,点击连接
20190106120836563.png

到此为止,4G路由器就搞定了。

【附】移远提供的拨号程序和参考文档下载地址:https://download.csdn.net/download/hunzhangzui9837/10899023
————————————————
版权声明:本文为CSDN博主「小白clever」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hunzhangzui9837/article/details/85916965