Am Freitag, 16. März 2007 00:02 schrieb Greg KH:
> > > However, shouldn't we just be calling "shutdown" here instead?  Lots of
> > 
> > Currently the locking is not good enough to call shutdown() so early.
> > It could be strengthened. Make your choice. I can and will do it
> > either way. Just the current state of affairs is bad.
> 
> I agree, but we still need the shutdown() function to free the memory.

If but only if we touch the subdriver after shutdown(). If we can avoid
that, shutdown() can be called in disconnect(). Here's an untested patch
to do just that. I'll test it tomorrow.

The principal approach is to never call down into the subdriver after
a device is known disconnected, then to wait for all ongoing operations
to finish and then call shutdown.

        Regards
                Oliver

--- linux-2.6.20/drivers/usb/serial/usb-serial.c        2007-03-16 
01:32:38.000000000 +0100
+++ linux-2.6.21-rc3-git7/drivers/usb/serial/usb-serial.c       2007-03-16 
01:26:28.000000000 +0100
@@ -59,8 +59,10 @@
 
 static int debug;
 static struct usb_serial *serial_table[SERIAL_TTY_MINORS];     /* initially 
all NULL */
+static u32 deathrow[SERIAL_TTY_MINORS/32];                     /* 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 % 32, &deathrow[i / 32]);
                }
                spin_unlock(&table_lock);
                return serial;
@@ -112,6 +115,54 @@
        return NULL;
 }
 
+/* requires locking from caller*/
+static int on_deathrow(struct usb_serial_port *port)
+{
+       int number = port->number;
+
+       return deathrow[number / 32] & (1 << (number % 32)); 
+}
+
+/* requires locking from caller*/
+static void schedule_execution(struct usb_serial_port *port)
+{
+       int number = port->number;
+
+       deathrow[number / 32] |= (1 << (number % 32));
+}
+
+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);
+       serial->busy--;
+       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 +199,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 +251,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 +285,7 @@
        }
 
        mutex_unlock(&port->mutex);
+       unlock_serial(port);
        return 0;
 
 bailout_module_put:
@@ -242,6 +295,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 +353,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 +381,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 +408,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 +434,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 +457,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 +480,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 +509,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 +532,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 +590,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 +602,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 +633,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 +721,7 @@
        serial->type = driver;
        serial->interface = interface;
        kref_init(&serial->kref);
+       spin_lock_init(&serial->lock);
 
        return serial;
 }
@@ -1058,12 +1168,20 @@
        if (serial) {
                for (i = 0; i < serial->num_ports; ++i) {
                        port = serial->port[i];
+                       spin_lock_irq(&serial->lock);
+                       schedule_execution(port);
+                       spin_unlock_irq(&serial->lock);
                        if (port) {
                                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);
--- linux-2.6.20/include/linux/usb/serial.h     2007-03-16 01:32:25.000000000 
+0100
+++ linux-2.6.21-rc3-git7/include/linux/usb/serial.h    2007-03-16 
01:20:55.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;
+       char                            busy;
        struct usb_serial_port *        port[MAX_NUM_PORTS];
        struct kref                     kref;
        void *                          private;

-------------------------------------------------------------------------
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