Linux下对接EC20,EC25,4G模组驱动安装

5,310 阅读7分钟

Linux 下安装 移远4G模块驱动,并调试

安装环境

  • ubuntu 18.04
  • 内核linux-Kernel-4.15.0
  • 对接移远的 EC25的 4G 模组.

注意事项

  • 如果碰到 AT命令 一组命令都正常. 但是用PPP或者 gobinet +QconnectManager 都拨号不成功的话,得思考一下相关桌面系统有没有安装其他的网络或者4G拨号工具.比如我使用的ubuntu 安装了modemmanager,得把它卸载掉apt-get remove modemmanager.

安装前准备

  • 下载内核源码
  1. sudo apt install linux-source-4.15.0
  2. 或者 到https://mirrors.edge.kernel.org/pub/linux/kernel/下载源码
  • 有些系统被设置了 禁止apt
# 在终端输入下面命令强制解锁
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock

sudo rm /var/lib/dpkg/lock-frontend
  1. 编译内核需要一些图形化处理的库
sudo apt-get install libncurses5-dev libssl-dev
sudo apt-get install build-essential openssl
sudo apt-get install zlibc minizip 
sudo apt-get install libidn11-dev libidn11

如果报错:某某 依赖没有被安装,去pkgs.org 该网站下载 例如: libncurses5-dev_6.1-1ubuntu1.18.04_amd64.deb
下载好后 dpkg -i 路径/

特别注意modemmanager

  • 这是个坑,因为modemmanager 也是拨号上网的程序,只要把它卸载了才能拨号成功.
  • apt-get remove modemmanager

更改内核源码

添加USB 相关的内核源码

  • add VID 和 PID
  1. /drivers/usb/serial/option.c
static const struct usb_device_id option_ids[] = {
	#if 1 //Added by Quectel 
	{ USB_DEVICE(0x2C7C, 0x0125) }, /* Quectel EC25 */ 
	#endif 
}
  1. /drivers/usb/serial/qcserial.c
# 删除这行
{USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */ 
  1. /drivers/net/usb/qmi_wwan.c
# 删除这行
{QMI_GOBI_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */ 
  • 添加USB数据包传输机制
  1. /drivers/usb/serial/usb_wwan.c
static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
				      int endpoint,
				      int dir, void *ctx, char *buf, int len,
				      void (*callback) (struct urb *))
{
	struct usb_serial *serial = port->serial;
	struct urb *urb;

	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */
	if (!urb)
		return NULL;

	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;
}

  • 添加系统的重启 和设备重启的机制
  1. /drivers/usb/serial/option.c
static struct usb_serial_driver option_1port_device = {
	.read_int_callback = option_instat_callback,
#ifdef CONFIG_PM
	.suspend           = usb_wwan_suspend,
	.resume            = usb_wwan_resume,
	#if 1 //Added by Quectel
	.reset_resume = usb_wwan_resume,
	#endif
#endif
};

  • Use GobiNet or QMI WWAN 使用 GobiNet 拨号
  1. /drivers/usb/serial/option.c
static int option_probe(struct usb_serial *serial,
			const struct usb_device_id *id)
{


	/*
	 * Allow matching on bNumEndpoints for devices whose interface numbers
	 * can change (e.g. Quectel EP06).
	 */
	if (device_flags & NUMEP2 && iface_desc->bNumEndpoints != 2)
		return -ENODEV;
	
	
	#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 
	 if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C) 
	 && serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4) 
	 return -ENODEV; 
	#endif
	
	#if 1 //Added by Quectel
	//For USB Auto Suspend
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9090)) {
		pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
		usb_enable_autosuspend(serial->dev);
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9003)) {
		pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
		usb_enable_autosuspend(serial->dev);
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9215)) {
		pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
		usb_enable_autosuspend(serial->dev);
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
		pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
		usb_enable_autosuspend(serial->dev);
	}
	#endif
	
	#if 1 //Added by Quectel
	//For USB Remote Wakeup
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9090)) {
		device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9003)) {
		device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6) &&
		serial->dev->descriptor.idProduct == cpu_to_le16(0x9215)) {
		device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
	}
	if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
		device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
	}
	#endif

	/* Store the device flags so we can use them during attach. */
	usb_set_serial_data(serial, (void *)device_flags);

	return 0;
}

添加 GobiNet Driver 拨号内核源码

添加QMI WWAN Driver 内核源码

  • 添加VID and PID
  1. /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(0x2C7C, 0x0125, 4) }, /* Quectel EC25 */ 
	#endif
}
  1. /drivers/usb/serial/qcserial.c
# 删除这行
{USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */ 
  1. /drivers/net/usb/qmi_wwan.c
# 删除这行
{QMI_GOBI_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */ 
  • Add Support for Raw IP Mode
#include <linux/module.h>
#include <linux/sched/signal.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/mii.h>
#include <linux/rtnetlink.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#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




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;
}




static const struct driver_info	qmi_wwan_info = {
	.description	= "WWAN/QMI device",
	.flags		= FLAG_WWAN | FLAG_SEND_ZLP,
	.bind		= qmi_wwan_bind,
	.unbind		= qmi_wwan_unbind,
	.manage_power	= qmi_wwan_manage_power,
	.rx_fixup       = qmi_wwan_rx_fixup,
#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 

配置make menuconfig

空 代表无  * 代表 	M 代表只编译,加入后才有效

[*] Device Drivers → 
	[*] USB Support → 
		[*] USB Serial Converter support → 
 			[*] USB driver for GSM and CDMA modems 

[*] Device Drivers → 
	[*] Network device support → 
		[*] PPP (point-to-point protocol) support 

 			
[*] Device Drivers → 
	-*- Network device support → 
		USB Network Adapters → 
			{*} Multi-purpose USB Networking Framework 
				<*> QMI WWAN driver for Qualcomm MSM based 3G and LTE modems 
  • 单独编译某些内核源码
# 编译USB方面的


sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/usb/serial obj-m=option.o modules 
sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/usb/serial obj-m=usb_wwan.o 
modules 
sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/usb/serial obj-m=qcserial.o modules


sudo cp drivers/usb/serial/option.ko /lib/modules/`uname -r`/kernel/drivers/usb/serial 
sudo cp drivers/usb/serial/usb_wwan.ko /lib/modules/`uname -r`/kernel/drivers/usb/serial 
sudo cp drivers/usb/serial/qcserial.ko /lib/modules/`uname -r`/kernel/drivers/usb/serial 
sudo depmod 
sudo reboot 


# 编译 GobiNet Driver
sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/net/usb obj-m=GobiNet.o modules 
# 这个是重复的模块,上面编译了下面就可以不加
sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/usb/serial obj-m=qcserial.o modules 

 
sudo cp drivers/net/usb/GobiNet.ko /lib/modules/`uname -r`/kernel/drivers/net/usb 
sudo cp drivers/usb/serial/qcserial.ko /lib/modules/`uname -r`/kernel/drivers/usb/serial 
sudo depmod 
sudo reboot 

 

# 编译 QMI WWAN Driver

sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/net/usb obj-m=qmi_wwan.o modules 
# 这个是重复的模块,上面编译了下面就可以不加
sudo make -C /lib/modules/`uname -r`/build M=`pwd`/drivers/usb/serial obj-m=qcserial.o modules 

 
sudo cp drivers/net/usb/qmi_wwan.ko /lib/modules/`uname -r`/kernel/drivers/net/usb 
sudo cp drivers/usb/serial/qcserial.ko /lib/modules/`uname -r`/kernel/drivers/usb/serial 
sudo depmod 
sudo reboot 

配置 netplan 双网卡和路由信息

  • /etc/netplan/01-netcfg.yaml
# Let NetworkManager manage all devices on this system

# renderer: NetworkManager   enp0s20f0u4i4
network:
  ethernets:
    enp4s0:
      dhcp4: false
      addresses: [192.168.3.100/24]
      routes:
        - to: 0.0.0.0/0
          via: 255.255.255.0
          metric: 0
      optional: true
    enp3s0:
      dhcp4: true
      optional: true
    wwan0:
      dhcp4: true
      routes:
        - to: 0.0.0.0/0
          via: 0.0.0.0
          metric: 100
      optional: true   
    enp0s20f0u4i4:
      dhcp4: true
      routes:
        - to: 0.0.0.0/0
          via: 0.0.0.0
          metric: 100
      optional: true      
  version: 2
  renderer: networkd

开启 WIFI热点

  • nm-connection-editor

关闭系统更新

  1. 界面操作
    系统设置-->软件和更新-->更新,将“自动检查更新”和“有新版本时通知我”设置为“从不“,关闭对话框完成设置。
  2. 修改配置文件
    修改配置文件/etc/apt/apt.conf.d/10periodic。全部将1改为0
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";
APT::Periodic::Unattended-Upgrade "0";