2009-04-09

3G Support: Qualcomm Gobi chipset - Step1: qcserial kernel module

The qcserial kernel module serves 2 functions. Its first function is to load the correct firmware into the Gobi chipset. The second purpose is to provide the serial interface to the modem device which becomes available after the firmware is successfully loaded and initialized.

The qcserial module is quite new. So new that it is part of the 2.6.30 Linux kernel, but not available as part of any previous releases. Since I wanted to use Ubuntu's regular stock kernel it was necessary to get just one file: qcserial.c

The best way is to go to http://kernel.org/ and grab the most recent kernel source code and find qcserial.c among the usb/serial kernel modules. As an alternative I pasted the sourcecode plus the makefile at the end of this post.

To install the module, copy both files, qcserial.c and Makefile to an empty directory. Build the module with make and install it with make install. The module is loaded by modprobe qcserial. After a reboot the module is loaded automatically.

If the module is loaded successfully, a serial device /dev/ttyUSB0 appears. This will be used by the firmware loader.



### BEGIN qcserial.c ##################################
/*
* Qualcomm Serial USB driver
*
* Copyright (c) 2008 QUALCOMM Incorporated.
* Copyright (c) 2009 Greg Kroah-Hartman
* Copyright (c) 2009 Novell Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
*/

#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>

#define DRIVER_VERSION "v0.4"
#define DRIVER_AUTHOR "Qualcomm Inc"
#define DRIVER_DESC "Qualcomm USB Serial driver"

#define NUM_BULK_EPS 1
#define MAX_BULK_EPS 6

static int debug;

static struct usb_device_id id_table[] = {
{USB_DEVICE(0x05c6, 0x9211)}, /* Acer Gobi QDL device */
{USB_DEVICE(0x05c6, 0x9212)}, /* Acer Gobi Modem Device */
{USB_DEVICE(0x03f0, 0x1f1d)}, /* HP un2400 Gobi Modem Device */
{USB_DEVICE(0x03f0, 0x201d)}, /* HP un2400 Gobi QDL Device */
{USB_DEVICE(0x05c6, 0x9221)}, /* Sony Vaio Z31 Gobi QDL Device */
{USB_DEVICE(0x05c6, 0x9222)}, /* Sony Vaio Z31 Gobi Modem Device */
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, id_table);

static struct usb_driver qcdriver = {
.name = "qcserial",
.probe = usb_serial_probe,
.disconnect = usb_serial_disconnect,
.id_table = id_table,
.suspend = usb_serial_suspend,
.resume = usb_serial_resume,
.supports_autosuspend = true,
};

static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
{
int retval = -ENODEV;
__u8 nintf;
__u8 ifnum;

dbg("%s", __func__);

nintf = serial->dev->actconfig->desc.bNumInterfaces;
dbg("Num Interfaces = %d", nintf);
ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
dbg("This Interface = %d", ifnum);

switch (nintf) {
case 1:
/* QDL mode */
if (serial->interface->num_altsetting == 2) {
struct usb_host_interface *intf;

intf = &serial->interface->altsetting[1];
if (intf->desc.bNumEndpoints == 2) {
if (usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
dbg("QDL port found");
retval = usb_set_interface(serial->dev, ifnum, 1);
if (retval < 0) {
dev_err(&serial->dev->dev,
"Could not set interface, error %d\n",
retval);
retval = -ENODEV;
}
return retval;
}
}
}
break;

case 4:
/* Composite mode */
if (ifnum == 2) {
dbg("Modem port found");
retval = usb_set_interface(serial->dev, ifnum, 0);
if (retval < 0) {
dev_err(&serial->dev->dev,
"Could not set interface, error %d\n",
retval);
retval = -ENODEV;
}
return retval;
}
break;

default:
dev_err(&serial->dev->dev,
"unknown number of interfaces: %d\n", nintf);
return -ENODEV;
}

return retval;
}

static struct usb_serial_driver qcdevice = {
.driver = {
.owner = THIS_MODULE,
.name = "qcserial",
},
.description = "Qualcomm USB modem",
.id_table = id_table,
.usb_driver = &qcdriver,
.num_ports = 1,
.probe = qcprobe,
};

static int __init qcinit(void)
{
int retval;

retval = usb_serial_register(&qcdevice);
if (retval)
return retval;

retval = usb_register(&qcdriver);
if (retval) {
usb_serial_deregister(&qcdevice);
return retval;
}

printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION "\n");

return 0;
}

static void __exit qcexit(void)
{
usb_deregister(&qcdriver);
usb_serial_deregister(&qcdevice);
}

module_init(qcinit);
module_exit(qcexit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRIVER_VERSION);

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");
### EOF qcserial.c #########################################




### BEGIN Makefile #######################################
obj-m += qcserial.o

KDIR := /lib/modules/$(shell uname -r)
PWD := $(shell pwd)

all: default

default:
$(MAKE) -C $(KDIR)/build SUBDIRS=$(PWD) modules

clean:
$(MAKE) -C $(KDIR)/build SUBDIRS=$(PWD) clean
@rm -f modules.order
@rm -f Module.markers
@rm -f *~

install:
cp qcserial.ko $(KDIR)/kernel/drivers/usb/serial/
depmod -a

uninstall:
rm $(KDIR)/kernel/drivers/usb/serial/qcserial.ko
rmmod qcserial
### EOF Makefile ##########################################

13 comments:

  1. Hello, this lines are incorrect
    #include
    #include
    #include
    #include

    ReplyDelete
  2. diegok, thanks for the pointer. Sorry, I missed that. The blogspot software swallowed part the #includes, apparently because the are in '<' and '>' and look like HTML tags.

    What's missing is this:

    #include '<'linux/tty.h'>'
    #include '<'linux/tty_flip.h'>'
    #include '<'linux/usb.h'>'
    #include '<'linux/usb/serial.h'>'

    Obviously, the single quotes have to be removed.

    Is there a way to post properly formatted source code?

    ReplyDelete
  3. Dear Michael,

    your information was very useful. However, on my Sony Vaio VGN-Z41WD I'm facing another problem. If I boot into Linux from a cold start, the Qualcomm device is not listed by lsusb. It only appears after booting in Windows and activating the 3G utility. I could verify that if it is not listed among the USB devices the qcserial module is not able to create /dev/ttyUSB0, hence it is unusable. echo 1 > /sys/devices/platform/sony-laptop/wwanpower does not activate the device either. I googled extensively for a solution but I couldn't find any. Do you have one? Thanks, bye

    Paolo

    ReplyDelete
  4. Was getting some errors about modules not being a valid target, turned out I forgot to install the kernel-sources first. :)

    ReplyDelete
  5. Hello.
    Thanks for the code!
    But I can't seem to get it work. (Newbie)
    When I run the "make" command it gives me the following:

    Makefile 14: Commands commence before first target. Stop

    Whats wrong?
    Thanks!

    ReplyDelete
  6. @Nariman

    Dunno. I tested this on Ubuntu 9.04. Are you attempting this on a different distro? Make sure all the development tools are installed.

    If you use, for example, Ubuntu 9.10 with a newer kernel then the qcserial module should already be included.

    ReplyDelete
  7. Michael
    Yes, I do use 9.10.
    I did notice that the card did work if I booted in to windows. Then the card would be listed in the NM-applet dropdown.
    The question is how to make it in active state when I boot directly to ubuntu?
    Any tips/links?
    Thanks!
    Nariman

    ReplyDelete
  8. Has there been any progress on this? I have the Sony Vaio P-Series with the Qualcomm® Wireless HS-USB Modem 9222.

    I need assistance finding the firmware. Where do I look on my Windows partition to find the firmware file? Or how can I extract the firmware from Sony's driver installer?

    Any help would be greatly appreciated!

    Helamonster

    ReplyDelete
  9. @Helamonster
    On my System the Qualcomm Driver installed the firmware files in c:\QUALCOMM\QDLService\Packages\0
    thru
    c:\QUALCOMM\QDLService\Packages\9
    Since the firmware files are country-specific you need to find out which of the files in the 10 subdirectories work for you.

    As to how the extract the firmware from the driver installer: in case you dont't have a bootable Windows partition, you can try to run the installer via WINE. Even if the installation itself will most probably fail at some point, you may reach an intermediate state where the directory structure with the firmware files is created somewhere in the process.

    ReplyDelete
  10. Enjoyed your post, it's very interesting. Nice to read something different as well!

    Visit:
    answers to job interview questions
    Recognized by many

    ReplyDelete