USB 是一种分层总线结构。USB 设备与主机之间的数据传输由 USB 控制器控制。Linux USB 驱动程序架构如下图所示。Linux USB 主机驱动包括三部分:USB 主机控制器驱动、USB 核心和 USB 设备驱动。

模块加载 USB 转串口 option 驱动程序后,在/dev 目录下创建 ttyUSB0、ttyUSB1 和 ttyUSB2 等设备文件。以下章节介绍如何将 USB 转串口 option 驱动程序移植到 Linux 操作系统中。
- // 需要修改的内核配置
- longan/kernel/linux-4.9/.config
-
- // 需要修改的驱动文件
- longan/kernel/linux-4.9/drivers/usb/serial/option.c
- longan/kernel/linux-4.9/drivers/usb/serial/usb_wwan.c
-
- // 需要用到的驱动文件
- longan/kernel/linux-4.9/drivers/net/usb/cdc-acm.c
- longan/kernel/linux-4.9/drivers/net/usb/cdc-ether.c
1.在 option_ids 列表内增加 EC200M-CN 的 PID\VID, 这样才能识别到该 USB 设备为串口设备,文档内可查。
- // linux-4.9/drivers/usb/serial/option.c
- static const struct usb_device_id option_ids[] = {
- #ifdef SUPPORT_QUECTEL
- { USB_DEVICE(0x2C7C, 0x6002) }, // support EC200S/EC200M
- #endif
- ......
- }
2.一个 USB 设备可以有多个功能不同的接口,在 option_ids 添加该设备的 PID\VID 后,会导致该设备的所有接口都会绑定到 USB Serial Option 驱动上,导致 USBNet 驱动接口无法正常工作,因此需要在 option_probe 中根据类码、接口索引、端点数量、子类码将 USBNet 的接口排除出来。
- // linux-4.9/drivers/usb/serial/option.c
- static int option_probe(struct usb_serial *serial, const struct usb_device_id *id)
- {
- ......
- #ifdef SUPPORT_QUECTEL
- if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
- __u16 idProduct = le16_to_cpu(serial->dev->descriptor.idProduct);
- struct usb_interface_descriptor *intf = &serial->interface->cur_altsetting->desc;
- if (intf->bInterfaceClass != 0xFF || intf->bInterfaceSubClass == 0x42) {
- //ECM, RNDIS, NCM, MBIM, ACM, UAC, ADB
- return -ENODEV;
- }
- if ((idProduct&0xF000) == 0x0000) {
- //MDM interface 4 is QMI
- if (intf->bInterfaceNumber == 4 && intf->bNumEndpoints == 3 && intf->bInterfaceSubClass == 0xFF && intf->bInterfaceProtocol == 0xFF)
- return -ENODEV;
- }
- }
- #ifdef SUPPORT_QUECTEL_AUTO_SUSPEND
- //For USB Auto Suspend
- if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
- pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
- usb_enable_autosuspend(serial->dev);
- }
- #endif
- #ifdef SUPPORT_QUECTEL_REMOTE_WAKEUP
- //For USB Remote Wakeup
- if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
- device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
- }
- #endif
- #endif
- /* Store the device flags so we can use them during attach. */
- usb_set_serial_data(serial, (void *)device_flags);
- return 0;
- }
3.根据USB协议的要求,在批量输出传输期间,通过设置 URB_ZERO_PACKET 标志来添加处理零数据包的机制。
- diff --git a/longan/kernel/linux-4.9/drivers/usb/serial/usb_wwan.c b/longan/kernel/linux-4.9/drivers/usb/serial/usb_wwan.c
- old mode 100644
- new mode 100755
- index 3dfdfc8..e56b275
- --- a/longan/kernel/linux-4.9/drivers/usb/serial/usb_wwan.c
- +++ b/longan/kernel/linux-4.9/drivers/usb/serial/usb_wwan.c
- @@ -36,6 +36,8 @@
- #include <linux/serial.h>
- #include "usb-wwan.h"
-
- +#define SUPPORT_QUECTEL 1
- +
- /*
- * Generate DTR/RTS signals on the port using the SET_CONTROL_LINE_STATE request
- * in CDC ACM.
- @@ -504,6 +506,14 @@ static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
- usb_fill_bulk_urb(urb, serial->dev,
- usb_sndbulkpipe(serial->dev, endpoint) | dir,
- buf, len, callback, ctx);
- +
- +#ifdef SUPPORT_QUECTEL
- + if (dir == USB_DIR_OUT) {
- + struct usb_device_descriptor *desc = &serial->dev->descriptor;
- + if (desc->idVendor == cpu_to_le16(0x2C7C))
- + urb->transfer_flags |= URB_ZERO_PACKET;
- + }
- +#endif
-
- return urb;
- }
4.增加 USB 控制器复位后恢复操作
- // linux-4.9/drivers/usb/serial/option.c
- static struct usb_serial_driver option_1port_device = {
- ......
- #ifdef SUPPORT_QUECTEL
- .reset_resume = usb_wwan_resume,
- #endif
- };
5.在内核中启用 USB SERIAL 配置
- CONFIG_USB_SERIAL=y
- CONFIG_USB_SERIAL_WWAN=y
- CONFIG_USB_SERIAL_OPTION=y
- CONFIG_USB_ACM=y
- CONFIG_USB_NET_DRIVERS=y
- CONFIG_USB_USBNET=y
- CONFIG_USB_NET_CDCETHER=y
6.配置内核
- (1):执行以下命令切换到内核目录
- cd <用户内核目录>
-
- (2):执行以下命令编译内核。
- make menuconfig
-
- (3):启用配置项。
- 选择<*>表示将驱动程序编译到内核映像。
- 选择<M>表示将驱动程序编译成模块。
以 USB 转串口 option 驱动为例,用户可以通过以下选项启用CONFIG_USB_SERIAL_OPTION,将USB 转串口 option 驱动编译到内核镜像。

主要是修改 ril 库相关的文件,实现拨号和衔接数据通路。
1.ril 库移植
涉及文件列表
- android/hardware/ril/rild/radio.xml
- android/hardware/ril/rild/rild.rc
- android/device/softwinner/common/sepolicy/vendor/rild.te
- android/vendor/aw/public/prebuild/lib/librild/radio_common.mk
- android/vendor/aw/public/prebuild/lib/librild/lib/lib32/libquectel-ril.so
- android/vendor/aw/public/prebuild/lib/librild/lib/lib64/libquectel-ril.so
构建文件拷贝脚本,将移远的 ril 库以及 apns-conf.xml、ql-ril.conf 文件更新到系统指定目录下
- android/vendor/aw/public/prebuild/lib/librild/lib/lib32/libquectel-ril.so
- android/vendor/aw/public/prebuild/lib/librild/lib/lib64/libquectel-ril.so
- mercury-demo:/ # getprop | grep ril
- getprop | grep ril
- [gsm.version.ril-impl]: [Quectel_Android_RIL_Driver_V3.5.0]
- [init.svc.vendor.ril-daemon]: [running]
- [ro.boottime.vendor.ril-daemon]: [9918673186]
- [ro.radio.noril]: [false]
- [vendor.rild.libargs]: [-d/dev/ttyUSB2]
- [vendor.rild.libpath]: [/vendor/lib64/libquectel-ril.so]
以上信息包括ril库的版本,ril守护进程的运行状态,还有ril库文件的路径等等,在移植相应ril库文件后EC200M模块可正常上网。
如有其他问题可进行抓取日志分析,抓取模块Log方法如下:
- adb root
- adb shell mkdir /data/quectel_debug_log
- adb shell chmod 777 /data/quectel_debug_log
- adb reboot