Am Dienstag, 20. März 2007 00:56 schrieb John:
> 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.

Hi John,

this was so long ago that I made a patch based on the discussion
we had back then. Would you mind having a look at it? We should
merge those patches to get an optimum.

        Regards
                Oliver

--- a/include/linux/usb/serial.h        2007-03-16 01:32:25.000000000 +0100
+++ b/include/linux/usb/serial.h        2007-03-16 20:32:05.000000000 +0100
@@ -128,6 +128,7 @@
        struct usb_device *             dev;
        struct usb_serial_driver *      type;
        struct usb_interface *          interface;
+       spinlock_t                      lock;
        unsigned char                   minor;
        unsigned char                   num_ports;
        unsigned char                   num_port_pointers;
@@ -135,6 +136,7 @@
        char                            num_interrupt_out;
        char                            num_bulk_in;
        char                            num_bulk_out;
+       int                             busy;
        struct usb_serial_port *        port[MAX_NUM_PORTS];
        struct kref                     kref;
        void *                          private;
--- a/drivers/usb/serial/usb-serial.c   2007-03-16 01:32:38.000000000 +0100
+++ b/drivers/usb/serial/usb-serial.c   2007-03-16 21:24:47.000000000 +0100
@@ -59,8 +59,10 @@
 
 static int debug;
 static struct usb_serial *serial_table[SERIAL_TTY_MINORS];     /* initially 
all NULL */
+static unsigned long deathrow[SERIAL_TTY_MINORS/BITS_PER_LONG];                
        /* disconnected but open*/
 static spinlock_t table_lock;
 static LIST_HEAD(usb_serial_driver_list);
+static DECLARE_WAIT_QUEUE_HEAD(usbserial_henchman);
 
 struct usb_serial *usb_serial_get_by_index(unsigned index)
 {
@@ -104,6 +106,7 @@
                for (i = *minor; (i < (*minor + num_ports)) && (i < 
SERIAL_TTY_MINORS); ++i) {
                        serial_table[i] = serial;
                        serial->port[j++]->number = i;
+                       __clear_bit(i, deathrow);
                }
                spin_unlock(&table_lock);
                return serial;
@@ -112,6 +115,53 @@
        return NULL;
 }
 
+/* requires locking from caller*/
+static int on_deathrow(struct usb_serial_port *port)
+{
+       int number = port->number;
+
+       return test_bit(number, deathrow); 
+}
+
+/* requires locking from caller*/
+static void schedule_execution(struct usb_serial_port *port)
+{
+       int number = port->number;
+
+       __set_bit(number, deathrow);
+}
+
+static int lock_serial(struct usb_serial_port *port)
+{
+       struct usb_serial *serial = port->serial;
+       unsigned long flags;
+       int terminated;
+
+       spin_lock_irqsave(&serial->lock, flags);
+       terminated = on_deathrow(port);
+       if (!terminated)
+               serial->busy++;
+       spin_unlock_irqrestore(&serial->lock, flags);
+
+       if (terminated)
+               return -ENODEV;
+       return 0;
+}
+
+static void unlock_serial(struct usb_serial_port *port)
+{
+       struct usb_serial *serial = port->serial;
+       unsigned long flags;
+       int waiters;
+
+       spin_lock_irqsave(&serial->lock, flags);
+       waiters = --serial->busy;
+       spin_unlock_irqrestore(&serial->lock, flags);
+
+       if (!waiters)
+               wake_up(&usbserial_henchman);
+}
+
 static void return_serial(struct usb_serial *serial)
 {
        int i;
@@ -148,9 +198,6 @@
                        serial->port[i] = NULL;
                }
 
-       if (serial->type->shutdown)
-               serial->type->shutdown(serial);
-
        /* return the minor range that this device had */
        return_serial(serial);
 
@@ -203,9 +250,13 @@
                goto bailout_kref_put;
        }
 
+       retval = lock_serial(port);
+       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;
@@ -233,6 +284,7 @@
        }
 
        mutex_unlock(&port->mutex);
+       unlock_serial(port);
        return 0;
 
 bailout_module_put:
@@ -242,6 +294,8 @@
        tty->driver_data = NULL;
        port->tty = NULL;
        mutex_unlock(&port->mutex);
+bailout_kref_put_unlock:
+       unlock_serial(port);
 bailout_kref_put:
        usb_serial_put(serial);
        return retval;
@@ -298,9 +352,15 @@
                goto exit;
        }
 
+       retval = lock_serial(port);
+       if (retval)
+               goto exit;
+
        /* pass on to the driver specific version of this function */
        retval = port->serial->type->write(port, buf, count);
 
+       unlock_serial(port);
+
 exit:
        return retval;
 }
@@ -320,9 +380,14 @@
                goto exit;
        }
 
+       retval = lock_serial(port);
+       if (retval)
+               goto exit;
        /* pass on to the driver specific version of this function */
        retval = port->serial->type->write_room(port);
 
+       unlock_serial(port);
+
 exit:
        return retval;
 }
@@ -342,9 +407,13 @@
                goto exit;
        }
 
+       retval = lock_serial(port);
+       if (retval)
+               goto exit;
        /* pass on to the driver specific version of this function */
        retval = port->serial->type->chars_in_buffer(port);
 
+       unlock_serial(port);
 exit:
        return retval;
 }
@@ -364,8 +433,12 @@
        }
 
        /* pass on to the driver specific version of this function */
-       if (port->serial->type->throttle)
+       if (port->serial->type->throttle) {
+               if (0 < lock_serial(port))
+                       return;
                port->serial->type->throttle(port);
+               unlock_serial(port);
+       }
 }
 
 static void serial_unthrottle (struct tty_struct * tty)
@@ -383,8 +456,12 @@
        }
 
        /* pass on to the driver specific version of this function */
-       if (port->serial->type->unthrottle)
+       if (port->serial->type->unthrottle) {
+               if (0 < lock_serial(port))
+                       return;
                port->serial->type->unthrottle(port);
+               unlock_serial(port);
+       }
 }
 
 static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned 
int cmd, unsigned long arg)
@@ -402,12 +479,16 @@
                goto exit;
        }
 
+       retval = lock_serial(port);
+       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;
-
+       
+       unlock_serial(port);
 exit:
        return retval;
 }
@@ -427,8 +508,12 @@
        }
 
        /* pass on to the driver specific version of this function if it is 
available */
-       if (port->serial->type->set_termios)
+       if (port->serial->type->set_termios) {
+               if (0 < lock_serial(port))
+                       return;
                port->serial->type->set_termios(port, old);
+               unlock_serial(port);
+       }
 }
 
 static void serial_break (struct tty_struct *tty, int break_state)
@@ -446,8 +531,12 @@
        }
 
        /* pass on to the driver specific version of this function if it is 
available */
-       if (port->serial->type->break_ctl)
+       if (port->serial->type->break_ctl) {
+               if (0 < lock_serial(port))
+                       return;
                port->serial->type->break_ctl(port, break_state);
+               unlock_serial(port);
+       }
 }
 
 static int serial_read_proc (char *page, char **start, off_t off, int count, 
int *eof, void *data)
@@ -500,6 +589,7 @@
 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;
@@ -511,16 +601,26 @@
                return -ENODEV;
        }
 
+       retval = lock_serial(port);
+       if (retval)
+               goto exit;
+
        if (port->serial->type->tiocmget)
-               return port->serial->type->tiocmget(port, file);
+               retval = port->serial->type->tiocmget(port, file);
+       else
+               retval = -EINVAL;
+
+       unlock_serial(port);
+exit:
 
-       return -EINVAL;
+       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;
@@ -532,10 +632,18 @@
                return -ENODEV;
        }
 
+       retval = lock_serial(port);
+       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;
 
-       return -EINVAL;
+       unlock_serial(port);
+exit:
+       return retval;
 }
 
 /*
@@ -612,6 +720,7 @@
        serial->type = driver;
        serial->interface = interface;
        kref_init(&serial->kref);
+       spin_lock_init(&serial->lock);
 
        return serial;
 }
@@ -1059,11 +1168,19 @@
                for (i = 0; i < serial->num_ports; ++i) {
                        port = serial->port[i];
                        if (port) {
+                               spin_lock_irq(&serial->lock);
+                               schedule_execution(port);
+                               spin_unlock_irq(&serial->lock);
                                if (port->tty)
                                        tty_hangup(port->tty);
                                kill_traffic(port);
                        }
                }
+               wait_event(usbserial_henchman, serial->busy == 0);
+               /* allow the subdriver to cleanly kill
+                * private URBs */
+               if (serial->type->shutdown)
+                       (serial->type->shutdown)(serial);
                /* let the last holder of this object 
                 * cause it to be cleaned up */
                usb_serial_put(serial);

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
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