Hi, this is John's patch for fixing the last disconnect/IO races in usb-serial. I ported it to Greg's current tree. To explain how it works, I quote him:
---------------------------- This is a fix for race condition in usb-serial.c between serial_open and usb_serial_disconnect (kernel version 2.6.21-rc3) Prior to this patch instances of usb_serial were not ref-counted consistently, which caused serial_open to reuse already deleted instance. For more details search Web for "Possible race condition in usb-serial.c" discussion in Dec 2006. The present patch ensures that instances of usb_serial are ref-counted consistently by calling kref_get in get_free_serial and usb_serial_put in return_serial. Another problem was that the code prior to this patch called serial->type->shutdown(serial) from destroy_serial, which may execute after usb_serial_disconnect (if the serial device was opened). This was an error because no IO should be done on a USB device after usb_serial_disconnect returns. To fix that the present patch moved call to serial->type->shutdown(serial) from destroy_serial to usb_serial_disconnect. Also it introduces functions serial_lock, serial_unlock and serial_lock_and_wait_before_shutdown. serial_lock and serial_unlock are called from each serial_* routine and serial_lock_and_wait_before_shutdown is called from usb_serial_disconnect to ensure that all serial_* calls exit before usb_serial_disconnect calls serial->type->shutdown(serial), Signed-off-by: John Green <[EMAIL PROTECTED]> Signed-off-by: Oliver Neukum <[EMAIL PROTECTED]> -- --- a/drivers/usb/serial/usb-serial.c 2007-05-03 11:12:01.000000000 +0200 +++ b/drivers/usb/serial/usb-serial.c 2007-05-03 12:26:19.000000000 +0200 @@ -61,9 +61,41 @@ static struct usb_driver usb_serial_driv static int debug; static struct usb_serial *serial_table[SERIAL_TTY_MINORS]; /* initially all NULL */ -static spinlock_t table_lock; +static unsigned long ports_reserved[SERIAL_TTY_MINORS/BITS_PER_LONG]; +static spinlock_t table_lock; /* protects serial_table and ports_reserved */ static LIST_HEAD(usb_serial_driver_list); +/* + usb_serial objects are ref counted. This is necessary because pointers to an object + may be stored in several places. Each time a pointer is duplicated, the ref count + should be incremented. This done using kref_get(&serial->kref). When pointer is + destroyed, the count should be decremented using usb_serial_put(serial). + A typical life cycle of usb_serial: + + 1. usb_serial_probe creates usb_serial (ref count set to 1) and stores the pointer + in the usb_interface using usb_set_intfdata. + 2. usb_serial_probe calls get_free_serial, which copies the pointer one or more times + to serial_table indexed by TTY minor index (ref count is incremeted for each copy). + 3. serial_open takes this pointer from serial_table (ref count incremented) and passes it to TTY + (this is done indirectly: tty->driver_data = port, but port->serial = serial). + 4. serial_close decrements the ref count (because tty object is destroyed). + 5. usb_serial_disconnect calls return_serial, which erases all pointers to the given serial + from serial_table (decrementing the ref count). + 6. usb_serial_disconnect destroys the pointer, which was stored in usb_interface + (decrementing the ref count). + At this point the ref count normally reaches 0 and the object destroyed. + Alternatively, serial_close may be called after usb_serial_disconnect and the object destroyed + at that time. + + ports_reserved is an array of bits parallel to usb_serial. + get_free_serial uses this array to locate a range of TTY devices, which are not yet registered. + Then get_free_serial reserves the found range by setting bits. + After that usb_serial_probe registers TTY devices in this range using device_register. + A bit is cleared by destroy_serial after it unregisters devices using device_unregister. +*/ + +void usb_serial_put(struct usb_serial *serial); + struct usb_serial *usb_serial_get_by_index(unsigned index) { struct usb_serial *serial; @@ -92,7 +124,7 @@ static struct usb_serial *get_free_seria good_spot = 1; for (j = 1; j <= num_ports-1; ++j) - if ((i+j >= SERIAL_TTY_MINORS) || (serial_table[i+j])) { + if ((i+j >= SERIAL_TTY_MINORS) || (test_bit(i+j, ports_reserved))) { good_spot = 0; i += j; break; @@ -104,7 +136,9 @@ static struct usb_serial *get_free_seria j = 0; dbg("%s - minor base = %d", __FUNCTION__, *minor); for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i) { + kref_get(&serial->kref); serial_table[i] = serial; + __set_bit(i, ports_reserved); serial->port[j++]->number = i; } spin_unlock(&table_lock); @@ -126,10 +160,82 @@ static void return_serial(struct usb_ser spin_lock(&table_lock); for (i = 0; i < serial->num_ports; ++i) { serial_table[serial->minor + i] = NULL; + usb_serial_put(serial); } spin_unlock(&table_lock); } +static int usb_serial_check_reserved(unsigned index) +{ + int ret; + + spin_lock(&table_lock); + ret = test_bit(index, ports_reserved); + spin_unlock(&table_lock); + return ret; +} + +static void usb_serial_unreserve(unsigned index, unsigned num_ports) +{ + int i; + + spin_lock(&table_lock); + for (i = 0; i < num_ports; ++i, ++index) { + __clear_bit(index, ports_reserved); + } + spin_unlock(&table_lock); +} + +/* + * serial_lock expects a valid usb_serial * serial pointer. + * serial_lock returns 0 if usb_serial is still active and the caller can use it. + * In this case the caller must call serial_unlock. + * serial_lock returns -ENODEV if disconnection routine was already called on the device. + * In this case the caller should not perform any IO operations, but should return an error code. + */ +int serial_lock(struct usb_serial * serial) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&serial->lock, flags); + if (serial->shutdown_called) { + ret = -ENODEV; + } else { + serial->lock_count++; + ret = 0; + } + spin_unlock_irqrestore(&serial->lock, flags); + return ret; +} + +void serial_unlock(struct usb_serial * serial) +{ + int wakeup = 0; + unsigned long flags; + + spin_lock_irqsave(&serial->lock, flags); + serial->lock_count--; + if (serial->shutdown_called && serial->lock_count == 0) { + wakeup = 1; + } + spin_unlock_irqrestore(&serial->lock, flags); + + if(wakeup) { + wake_up(&serial->shutdown_wait); + } +} + +void serial_lock_and_wait_before_shutdown(struct usb_serial * serial) +{ + unsigned long flags; + + spin_lock_irqsave(&serial->lock, flags); + serial->shutdown_called = 1; + spin_unlock_irqrestore(&serial->lock, flags); + wait_event(serial->shutdown_wait, serial->lock_count == 0); +} + static void destroy_serial(struct kref *kref) { struct usb_serial *serial; @@ -140,10 +246,8 @@ static void destroy_serial(struct kref * dbg("%s - %s", __FUNCTION__, serial->type->description); - serial->type->shutdown(serial); - /* return the minor range that this device had */ - return_serial(serial); + usb_serial_unreserve(serial->minor, serial->num_ports); for (i = 0; i < serial->num_ports; ++i) serial->port[i]->open_count = 0; @@ -193,7 +297,11 @@ static int serial_open (struct tty_struc /* get the serial object associated with this tty pointer */ serial = usb_serial_get_by_index(tty->index); if (!serial) { - tty->driver_data = NULL; + /* There may be other files open for this port after device disconnection. + in this case don't erase tty->driver_data */ + if (!usb_serial_check_reserved(tty->index)) { + tty->driver_data = NULL; + } return -ENODEV; } @@ -204,9 +312,13 @@ static int serial_open (struct tty_struc goto bailout_kref_put; } + retval = serial_lock(serial); + if (retval) + goto bailout_kref_put; + if (mutex_lock_interruptible(&port->mutex)) { retval = -ERESTARTSYS; - goto bailout_kref_put; + goto bailout_kref_put_unlock; } ++port->open_count; @@ -234,6 +346,7 @@ static int serial_open (struct tty_struc } mutex_unlock(&port->mutex); + serial_unlock(serial); return 0; bailout_module_put: @@ -243,6 +356,8 @@ bailout_mutex_unlock: tty->driver_data = NULL; port->tty = NULL; mutex_unlock(&port->mutex); +bailout_kref_put_unlock: + serial_unlock(serial); bailout_kref_put: usb_serial_put(serial); return retval; @@ -299,8 +414,13 @@ static int serial_write (struct tty_stru goto exit; } + retval = serial_lock(port->serial); + if (retval) + goto exit; + /* pass on to the driver specific version of this function */ retval = port->serial->type->write(port, buf, count); + serial_unlock(port->serial); exit: return retval; @@ -321,9 +441,12 @@ static int serial_write_room (struct tty goto exit; } + retval = serial_lock(port->serial); + if (retval) + goto exit; /* pass on to the driver specific version of this function */ retval = port->serial->type->write_room(port); - + serial_unlock(port->serial); exit: return retval; } @@ -343,8 +466,12 @@ static int serial_chars_in_buffer (struc goto exit; } + retval = serial_lock(port->serial); + if (retval) + goto exit; /* pass on to the driver specific version of this function */ retval = port->serial->type->chars_in_buffer(port); + serial_unlock(port->serial); exit: return retval; @@ -364,9 +491,13 @@ static void serial_throttle (struct tty_ return; } + if(serial_lock(port->serial)) + return; /* pass on to the driver specific version of this function */ if (port->serial->type->throttle) port->serial->type->throttle(port); + serial_unlock(port->serial); + } static void serial_unthrottle (struct tty_struct * tty) @@ -383,9 +514,13 @@ static void serial_unthrottle (struct tt return; } + if(serial_lock(port->serial)) + return; /* pass on to the driver specific version of this function */ if (port->serial->type->unthrottle) port->serial->type->unthrottle(port); + serial_unlock(port->serial); + } static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) @@ -403,11 +538,16 @@ static int serial_ioctl (struct tty_stru goto exit; } + retval = serial_lock(port->serial); + if (retval) + goto exit; /* pass on to the driver specific version of this function if it is available */ if (port->serial->type->ioctl) retval = port->serial->type->ioctl(port, file, cmd, arg); else retval = -ENOIOCTLCMD; + serial_unlock(port->serial); + exit: return retval; @@ -427,9 +567,13 @@ static void serial_set_termios (struct t return; } + if(serial_lock(port->serial)) + return; /* pass on to the driver specific version of this function if it is available */ if (port->serial->type->set_termios) port->serial->type->set_termios(port, old); + serial_unlock(port->serial); + } static void serial_break (struct tty_struct *tty, int break_state) @@ -446,9 +590,12 @@ static void serial_break (struct tty_str return; } + if(serial_lock(port->serial)) + return; /* pass on to the driver specific version of this function if it is available */ if (port->serial->type->break_ctl) port->serial->type->break_ctl(port, break_state); + serial_unlock(port->serial); } static int serial_read_proc (char *page, char **start, off_t off, int count, int *eof, void *data) @@ -465,6 +612,8 @@ static int serial_read_proc (char *page, serial = usb_serial_get_by_index(i); if (serial == NULL) continue; + if(serial_lock(serial)) + continue; length += sprintf (page+length, "%d:", i); if (serial->type->driver.owner) @@ -481,6 +630,7 @@ static int serial_read_proc (char *page, length += sprintf (page+length, "\n"); if ((length + begin) > (off + count)) { + serial_unlock(serial); usb_serial_put(serial); goto done; } @@ -488,6 +638,7 @@ static int serial_read_proc (char *page, begin += length; length = 0; } + serial_unlock(serial); usb_serial_put(serial); } *eof = 1; @@ -501,6 +652,7 @@ done: static int serial_tiocmget (struct tty_struct *tty, struct file *file) { struct usb_serial_port *port = tty->driver_data; + int retval; if (!port) return -ENODEV; @@ -512,16 +664,23 @@ static int serial_tiocmget (struct tty_s return -ENODEV; } + retval = serial_lock(port->serial); + if(retval) + goto exit; if (port->serial->type->tiocmget) - return port->serial->type->tiocmget(port, file); - - return -EINVAL; + retval = port->serial->type->tiocmget(port, file); + else + retval = -EINVAL; + serial_unlock(port->serial); +exit: + return retval; } static int serial_tiocmset (struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; + int retval; if (!port) return -ENODEV; @@ -533,10 +692,17 @@ static int serial_tiocmset (struct tty_s return -ENODEV; } + retval = serial_lock(port->serial); + if (retval) + goto exit; if (port->serial->type->tiocmset) - return port->serial->type->tiocmset(port, file, set, clear); + retval = port->serial->type->tiocmset(port, file, set, clear); + else + retval = -EINVAL; + serial_unlock(port->serial); - return -EINVAL; +exit: + return retval; } /* @@ -614,6 +780,9 @@ static struct usb_serial * create_serial serial->interface = interface; kref_init(&serial->kref); + spin_lock_init(&serial->lock); + init_waitqueue_head(&serial->shutdown_wait); + return serial; } @@ -1057,6 +1226,9 @@ void usb_serial_disconnect(struct usb_in usb_set_intfdata (interface, NULL); if (serial) { + + return_serial(serial); /* erase pointers to serial from serial_table */ + for (i = 0; i < serial->num_ports; ++i) { port = serial->port[i]; if (port) { @@ -1065,8 +1237,20 @@ void usb_serial_disconnect(struct usb_in kill_traffic(port); } } - /* let the last holder of this object - * cause it to be cleaned up */ + + /* The following call will block if any serial_* routine (on our serial) + * is still in progress. It will wait for all such routines to exit. + * After that all further calls to serial_* routines will return immediately + * without performing any IO or touching the content of serial or port. + * Therefore, we can cleanup serial and ports. + */ + serial_lock_and_wait_before_shutdown(serial); + serial->type->shutdown(serial); + + /* Now we destroy the pointer to serial, which was associated with usb_interface. + * This is likely to delete serial object, unless some TTY files are still open. + * In that case the object will be deleted from serial_close. + */ usb_serial_put(serial); } dev_info(dev, "device disconnected\n"); --- a/include/linux/usb/serial.h 2007-03-23 15:51:38.610525100 -0800 +++ b/include/linux/usb/serial.h 2007-03-23 17:16:58.524274200 -0800 @@ -119,6 +119,11 @@ static inline void usb_set_serial_port_d * @num_bulk_in: number of bulk in endpoints we have * @num_bulk_out: number of bulk out endpoints we have * @port: array of struct usb_serial_port structures for the different ports. + * @kref: refcount for storing the number of pointers to usb_serial + * @lock: spinlock to protect access to lock_count and shutdown_called + * @lock_count: number of serial_* routines, which locked usb_serial and are using it. + * @shutdown_called: If this flag is set, serial_* routines must not use other fields of usb_serial. + * @shutdown_wait: used by usb_serial_disconnect to block until all serial_* routines exit. * @private: place to put any driver specific information that is needed. The * usb-serial driver is required to manage this data, the usb-serial core * will not touch this. Use usb_get_serial_data() and @@ -136,7 +141,11 @@ struct usb_serial { char num_bulk_in; char num_bulk_out; struct usb_serial_port * port[MAX_NUM_PORTS]; - struct kref kref; + struct kref kref; + spinlock_t lock; + int lock_count; + int shutdown_called; + wait_queue_head_t shutdown_wait; void * private; }; #define to_usb_serial(d) container_of(d, struct usb_serial, kref) ------------------------------------------------------------------------- This SF.net email is sponsored by DB2 Express Download DB2 Express C - the FREE version of DB2 express and take control of your XML. No limits. Just data. Click to get it now. http://sourceforge.net/powerbar/db2/ _______________________________________________ linux-usb-devel@lists.sourceforge.net To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel