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

Reply via email to