[patch]fix usb serial race due to late shutdown #2

Oliver Neukum <[email protected]>
Newsgroups gmane.linux.usb.devel
Organization Novell
Message-ID <[email protected]>
Hi,

this uses Greg's idea to call shutdown() during disconnect().
It can do so by decoupling subdriver and core upon disconnect():
Having made sure that no callbacks into a subdriver can occur, the
subdriver's memory can be freed immediately.

With Alan's suggested cleanup.

	Regards
		Oliver

Signed-off-by: Oliver Neukum <[email protected]>
-----

--- 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
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.