Porting Quectel EC20 USB Modem Driver to Linux Kernel
USB Serial Driver Modifications
Adding Vendor and Product IDs
To enable kernel recognition of the module, add its Vendor ID (VID) and Product ID (PID) definitions. Modify the drivers/usb/serial/option.c file as shown.
#define VIATELECOM_VENDOR_ID 0x15eb
#define VIATELECOM_PRODUCT_CDS7 0x0001
#define QUECTEL_VENDOR_EC20 0x2c7c
#define QUECTEL_PRODUCT_EC20 0x0125
static const struct usb_device_id option_ids[] = {
{ USB_DEVICE(QUECTEL_VENDOR_EC20, QUECTEL_PRODUCT_EC20) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) },
// ... additional entries
};
Implementing Zero-Packet Handling
USB bulk-out tranfsers require zero-packet termination under specific conditions. Update the drivers/usb/serial/usb_wwan.c file's usb_wwan_setup_urb function.
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);
if (!urb)
return NULL;
usb_fill_bulk_urb(urb, serial->dev,
usb_sndbulkpipe(serial->dev, endpoint) | dir,
buf, len, callback, ctx);
#if 1 // Zero-packet handling for Quectel modules
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;
}
Filtering Interface Numbers
Prevent USB serial driver registration for interfaces with bInterfaceNumber >= 4 by modifying the probe function in drivers/usb/serial/option.c.
static int option_probe(struct usb_serial *serial,
const struct usb_device_id *id)
{
struct usb_interface_descriptor *iface_desc =
&serial->interface->cur_altsetting->desc;
struct usb_device_descriptor *dev_desc = &serial->dev->descriptor;
const struct option_blacklist_info *blacklist;
if (iface_desc->bInterfaceClass == 0x08)
return -ENODEV;
blacklist = (void *)id->driver_info;
if (blacklist && test_bit(iface_desc->bInterfaceNumber,
&blacklist->reserved))
return -ENODEV;
if (dev_desc->idVendor == cpu_to_le16(SAMSUNG_VENDOR_ID) &&
dev_desc->idProduct == cpu_to_le16(SAMSUNG_PRODUCT_GT_B3730) &&
iface_desc->bInterfaceClass != USB_CLASS_CDC_DATA)
return -ENODEV;
#if 1 // Exclude EC20 NDIS interfaces
if (serial->dev->descriptor.idVendor == cpu_to_le16(QUECTEL_VENDOR_EC20) &&
serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4) {
printk(KERN_INFO "EC20 NDIS interface filtered\n");
return -ENODEV;
}
#endif
usb_set_serial_data(serial, (void *)blacklist);
return 0;
}
Removing EC20 Support from qcserial and qmi_wwan
Comment out EC20 entries in drivers/net/usb/qmi_wwan.c and drivers/usb/serial/qcserial.c to avoid driver conflicts.
// In qmi_wwan.c
static const struct usb_device_id products[] = {
{QMI_GOBI_DEVICE(0x05c6, 0x9225)},
{QMI_GOBI_DEVICE(0x05c6, 0x9245)},
{QMI_GOBI_DEVICE(0x03f0, 0x251d)},
// {QMI_GOBI_DEVICE(0x05c6, 0x9215)}, // Disabled for EC20
{QMI_GOBI_DEVICE(0x05c6, 0x9265)},
{QMI_GOBI_DEVICE(0x05c6, 0x9235)},
};
// In qcserial.c
const struct usb_device_id id_table[] = {
{USB_DEVICE(0x03f0, 0x241d)},
{USB_DEVICE(0x03f0, 0x251d)},
{USB_DEVICE(0x05c6, 0x9214)},
// {USB_DEVICE(0x05c6, 0x9215)}, // Disabled for EC20
{USB_DEVICE(0x05c6, 0x9264)},
{USB_DEVICE(0x05c6, 0x9265)},
{USB_DEVICE(0x05c6, 0x9234)},
};
Kernel Configuration and Testing
Enable USB serial support and relevant options via make menuconfig. After compiling and flashing the kernel, verify module detection and serial port enumeration.
$ lsusb
Bus 001 Device 001: ID 1d6b:0002
Bus 002 Device 001: ID 1d6b:0002
Bus 001 Device 002: ID 2c7c:0125
$ ls /dev/ttyUSB*
/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3
$ microcom -s 9600 /dev/ttyUSB2
ate
OK
at+csq
+CSQ: 15,99
PPP Dial-up Configuraton
Copy quectel-chat-connect, quectel-chat-disconnect, and quectel-ppp scripts to /etc/ppp/peers/. Place pppd and chat binaries in /usr/bin/. Update the APN in quectel-chat-connect and set the serial device, username, and password in quectel-ppp.
# /etc/ppp/peers/quectel-chat-connect
ABORT "BUSY"
ABORT "NO CARRIER"
ABORT "NO DIALTONE"
ABORT "ERROR"
ABORT "NO ANSWER"
TIMEOUT 30
"" AT
OK ATE0
OK ATI;+CSUB;+CSQ;+CPIN?;+COPS?;+CGREG?;&D2
OK AT+CGDCONT=1,"IP","cmnet",,0,0
OK ATD*99#
CONNECT
# /etc/ppp/peers/quectel-ppp
/dev/ttyUSB3 115200
user "test" password "test"
Initiate a PPP connection and verify network connectivity.
$ pppd call quectel-ppp &
...
local IP address 10.37.158.154
remote IP address 10.64.64.64
primary DNS address 211.136.17.107
secondary DNS address 211.136.20.203
$ ifconfig ppp0
ppp0 Link encap:Point-to-Point Protocol
inet addr:10.37.158.154 P-t-P:10.64.64.64 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
$ echo "nameserver 8.8.8.8" > /etc/resolv.conf
$ ping www.baidu.com
PING baidu.com (123.125.114.144): 56 data bytes
64 bytes from 123.125.114.144: seq=0 ttl=53 time=102.081 ms