sierra: driver urb handling improvements
Elina Pasheva [Thu, 11 Jun 2009 13:30:21 +0000 (14:30 +0100)]
[Folded from eight patches into one as the original set according to the
 author "All of the patches need to be applied to obtain a working product"
 so keeping them split seems unhelpful

 Merge fixes done versus other conflicting changes and moved the
 spin_lock_init from open to setup time -- Alan]

Summary of the changes and code re-organization in this patch:

- The memory for urbs is allocated and urbs are submitted only for the active
  interfaces (instead of pre-allocating these for all interfaces). This will
  save memory especially in the case of using composite devices.
- The code has been re-organized and functionality has been extracted from
  sierra_startup(), sierra_shutdown(), sierra_open(), sierra_close() and added
  in helper functions sierra_release_urb(), sierra_stop_rx_urbs(),
  sierra_submit_rx_urbs() and sierra_setup_urb()

- Added function sierra_release_urb() to free an urb and its transfer
buffer.
- Removed unecessary include file reference and comment.
- Added function sierra_stop_rx_urbs() that takes care of the release of
receive and interrupt urbs. This function is to be called by sierra_close()
whenever an interface is de-activated.
- Added new function sierra_submit_rx_urbs() that handles the submission of
receive urbs and interrupt urbs (if any) during the interface activation.
This function is to be called by sierra_open(). Added a second parameter to
pass the memory allocation (as suggested by Oliver Neukum) so that this
function can be used in post_reset() and resume().
- Added new function sierra_setup_urb() that contains the functionality to
allocate an urb, fill bulk urb using the supplied memory allocation flag
and release urb upon error. Added parameter so that the caller pass the
memory allocation flag for flexibility.
- Moved sierra_close() before sierra_open() to resolve dependencies
introduced by the code reorganization.
- Modified sierra_close() to call sierra_stop_rx_urbs() and
sierra_release_urb() functions added in previous patch.
- Modified sierra_open() to call sierra_setup_urb() and sierra_submit_rx_urbs()
functions; note urbs are allocated and submitted for each activated interface.
- Modified sierra_startup() so that allocation of urbs happens whenever an
interface is activated (urb allocation is moved to sierra_open()).
- Modified sierra_shutdown() so that urbs are freed whenever an interface is
de-activated (urb freeing moved to sierra_close() as shown in previous patch
from the series)
- Removed unecessary data structure from sierra_port_private_data
- Suppress an entry in logs by not re-submitting an urb when usb_submit_urb()
returns -EPERM, as this shows that usb_kill_urb() is running (as suggested by
Oliver Neukum)

Signed-off-by: Elina Pasheva <epasheva@sierrawireless.com>
Signed-off-by: Alan Cox <alan.cox@linux.intel.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

drivers/usb/serial/sierra.c

index 222a623..88ec7bf 100644 (file)
 #include <linux/module.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
-#include <linux/usb/ch9.h>
 
 #define SWIMS_USB_REQUEST_SetPower     0x00
 #define SWIMS_USB_REQUEST_SetNmea      0x07
 
-/* per port private data */
 #define N_IN_URB       4
 #define N_OUT_URB      4
 #define IN_BUFLEN      4096
@@ -229,7 +227,6 @@ struct sierra_port_private {
 
        /* Input endpoints and buffers for this port */
        struct urb *in_urbs[N_IN_URB];
-       char *in_buffer[N_IN_URB];
 
        /* Settings for the port */
        int rts_state;  /* Handshaking pins (outputs) */
@@ -334,6 +331,17 @@ static int sierra_tiocmset(struct tty_struct *tty, struct file *file,
        return sierra_send_setup(port);
 }
 
+static void sierra_release_urb(struct urb *urb)
+{
+       struct usb_serial_port *port;
+       if (urb) {
+               port =  urb->context;
+               dev_dbg(&port->dev, "%s: %p\n", __func__, urb);
+               kfree(urb->transfer_buffer);
+               usb_free_urb(urb);
+       }
+}
+
 static void sierra_outdat_callback(struct urb *urb)
 {
        struct usb_serial_port *port = urb->context;
@@ -458,7 +466,7 @@ static void sierra_indat_callback(struct urb *urb)
                                " received", __func__);
 
                /* Resubmit urb so we continue receiving */
-               if (port->port.count && status != -ESHUTDOWN) {
+               if (port->port.count && status != -ESHUTDOWN && status != -EPERM) {
                        err = usb_submit_urb(urb, GFP_ATOMIC);
                        if (err)
                                dev_err(&port->dev, "resubmit read urb failed."
@@ -550,100 +558,184 @@ static int sierra_write_room(struct tty_struct *tty)
        return 2048;
 }
 
-static int sierra_open(struct tty_struct *tty,
-                       struct usb_serial_port *port, struct file *filp)
+static void sierra_stop_rx_urbs(struct usb_serial_port *port)
 {
-       struct sierra_port_private *portdata;
-       struct usb_serial *serial = port->serial;
        int i;
-       struct urb *urb;
-       int result;
+       struct sierra_port_private *portdata = usb_get_serial_port_data(port);
 
-       portdata = usb_get_serial_port_data(port);
+       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++)
+               usb_kill_urb(portdata->in_urbs[i]);
 
-       dev_dbg(&port->dev, "%s", __func__);
+       usb_kill_urb(port->interrupt_in_urb);
+}
 
-       /* Set some sane defaults */
-       portdata->rts_state = 1;
-       portdata->dtr_state = 1;
+static int sierra_submit_rx_urbs(struct usb_serial_port *port, gfp_t mem_flags)
+{
+       int ok_cnt;
+       int err = -EINVAL;
+       int i;
+       struct urb *urb;
+       struct sierra_port_private *portdata = usb_get_serial_port_data(port);
 
-       /* Reset low level data toggle and start reading from endpoints */
-       for (i = 0; i < N_IN_URB; i++) {
+       ok_cnt = 0;
+       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++) {
                urb = portdata->in_urbs[i];
                if (!urb)
                        continue;
-               if (urb->dev != serial->dev) {
-                       dev_dbg(&port->dev, "%s: dev %p != %p",
-                                __func__, urb->dev, serial->dev);
-                       continue;
+               err = usb_submit_urb(urb, mem_flags);
+               if (err) {
+                       dev_err(&port->dev, "%s: submit urb failed: %d\n",
+                               __func__, err);
+               } else {
+                       ok_cnt++;
                }
+       }
 
-               /*
-                * 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\n",
-                               i, result, urb->transfer_buffer_length);
+       if (ok_cnt && port->interrupt_in_urb) {
+               err = usb_submit_urb(port->interrupt_in_urb, mem_flags);
+               if (err) {
+                       dev_err(&port->dev, "%s: submit intr urb failed: %d\n",
+                               __func__, err);
                }
        }
 
-       sierra_send_setup(port);
+       if (ok_cnt > 0) /* at least one rx urb submitted */
+               return 0;
+       else
+               return err;
+}
+
+static struct urb *sierra_setup_urb(struct usb_serial *serial, int endpoint,
+                                       int dir, void *ctx, int len,
+                                       gfp_t mem_flags,
+                                       usb_complete_t callback)
+{
+       struct urb      *urb;
+       u8              *buf;
+
+       if (endpoint == -1)
+               return NULL;
 
-       /* 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\n",
-                               result);
+       urb = usb_alloc_urb(0, mem_flags);
+       if (urb == NULL) {
+               dev_dbg(&serial->dev->dev, "%s: alloc for endpoint %d failed\n",
+                       __func__, endpoint);
+               return NULL;
        }
-       return 0;
+
+       buf = kmalloc(len, mem_flags);
+       if (buf) {
+               /* Fill URB using supplied data */
+               usb_fill_bulk_urb(urb, serial->dev,
+                       usb_sndbulkpipe(serial->dev, endpoint) | dir,
+                       buf, len, callback, ctx);
+
+               /* debug */
+               dev_dbg(&serial->dev->dev, "%s %c u : %p d:%p\n", __func__,
+                               dir == USB_DIR_IN ? 'i' : 'o', urb, buf);
+       } else {
+               dev_dbg(&serial->dev->dev, "%s %c u:%p d:%p\n", __func__,
+                               dir == USB_DIR_IN ? 'i' : 'o', urb, buf);
+
+               sierra_release_urb(urb);
+               urb = NULL;
+       }
+
+       return urb;
 }
 
-static void sierra_dtr_rts(struct usb_serial_port *port, int on)
+static void sierra_close(struct usb_serial_port *port)
 {
+       int i;
        struct usb_serial *serial = port->serial;
        struct sierra_port_private *portdata;
 
+       dev_dbg(&port->dev, "%s\n", __func__);
        portdata = usb_get_serial_port_data(port);
-       portdata->rts_state = on;
-       portdata->dtr_state = on;
+
+       portdata->rts_state = 0;
+       portdata->dtr_state = 0;
 
        if (serial->dev) {
                mutex_lock(&serial->disc_mutex);
                if (!serial->disconnected)
                        sierra_send_setup(port);
                mutex_unlock(&serial->disc_mutex);
+
+               /* Stop reading urbs */
+               sierra_stop_rx_urbs(port);
+               /* .. and release them */
+               for (i = 0; i < N_IN_URB; i++) {
+                       sierra_release_urb(portdata->in_urbs[i]);
+                       portdata->in_urbs[i] = NULL;
+               }
        }
 }
 
-static void sierra_close(struct usb_serial_port *port)
+static int sierra_open(struct tty_struct *tty,
+                       struct usb_serial_port *port, struct file *filp)
 {
+       struct sierra_port_private *portdata;
+       struct usb_serial *serial = port->serial;
        int i;
+       int err;
+       int endpoint;
+       struct urb *urb;
+
+       portdata = usb_get_serial_port_data(port);
+
+       dev_dbg(&port->dev, "%s", __func__);
+
+       /* Set some sane defaults */
+       portdata->rts_state = 1;
+       portdata->dtr_state = 1;
+
+
+       endpoint = port->bulk_in_endpointAddress;
+       for (i = 0; i < ARRAY_SIZE(portdata->in_urbs); i++) {
+               urb = sierra_setup_urb(serial, endpoint, USB_DIR_IN, port,
+                                       IN_BUFLEN, GFP_KERNEL,
+                                       sierra_indat_callback);
+               portdata->in_urbs[i] = urb;
+       }
+       /* clear halt condition */
+       usb_clear_halt(serial->dev,
+                       usb_sndbulkpipe(serial->dev, endpoint) | USB_DIR_IN);
+
+       err = sierra_submit_rx_urbs(port, GFP_KERNEL);
+       if (err) {
+               /* get rid of everything as in close */
+               sierra_close(port);
+               return err;
+       }
+       sierra_send_setup(port);
+
+       return 0;
+}
+
+
+static void sierra_dtr_rts(struct usb_serial_port *port, int on)
+{
        struct usb_serial *serial = port->serial;
        struct sierra_port_private *portdata;
 
-       dev_dbg(&port->dev, "%s", __func__);
        portdata = usb_get_serial_port_data(port);
+       portdata->rts_state = on;
+       portdata->dtr_state = on;
 
        if (serial->dev) {
-               /* Stop reading/writing urbs */
-               for (i = 0; i < N_IN_URB; i++)
-                       usb_kill_urb(portdata->in_urbs[i]);
+               mutex_lock(&serial->disc_mutex);
+               if (!serial->disconnected)
+                       sierra_send_setup(port);
+               mutex_unlock(&serial->disc_mutex);
        }
-       usb_kill_urb(port->interrupt_in_urb);
 }
 
 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;
 
        dev_dbg(&serial->dev->dev, "%s", __func__);
 
@@ -665,34 +757,8 @@ static int sierra_startup(struct usb_serial *serial)
                        return -ENOMEM;
                }
                spin_lock_init(&portdata->lock);
-               for (j = 0; j < N_IN_URB; j++) {
-                       portdata->in_buffer[j] = kmalloc(IN_BUFLEN, GFP_KERNEL);
-                       if (!portdata->in_buffer[j]) {
-                               for (--j; j >= 0; j--)
-                                       kfree(portdata->in_buffer[j]);
-                               kfree(portdata);
-                               return -ENOMEM;
-                       }
-               }
-
+               /* Set the port private data pointer */
                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) {
-                               dev_dbg(&port->dev, "%s: alloc for in "
-                                       "port failed.", __func__);
-                               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;
@@ -700,7 +766,7 @@ static int sierra_startup(struct usb_serial *serial)
 
 static void sierra_shutdown(struct usb_serial *serial)
 {
-       int i, j;
+       int i;
        struct usb_serial_port *port;
        struct sierra_port_private *portdata;
 
@@ -713,12 +779,6 @@ static void sierra_shutdown(struct usb_serial *serial)
                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]);
-                       kfree(portdata->in_buffer[j]);
-               }
                kfree(portdata);
                usb_set_serial_port_data(port, NULL);
        }