From: Ivan Vecera <ivecera@redhat.com> Date: Tue, 22 Jan 2008 08:51:03 +0100 Subject: [usb] sierra MC8755: increase HSDPA performance Message-id: 4795A067.6020509@redhat.com O-Subject: Re: [RHEL5 PATCH] HSDPA Performance with Sierra MC8755 Bugzilla: 232885 BZ: https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=232885 Description (customer report): We have some ThinkPads T60 and X60 with internal MiniPCI UMTS / HSDPA Card. The Card is a Sierra MC8577 (USB Id 1199:6804). In RHEL5 there is a sierra Kernel Module which supports this card in general but Lenovo has it's own Product ID (6804). In Fedora 7 with Kernel vmlinuz-2.6.20-1.2986.fc7 there is a newer Sierra Module that supports the card fine. Without the sierra Module we can use the card with the usb-serial Kernel Module but only with about 40 KB/s in Downlink. With the Sierra Module on Fedora 7 we get 140 - 150 KB/s! Upstream status: The patch is partly contained in version 2.6.19. http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=033a3fb980b041c5b1c865d3e9dce 9217d1dc94b Only small portion of patch (device id table entries for product id 0x6804 is missing in upstream) Testing: I have built the kernel package with attached patch and sent it to customer to test it. The customer confirmed that the patch solves its problem. Regards, Ivan ===== diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index d29638d..6b8501a 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -1,75 +1,664 @@ /* - * Sierra Wireless CDMA Wireless Serial USB driver - * - * Current Copy modified by: Kevin Lloyd <linux@sierrawireless.com> - * Original Copyright (C) 2005-2006 Greg Kroah-Hartman <gregkh@suse.de> - * - * 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. - */ + USB Driver for Sierra Wireless + + Copyright (C) 2006 Kevin Lloyd <linux@sierrawireless.com> + + IMPORTANT DISCLAIMER: This driver is not commercially supported by + Sierra Wireless. Use at your own risk. + + This driver is free software; you can redistribute it and/or modify + it under the terms of Version 2 of the GNU General Public License as + published by the Free Software Foundation. + + Portions based on the option driver by Matthias Urlichs <smurf@smurf.noris.de> + Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org> + + History: +*/ + +#define DRIVER_VERSION "v.1.0.5" +#define DRIVER_AUTHOR "Kevin Lloyd <linux@sierrawireless.com>" +#define DRIVER_DESC "USB Driver for Sierra Wireless USB modems" #include <linux/kernel.h> -#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/errno.h> #include <linux/tty.h> +#include <linux/tty_flip.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/serial.h> +/* Function prototypes */ +static int sierra_open(struct usb_serial_port *port, struct file *filp); +static void sierra_close(struct usb_serial_port *port, struct file *filp); +static int sierra_startup(struct usb_serial *serial); +static void sierra_shutdown(struct usb_serial *serial); +static void sierra_rx_throttle(struct usb_serial_port *port); +static void sierra_rx_unthrottle(struct usb_serial_port *port); +static int sierra_write_room(struct usb_serial_port *port); + +static void sierra_instat_callback(struct urb *urb, struct pt_regs *regs); + +static int sierra_write(struct usb_serial_port *port, + const unsigned char *buf, int count); + +static int sierra_chars_in_buffer(struct usb_serial_port *port); +static int sierra_ioctl(struct usb_serial_port *port, struct file *file, + unsigned int cmd, unsigned long arg); +static void sierra_set_termios(struct usb_serial_port *port, + struct termios *old); +static void sierra_break_ctl(struct usb_serial_port *port, int break_state); +static int sierra_tiocmget(struct usb_serial_port *port, struct file *file); +static int sierra_tiocmset(struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear); +static int sierra_send_setup(struct usb_serial_port *port); + +static int sierra_set_power_state(struct usb_device *udev, __u16 swiState) +{ + int result; + dev_dbg(&udev->dev, "%s", "SET POWER STATE"); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x00, /* __u8 request */ + 0x40, /* __u8 request type */ + swiState, /* __u16 value */ + 0, /* __u16 index */ + NULL, /* void *data */ + 0, /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + return result; +} + static struct usb_device_id id_table [] = { - { USB_DEVICE(0x1199, 0x0018) }, /* Sierra Wireless MC5720 */ { USB_DEVICE(0x1199, 0x0020) }, /* Sierra Wireless MC5725 */ - { USB_DEVICE(0x1199, 0x0017) }, /* Sierra Wireless EM5625 */ { USB_DEVICE(0x1199, 0x0019) }, /* Sierra Wireless AirCard 595 */ { USB_DEVICE(0x1199, 0x6802) }, /* Sierra Wireless MC8755 */ { USB_DEVICE(0x1199, 0x6803) }, /* Sierra Wireless MC8765 */ + { USB_DEVICE(0x1199, 0x6804) }, /* Sierra Wireless MC8755 */ + { USB_DEVICE(0x1199, 0x6812) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6813) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6820) }, /* Sierra Wireless AirCard 875 */ + { } +}; + +static struct usb_device_id id_table_3port [] = { + { USB_DEVICE(0x1199, 0x0020) }, /* Sierra Wireless MC5725 */ + { USB_DEVICE(0x1199, 0x0019) }, /* Sierra Wireless AirCard 595 */ + { USB_DEVICE(0x1199, 0x6802) }, /* Sierra Wireless MC8755 */ + { USB_DEVICE(0x1199, 0x6803) }, /* Sierra Wireless MC8765 */ + { USB_DEVICE(0x1199, 0x6804) }, /* Sierra Wireless MC8755 */ { USB_DEVICE(0x1199, 0x6812) }, /* Sierra Wireless MC8775 */ + { USB_DEVICE(0x1199, 0x6813) }, /* Sierra Wireless MC8775 */ { USB_DEVICE(0x1199, 0x6820) }, /* Sierra Wireless AirCard 875 */ - /* Following devices are supported in the airprime.c driver */ - /* { USB_DEVICE(0x1199, 0x0112) }, */ /* Sierra Wireless AirCard 580 */ - /* { USB_DEVICE(0x0F3D, 0x0112) }, */ /* AirPrime/Sierra PC 5220 */ { } }; + + MODULE_DEVICE_TABLE(usb, id_table); static struct usb_driver sierra_driver = { - .name = "sierra_wireless", - .probe = usb_serial_probe, - .disconnect = usb_serial_disconnect, - .id_table = id_table, + .name = "sierra", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .no_dynamic_id = 1, }; -static struct usb_serial_driver sierra_device = { +static struct usb_serial_driver sierra_3port_device = { .driver = { - .owner = THIS_MODULE, - .name = "Sierra_Wireless", + .owner = THIS_MODULE, + .name = "sierra3", }, - .id_table = id_table, - .num_interrupt_in = NUM_DONT_CARE, - .num_bulk_in = NUM_DONT_CARE, - .num_bulk_out = NUM_DONT_CARE, - .num_ports = 3, + .description = "Sierra USB modem (3 port)", + .id_table = id_table_3port, + .num_interrupt_in = NUM_DONT_CARE, + .num_bulk_in = 3, + .num_bulk_out = 3, + .num_ports = 3, + .open = sierra_open, + .close = sierra_close, + .write = sierra_write, + .write_room = sierra_write_room, + .chars_in_buffer = sierra_chars_in_buffer, + .throttle = sierra_rx_throttle, + .unthrottle = sierra_rx_unthrottle, + .ioctl = sierra_ioctl, + .set_termios = sierra_set_termios, + .break_ctl = sierra_break_ctl, + .tiocmget = sierra_tiocmget, + .tiocmset = sierra_tiocmset, + .attach = sierra_startup, + .shutdown = sierra_shutdown, + .read_int_callback = sierra_instat_callback, +}; + +#ifdef CONFIG_USB_DEBUG +static int debug; +#else +#define debug 0 +#endif + +/* per port private data */ + +#define N_IN_URB 4 +#define N_OUT_URB 1 +#define IN_BUFLEN 4096 + +struct sierra_port_private { + spinlock_t lock; /* lock the structure */ + int outstanding_urbs; /* number of out urbs in flight */ + + /* Input endpoints and buffer for this port */ + struct urb *in_urbs[N_IN_URB]; + char in_buffer[N_IN_URB][IN_BUFLEN]; + + /* Settings for the port */ + int rts_state; /* Handshaking pins (outputs) */ + int dtr_state; + int cts_state; /* Handshaking pins (inputs) */ + int dsr_state; + int dcd_state; + int ri_state; }; +/* Functions used by new usb-serial code. */ static int __init sierra_init(void) { int retval; - - retval = usb_serial_register(&sierra_device); + retval = usb_serial_register(&sierra_3port_device); if (retval) - return retval; + goto failed_3port_device_register; + retval = usb_register(&sierra_driver); if (retval) - usb_serial_deregister(&sierra_device); + goto failed_driver_register; + + info(DRIVER_DESC ": " DRIVER_VERSION); + + return 0; + +failed_driver_register: + usb_serial_deregister(&sierra_3port_device); +failed_3port_device_register: return retval; } static void __exit sierra_exit(void) { - usb_deregister(&sierra_driver); - usb_serial_deregister(&sierra_device); + usb_deregister (&sierra_driver); + usb_serial_deregister(&sierra_3port_device); } module_init(sierra_init); module_exit(sierra_exit); + +static void sierra_rx_throttle(struct usb_serial_port *port) +{ + dbg("%s", __FUNCTION__); +} + +static void sierra_rx_unthrottle(struct usb_serial_port *port) +{ + dbg("%s", __FUNCTION__); +} + +static void sierra_break_ctl(struct usb_serial_port *port, int break_state) +{ + /* Unfortunately, I don't know how to send a break */ + dbg("%s", __FUNCTION__); +} + +static void sierra_set_termios(struct usb_serial_port *port, + struct termios *old_termios) +{ + dbg("%s", __FUNCTION__); + + sierra_send_setup(port); +} + +static int sierra_tiocmget(struct usb_serial_port *port, struct file *file) +{ + unsigned int value; + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + value = ((portdata->rts_state) ? TIOCM_RTS : 0) | + ((portdata->dtr_state) ? TIOCM_DTR : 0) | + ((portdata->cts_state) ? TIOCM_CTS : 0) | + ((portdata->dsr_state) ? TIOCM_DSR : 0) | + ((portdata->dcd_state) ? TIOCM_CAR : 0) | + ((portdata->ri_state) ? TIOCM_RNG : 0); + + return value; +} + +static int sierra_tiocmset(struct usb_serial_port *port, struct file *file, + unsigned int set, unsigned int clear) +{ + struct sierra_port_private *portdata; + + portdata = usb_get_serial_port_data(port); + + if (set & TIOCM_RTS) + portdata->rts_state = 1; + if (set & TIOCM_DTR) + portdata->dtr_state = 1; + + if (clear & TIOCM_RTS) + portdata->rts_state = 0; + if (clear & TIOCM_DTR) + portdata->dtr_state = 0; + return sierra_send_setup(port); +} + +static int sierra_ioctl(struct usb_serial_port *port, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void sierra_outdat_callback(struct urb *urb, struct pt_regs *regs) +{ + struct usb_serial_port *port = urb->context; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + int status = urb->status; + unsigned long flags; + + dbg("%s - port %d", __FUNCTION__, port->number); + + /* free up the transfer buffer, as usb_free_urb() does not do this */ + kfree(urb->transfer_buffer); + + if (status) + dbg("%s - nonzero write bulk status received: %d", + __FUNCTION__, status); + + spin_lock_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + spin_unlock_irqrestore(&portdata->lock, flags); + + usb_serial_port_softint(port); +} + +/* Write */ +static int sierra_write(struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + unsigned long flags; + unsigned char *buffer; + struct urb *urb; + int status; + + portdata = usb_get_serial_port_data(port); + + dbg("%s: write (%d chars)", __FUNCTION__, count); + + spin_lock_irqsave(&portdata->lock, flags); + if (portdata->outstanding_urbs > N_OUT_URB) { + spin_unlock_irqrestore(&portdata->lock, flags); + dbg("%s - write limit hit\n", __FUNCTION__); + return 0; + } + portdata->outstanding_urbs++; + spin_unlock_irqrestore(&portdata->lock, flags); + + buffer = kmalloc(count, GFP_ATOMIC); + if (!buffer) { + dev_err(&port->dev, "out of memory\n"); + count = -ENOMEM; + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + dev_err(&port->dev, "no more free urbs\n"); + count = -ENOMEM; + goto error_no_urb; + } + + memcpy(buffer, buf, count); + + usb_serial_debug_data(debug, &port->dev, __FUNCTION__, count, buffer); + + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + buffer, count, sierra_outdat_callback, port); + + /* send it down the pipe */ + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, "%s - usb_submit_urb(write bulk) failed " + "with status = %d\n", __FUNCTION__, status); + count = status; + goto error; + } + + /* we are done with this urb, so let the host driver + * really free it when it is finished with it */ + usb_free_urb(urb); + + return count; +error: + usb_free_urb(urb); +error_no_urb: + kfree(buffer); +error_no_buffer: + spin_lock_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + spin_unlock_irqrestore(&portdata->lock, flags); + return count; +} + +static void sierra_indat_callback(struct urb *urb, struct pt_regs *regs) +{ + int err; + int endpoint; + struct usb_serial_port *port; + struct tty_struct *tty; + unsigned char *data = urb->transfer_buffer; + + dbg("%s: %p", __FUNCTION__, urb); + + endpoint = usb_pipeendpoint(urb->pipe); + port = (struct usb_serial_port *) urb->context; + + if (urb->status) { + dbg("%s: nonzero status: %d on endpoint %02x.", + __FUNCTION__, urb->status, endpoint); + } else { + tty = port->tty; + if (tty && urb->actual_length) { + tty_buffer_request_room(tty, urb->actual_length); + tty_insert_flip_string(tty, data, urb->actual_length); + tty_flip_buffer_push(tty); + } else { + dbg("%s: empty read urb received", __FUNCTION__); + } + + /* Resubmit urb so we continue receiving */ + if (port->open_count && urb->status != -ESHUTDOWN) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_err(&port->dev, "resubmit read urb failed." + "(%d)", err); + } + } + return; +} + +static void sierra_instat_callback(struct urb *urb, struct pt_regs *regs) +{ + int err; + struct usb_serial_port *port = (struct usb_serial_port *) urb->context; + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + + dbg("%s", __FUNCTION__); + dbg("%s: urb %p port %p has data %p", __FUNCTION__,urb,port,portdata); + + if (urb->status == 0) { + struct usb_ctrlrequest *req_pkt = + (struct usb_ctrlrequest *)urb->transfer_buffer; + + if (!req_pkt) { + dbg("%s: NULL req_pkt\n", __FUNCTION__); + return; + } + if ((req_pkt->bRequestType == 0xA1) && + (req_pkt->bRequest == 0x20)) { + int old_dcd_state; + unsigned char signals = *((unsigned char *) + urb->transfer_buffer + + sizeof(struct usb_ctrlrequest)); + + dbg("%s: signal x%x", __FUNCTION__, signals); + + old_dcd_state = portdata->dcd_state; + portdata->cts_state = 1; + portdata->dcd_state = ((signals & 0x01) ? 1 : 0); + portdata->dsr_state = ((signals & 0x02) ? 1 : 0); + portdata->ri_state = ((signals & 0x08) ? 1 : 0); + + if (port->tty && !C_CLOCAL(port->tty) && + old_dcd_state && !portdata->dcd_state) + tty_hangup(port->tty); + } else { + dbg("%s: type %x req %x", __FUNCTION__, + req_pkt->bRequestType,req_pkt->bRequest); + } + } else + dbg("%s: error %d", __FUNCTION__, urb->status); + + /* Resubmit urb so we continue receiving IRQ data */ + if (urb->status != -ESHUTDOWN) { + urb->dev = serial->dev; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dbg("%s: resubmit intr urb failed. (%d)", + __FUNCTION__, err); + } +} + +static int sierra_write_room(struct usb_serial_port *port) +{ + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + unsigned long flags; + + dbg("%s - port %d", __FUNCTION__, port->number); + + /* try to give a good number back based on if we have any free urbs at + * this point in time */ + spin_lock_irqsave(&portdata->lock, flags); + if (portdata->outstanding_urbs > N_OUT_URB * 2 / 3) { + spin_unlock_irqrestore(&portdata->lock, flags); + dbg("%s - write limit hit\n", __FUNCTION__); + return 0; + } + spin_unlock_irqrestore(&portdata->lock, flags); + + return 2048; +} + +static int sierra_chars_in_buffer(struct usb_serial_port *port) +{ + dbg("%s - port %d", __FUNCTION__, port->number); + + /* + * We can't really account for how much data we + * have sent out, but hasn't made it through to the + * device as we can't see the backend here, so just + * tell the tty layer that everything is flushed. + */ + return 0; +} + +static int sierra_open(struct usb_serial_port *port, struct file *filp) +{ + struct sierra_port_private *portdata; + struct usb_serial *serial = port->serial; + int i; + struct urb *urb; + int result; + + portdata = usb_get_serial_port_data(port); + + dbg("%s", __FUNCTION__); + + /* Set some sane defaults */ + portdata->rts_state = 1; + portdata->dtr_state = 1; + + /* Reset low level data toggle and start reading from endpoints */ + for (i = 0; i < N_IN_URB; i++) { + urb = portdata->in_urbs[i]; + if (!urb) + continue; + if (urb->dev != serial->dev) { + dbg("%s: dev %p != %p", __FUNCTION__, + urb->dev, serial->dev); + continue; + } + + /* + * make sure endpoint data toggle is synchronized with the + * device + */ + usb_clear_halt(urb->dev, urb->pipe); + + result = usb_submit_urb(urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "submit urb %d failed (%d) %d", + i, result, urb->transfer_buffer_length); + } + } + + port->tty->low_latency = 1; + + sierra_send_setup(port); + + /* start up the interrupt endpoint if we have one */ + if (port->interrupt_in_urb) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, "submit irq_in urb failed %d", + result); + } + return 0; +} + +static void sierra_close(struct usb_serial_port *port, struct file *filp) +{ + int i; + struct usb_serial *serial = port->serial; + struct sierra_port_private *portdata; + + dbg("%s", __FUNCTION__); + portdata = usb_get_serial_port_data(port); + + portdata->rts_state = 0; + portdata->dtr_state = 0; + + if (serial->dev) { + sierra_send_setup(port); + + /* Stop reading/writing urbs */ + for (i = 0; i < N_IN_URB; i++) + usb_kill_urb(portdata->in_urbs[i]); + } + + usb_kill_urb(port->interrupt_in_urb); + + port->tty = NULL; +} + +static int sierra_send_setup(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct sierra_port_private *portdata; + + dbg("%s", __FUNCTION__); + + portdata = usb_get_serial_port_data(port); + + if (port->tty) { + int val = 0; + if (portdata->dtr_state) + val |= 0x01; + if (portdata->rts_state) + val |= 0x02; + + return usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + 0x22,0x21,val,0,NULL,0,USB_CTRL_SET_TIMEOUT); + } + + return 0; +} + +static int sierra_startup(struct usb_serial *serial) +{ + struct usb_serial_port *port; + struct sierra_port_private *portdata; + struct urb *urb; + int i; + int j; + + dbg("%s", __FUNCTION__); + + /*Set Device mode to D0 */ + sierra_set_power_state(serial->dev, 0x0000); + + /* Now setup per port private data */ + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); + if (!portdata) { + dbg("%s: kmalloc for sierra_port_private (%d) failed!.", + __FUNCTION__, i); + return -ENOMEM; + } + spin_lock_init(&portdata->lock); + + usb_set_serial_port_data(port, portdata); + + /* initialize the in urbs */ + for (j = 0; j < N_IN_URB; ++j) { + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) { + dbg("%s: alloc for in port failed.", + __FUNCTION__); + continue; + } + /* Fill URB using supplied data. */ + usb_fill_bulk_urb(urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port->bulk_in_endpointAddress), + portdata->in_buffer[j], IN_BUFLEN, + sierra_indat_callback, port); + portdata->in_urbs[j] = urb; + } + } + + return 0; +} + +static void sierra_shutdown(struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct sierra_port_private *portdata; + + dbg("%s", __FUNCTION__); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!port) + continue; + portdata = usb_get_serial_port_data(port); + if (!portdata) + continue; + + for (j = 0; j < N_IN_URB; j++) { + usb_kill_urb(portdata->in_urbs[j]); + usb_free_urb(portdata->in_urbs[j]); + portdata->in_urbs[j] = NULL; + } + kfree(portdata); + usb_set_serial_port_data(port, NULL); + } +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); + +#ifdef CONFIG_USB_DEBUG +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages"); +#endif +