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@xxxxxxxxxxxxxxxxxxx> --- Index: usb-2.6/drivers/usb/serial/usb-serial.c =================================================================== --- usb-2.6.orig/drivers/usb/serial/usb-serial.c +++ usb-2.6/drivers/usb/serial/usb-serial.c @@ -67,6 +67,11 @@ static struct usb_serial *serial_table[S 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; @@ -74,8 +79,15 @@ struct usb_serial *usb_serial_get_by_ind 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; } @@ -124,8 +136,10 @@ static void return_serial(struct usb_ser 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) @@ -159,9 +173,7 @@ static void destroy_serial(struct kref * void usb_serial_put(struct usb_serial *serial) { - mutex_lock(&table_lock); kref_put(&serial->kref, destroy_serial); - mutex_unlock(&table_lock); } /***************************************************************************** @@ -191,9 +203,12 @@ static int serial_install(struct tty_dri 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); @@ -219,7 +234,6 @@ static int serial_open (struct tty_struc return -ENODEV; } - mutex_lock(&serial->disc_mutex); portNumber = tty->index - serial->minor; port = serial->port[portNumber]; if (!port || serial->disconnected) @@ -523,6 +537,7 @@ static int serial_proc_show(struct seq_f seq_putc(m, '\n'); usb_serial_put(serial); + mutex_unlock(&serial->disc_mutex); } return 0; } -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html