介绍
本文档介绍如何在Linux操作系统中集成用于Quectel模块的USB驱动程序,以及如何成功加载USB驱动程序后使用模块。
适用产品
本文件适用于以下Quectel模块。
表1:适用产品
产品概述
Quectel UMTS/HSPA/LTE模块上的USB包含几个不同的功能接口。下表描述了Linux系统中不同模块的接口信息。
表2:接口信息
系统设置
本章主要介绍Linux下USB协议栈的总体结构以及如何使用USB协议栈
串行、CDC ACM、GobiNet和QMI WWAN驱动程序,以及如何编译和加载驱动程序。
Linux USB驱动程序结构
USB是一种层次化的总线结构。USB设备和主机之间的数据传输是由USB控制器实现。下图说明了USB驱动程序的体系结构。
Linux USB主机
驱动程序包括三部分:USB主机控制器驱动程序、USB内核驱动程序和USB设备驱动程序。
图1:USB驱动程序结构
USB主机控制器驱动程序是层次结构的底层,是一个可以直接和硬件交互的软件模块
USB核是整个USB主机驱动程序的核心,负责USB总线、USB总线的管理
设备和USB总线带宽;它为USB设备驱动程序提供接口,通过该接口
应用程序可以访问USB系统文件。
USB设备驱动程序与应用程序交互,主要提供访问
特定的USB设备。
USB串行驱动程序
如果客户使用的是UC15/UC20/EC20/EC21/EC25/EG91/EG95/BG96/AG35/EG06/EP06/EM06/EG12/EP12/EM12/EG16/EG18需要USB串行驱动程序,请阅读了解详。否则跳过
当模块连接到USB串行驱动程序时,驱动程序将在目录/dev中创建设备文件,命名如下:
ttyUSB0/ttyUSB1/ttyUSB2…
以下部分介绍如何集成USB串行驱动程序。
添加VID和PID
为了识别模块,客户应添加以下模块VID和PID信息:
文件:[KERNEL]/drivers/usb/serial/option.c
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, 0x0512) }, /* Quectel EG12/EP12/EM12/EG16/EG18 */
{ USB_DEVICE(0x2C7C, 0x0296) }, /* Quectel BG96 */
{ USB_DEVICE(0x2C7C, 0x0435) }, /* Quectel AG35 */
#endif
对于EC20模块,如果内核源文件中存在以下文件和语句,请删除它们,因为它们将与EC20的USB驱动程序冲突。
文件:[KERNEL]/drivers/usb/serial/qcsserial.c
{USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
文件:[KERNEL]/drivers/net/usb/qmi_wwan.c
{QMI_GOBI_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
添加零包机制
根据USB协议的要求,用户需要在散装运输过程中添加处理零包的机制
对于高于2.6.34的Linux内核版本:
文件:[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.6.35的Linux内核版本:
File:[kernel]/drivers/usb/serial/option.c
/* Helper functions used by option_setup_urbs */
static struct urb *option_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;
}
添加重置恢复
当MCU进入暂停/睡眠状态时,某些USB主机控制器/USB集线器将断电或重置模式,并且在MCU退出挂起/休眠模式后,它们无法恢复USB设备。请添加以下语句启用重置恢复过程。
对于高于3.4的Linux内核版本:
文件:[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
};
对于低于3.5的Linux内核版本:
文件:[KERNEL]/drivers/usb/serial/usb-erial.c
/* Driver structure we register with the USB core */
static struct usb_driver usb_serial_driver = {
.name = "usbserial",
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.suspend = usb_serial_suspend,
.resume = usb_serial_resume,
#if 1 //Added by Quectel
.reset_resume = usb_serial_resume,
#endif
.no_dynamic_id = 1,
.supports_autosuspend = 1,
};
Enlarge Bulk out URBs
对于低于2.6.29的Linux内核版本,需要bulk out URBs去获得更快的上行速度
File: [KERNEL]/drivers/usb/serial/option.c
#define N_IN_URB 4
#define N_OUT_URB 4 //Quectel 1
#define IN_BUFLEN 4096
#define OUT_BUFLEN 4096 //Quectel 128
使用GobiNet或QMI WWAN
如果客户使用的是C20/EC20/EC21/EC25/EG91/EG95/BG96/AG35/EG06/EP06/EM06/EG12/EP12/EM12/EG16/EG18,需要GobiNet或QMI WWAN,请添加以下语句防止这些模块的接口4用作USB串行设备。
对于高于2.6.30的Linux内核版本:
文件:[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的接口4可以用作USB网络设备
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的接口4可用作USB网络设备
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的接口4可用作USB网络设备
if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)
&& serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
return -ENODEV;
#endif
/* 存储设备id,以便在连接期间使用 */
usb_set_serial_data(serial, (void *)id);
return 0;
}
对于低于2.6.31的Linux内核版本:
文件:[KERNEL]/drivers/usb/serial/option.c
static int option_startup(struct usb_serial *serial)
{
……
dbg("%s", __func__);
#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
……
}
修改内核配置
内核配置中有几个必选项,请按照以下步骤操作
配置内核:
步骤1:更改到内核目录
cd <your kernel directory>
第二步:设置环境变量,导入board的defconfig。以下是Raspeberry Pi board的例子
export ARCH=arm
export CROSS_COMPILE=arm-none-linux-gnueabi
make bcmrpi_defconfig
步骤3:编译内核。
make menuconfig
步骤4:启用配置USB串行选项
[*] Device Drivers →
[*] USB Support →
[*] USB Serial Converter support →
[*] USB driver for GSM and CDMA modems
Linux下PC核心模块驱动程序的构建与加载
请按照以下步骤将驱动程序构建为内核模块,并使用modprobe命令加载在PC机上实现Linux操作系统的模块。
步骤1:切换到内核目录
cd <your kernel directory>
步骤2:构建驱动程序
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
步骤3:加载驱动程序并重新启动。
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
用于UG9的CDC ACM驱动程序
步骤2:生成驱动程序
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=u