1. 程式人生 > >《LEDE開發》AR9331路由器掛載移遠EC20、EC21、EC25、AG35等4G模組

《LEDE開發》AR9331路由器掛載移遠EC20、EC21、EC25、AG35等4G模組

1、開發環境

開發板:AR9331

原始碼版本:LEDE(官網地址:https://downloads.openwrt.org/

編譯宿主系統:ubuntu12.04(vm虛擬機器)

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對路由器最相容,現在我是LEDE最新版本的,相容性一般,QMI撥號有時命令會被掛起,總體來說還算完美。

2.1 USB Serial驅動

當模組連線到USB序列驅動時,驅動程式將在目錄/dev中建立裝置檔案,
ttyUSB0/ttyUSB1/ttyUSB2/ttyUSB3/ttyUSB4

下圖為各個 /ttyUSB的功能

我們的目的是需要cdc-wdm0 這個裝置檔案

 

接下來就是講解如何移植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

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

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

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

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

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

#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 修改配置

第一步:進入配置環境
$make menuconfig
第二步:配置

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
NetWork   >>
	<*> wwan
	<*>chat
	<*>ppp
	<*>uqmi
Utilities
	-*- comgt
	<*> comgt-ncm 
	<*>usb-modeswitch
Luci
1. Collections
  		<*> luci
3. Applications
  		<*> luci-app-multiwan (optional to support multiple 3g dongles)
  		<*> luci-app-qos (optional to provide QOS support)
6. Protocols
  		<*> luci-proto-3g
  		-*- luci-proto-ppp

2.4 編譯測試

以上操作完成後就是編譯原始碼了。

$ make V=s

將韌體燒寫進入板子中,插入4G模組,在dev目錄下檢視,就會出現cdc-wdm0 這個裝置檔案

有以上資訊表示驅動配置成功,接下來就撥號了。

3.5 撥號上網

撥號程式仍然使用移遠提供的quectel-CM,這是一個4G連線管理程式,這裡沒什麼說的,執行:

$mips-openwrt-linux-gcc *.c -o quectel-CM -lpthread –ldl

【注】也可將quectel-CM下的Makefile檔案修改成如下,然後 make

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開發板中,執行:

chmod 777 quectel-CM   #先改變許可權
./quectel-CM &

出現如下效果,說明撥號成功:

 接下來ping一下百度首頁地址,看是否可以聯網。

說明測試成功,開發板可以上網了。

3.6 後續問題

問題1:能ping通百度IP地址,無法ping通其域名www.baidu.com

這裡需要新增DNS解析伺服器的地址,在/etc目錄配置resolv.conf檔案新增DNS客戶,它包含了主機的域名搜尋順序和DNS伺服器的地址。開發板執行:

vim /etc/resolv.conf

編輯內容:

nameserver 114.114.114.114  #國內的DNS
nameserver 8.8.8.8          #國外的DNS

接下來,ping www.baidu.com

 問題2:路由器能上網後,其他裝置連線此路由器不能上網

ifconfig -a 看是否有wwan0這個埠

然後修改配置檔案

vim /etc/config/network

新增如下內容:

config interface 'wan'
        option  device '/dev/cdc-wdm0'
        option proto 'qmi'
        option apn  'cnnet'
        option username 'card'
        option password 'card'
        option ifname 'wwan0'

登入到路由器web介面,點選網路下的介面,也就是network下的interface,會發現有這麼一個裝置,點選連線


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

【附】移遠提供的撥號程式和參考文件下載地址: