usb-serial: change logic of serial lookups
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 1 Sep 2009 15:38:59 +0000 (11:38 -0400)
committerLive-CD User <linux@linux.site>
Sat, 19 Sep 2009 20:13:40 +0000 (13:13 -0700)
This patch (as1286) changes usb_serial_get_by_index().  Now the
routine will check whether the serial device has been disconnected; if
it has then the return value will be NULL.  If the device hasn't been
disconnected then the routine will return with serial->disc_mutex
held, so that the caller can use the structure without fear of racing
against driver unloads.

This permits the scope of table_mutex in destroy_serial() to be
reduced.  Instead of protecting the entire function, it suffices to
protect the part that actually uses serial_table[], i.e., the call to
return_serial().  There's no longer any danger of the refcount being
incremented after it reaches 0 (which was the reason for having the
large scope previously), because it can't reach 0 until the serial
device has been disconnected.

Also, the patch makes serial_install() check that serial is non-NULL
before attempting to use it.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/serial/usb-serial.c

index 266dc583867b95e5ba10ee01cb511536ccf3fdf9..87802ea8bbc8241927e1b56809ef6c72abac00f7 100644 (file)
@@ -66,6 +66,11 @@ static struct usb_serial *serial_table[SERIAL_TTY_MINORS];
 static DEFINE_MUTEX(table_lock);
 static LIST_HEAD(usb_serial_driver_list);
 
+/*
+ * Look up the serial structure.  If it is found and it hasn't been
+ * disconnected, return with its disc_mutex held and its refcount
+ * incremented.  Otherwise return NULL.
+ */
 struct usb_serial *usb_serial_get_by_index(unsigned index)
 {
        struct usb_serial *serial;
@@ -73,8 +78,15 @@ struct usb_serial *usb_serial_get_by_index(unsigned index)
        mutex_lock(&table_lock);
        serial = serial_table[index];
 
-       if (serial)
-               kref_get(&serial->kref);
+       if (serial) {
+               mutex_lock(&serial->disc_mutex);
+               if (serial->disconnected) {
+                       mutex_unlock(&serial->disc_mutex);
+                       serial = NULL;
+               } else {
+                       kref_get(&serial->kref);
+               }
+       }
        mutex_unlock(&table_lock);
        return serial;
 }
@@ -123,8 +135,10 @@ static void return_serial(struct usb_serial *serial)
 
        dbg("%s", __func__);
 
+       mutex_lock(&table_lock);
        for (i = 0; i < serial->num_ports; ++i)
                serial_table[serial->minor + i] = NULL;
+       mutex_unlock(&table_lock);
 }
 
 static void destroy_serial(struct kref *kref)
@@ -158,9 +172,7 @@ static void destroy_serial(struct kref *kref)
 
 void usb_serial_put(struct usb_serial *serial)
 {
-       mutex_lock(&table_lock);
        kref_put(&serial->kref, destroy_serial);
-       mutex_unlock(&table_lock);
 }
 
 /*****************************************************************************
@@ -190,9 +202,12 @@ static int serial_install(struct tty_driver *driver, struct tty_struct *tty)
                        return retval;
                /* allow the driver to update it */
                serial = usb_serial_get_by_index(tty->index);
-               if (serial->type->init_termios)
-                       serial->type->init_termios(tty);
-               usb_serial_put(serial);
+               if (serial) {
+                       if (serial->type->init_termios)
+                               serial->type->init_termios(tty);
+                       usb_serial_put(serial);
+                       mutex_unlock(&serial->disc_mutex);
+               }
        }
        /* Final install (we use the default method) */
        tty_driver_kref_get(driver);
@@ -218,7 +233,6 @@ static int serial_open (struct tty_struct *tty, struct file *filp)
                return -ENODEV;
        }
 
-       mutex_lock(&serial->disc_mutex);
        portNumber = tty->index - serial->minor;
        port = serial->port[portNumber];
        if (!port || serial->disconnected)
@@ -529,6 +543,7 @@ static int serial_proc_show(struct seq_file *m, void *v)
 
                seq_putc(m, '\n');
                usb_serial_put(serial);
+               mutex_unlock(&serial->disc_mutex);
        }
        return 0;
 }