1.本文档参考资料为Quectel_WCDMA<E_Linux_USB_Driver_User_Guide_V1.6.pdf,
实现QMI拨号,只需要参考下列章节即可
EC20有多个版本,看是否是EC20 R2.0还是普通EC20。
2.修改内核代码
修改USB串口驱动
File : [KERNEL]/drivers/usb/serial/option.c
staticconst 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(0x05C6, 0x9215) }, /* Quectel EC20 */
{ USB_DEVICE(0x2C7C, 0x0125) }, /* Quectel EC25/EC20 R2.0 */
{ USB_DEVICE(0x2C7C, 0x0121) }, /* Quectel EC21 */
#endif
2.1.1 . Add the Zero PacketMechanism
For Linux Kernel Version newer than 2.6.34:
File:[KERNEL]/drivers/usb/serial/usb_wwan.c
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 Quectelfor 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.2. Add Reset Resume
File: [KERNEL]/drivers/usb/serial/option.c
static struct usb_serial_driveroption_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.2.5 Use GobiNet or QMI WWAN
File: [KERNEL]/drivers/usb/serial/option.c
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'sinterface 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 interface4 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;
//QuectelEC21&EC25&EC20 R2.0'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 duringattach. */
usb_set_serial_data(serial, (void *)id);
return 0;
}
3. QMI WWAN Driver
3.1.1 Add VID and PID
fileis "[KERNEL]/drivers/net/usb/qmi_wwan.c".
static const struct usb_device_idproducts[] = {
#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(0x05C6, 0x9215, 4) }, /* Quectel EC20 */
{ QMI_FIXED_INTF(0x2C7C, 0x0125, 4) }, /* Quectel EC25/EC20R2.0 */
{ QMI_FIXED_INTF(0x2C7C, 0x0121, 4) }, /* Quectel EC21 */
#endif
3.1.2 Add Support for Raw IP Modefor EC21/EC25/EC20 R2.0
file is"[KERNEL]/drivers/net/usb/qmi_wwan.c"
#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, structsk_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, structsk_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 IPv4or IPv6 headers */
static bool possibly_iphdr(const char*data)
{
return (data[0] & 0xd0) == 0x40;
}
#endif
#endif
/* if follow function exist, modify itas 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 EC21&EC25&EC20 R2.0work 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 CDCDTR
intf->cur_altsetting->desc.bInterfaceNumber,
NULL, 0, 100);
}
#endif
err:
return status;
}
/* if follow struct exist, modify itas below */
static const struct driver_info qmi_wwan_info =
{
……
#if 1 //Added by Quectel
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0 ))
.tx_fixup =qmi_wwan_tx_fixup,
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
.rx_fixup =qmi_wwan_rx_fixup,
#endif
#endif
}
/* if follow struct exist, modify itas below */
static const struct driver_infoqmi_wwan_force_int4 = {
……
#if 1 //Added by Quectel
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0 ))
.tx_fixup =qmi_wwan_tx_fixup,
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
.rx_fixup =qmi_wwan_rx_fixup,
#endif
#endif
};
配置选择:
NetWork >>
wwan .....................GenericOpenWrt 3G/4G proto handler
Kernel modules >>
USB Support >>
Kmod -usb-core
Kmod -usb-net
>> kmod-usb-net-qmi-wwan
Kmod-usb-ohci //这个选项一定要勾选,否则可能无法在系统中查看设备
Kmod-usb-serial
Kmod-usb-serial-option
Kmod-usb-serial-wwan
kmod-usb-uhci
Kmod-usb2
成功过后,可以看到Openwrt系统启动打印信息,
查看dev下设备号 ls /dev
出现以上信息,表示驱动正常,下面可以启动拨号软件进行拨号。
编译在openwrt终端上可执行的EC20R2.0拨号程序quectel-CM
将Quectel_Linux_ConnectManager_SR01A01V21.zip进行解压,将文件下的quectel-CM交叉编译,编译为在板子openwrt15.05下可执行文件。(以下操作都是在非root用户下操作的)
1.交叉编译工具链编译的时报没有STAGING_DIR,它在ubuntu上的openwrt的目录下openwrt/staging_dir ,在ubuntu上键入下面命令
exportSTAGING_DIR=/home/sanliang/workspace/openwrt/staging_dir
2.设置交叉工具链的环境变量
PATH=$PATH:/home/sanliang/workspace/openwrt1/openwrt/staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/bin
注意在这个bin下是含有我们的mipsel-openwrt-linux-gcc编译工具的
3.将quectel-CM下的Makefile文件修改成如下,然后 make
CROSS-COMPILE:=mipsel-openwrt-linux-
得到可在openwrt下可执行的quectel-CM文件。
4.将quectel-CM文件上传到板子上,给予可执行权限 chmod 777 quectel-CM
后台运行: ./quectel-CM &
拨号成功打印信息如下:
5.添加DNS解析服务器的地址
现在已经拨号成功,但是可能无法pingwww.baidu.com,但是可以直接ping 百度的IP,这 需要设置/etc/resolv.conf来添加DNS解析服务器的地址,文件/etc/resolv.conf配置DNS客户, 它包含了主机的域名搜索顺序和DNS服务器的地址,每一行应包含一个关键字和一个或多 个的由空格隔开的参数。
我这里只设置了两个DNS,如下:
nameserver114.114.114.114
nameserver8.8.8.8
再次pingwww.baidu.com ,发现已经可以ping 通域名了