--- 0102/usb-serial.c	Thu Jan  4 13:37:12 2007
+++ 0104/usb-serial.c	Thu Jan  4 13:37:26 2007
@@ -60,7 +60,32 @@
 static int debug;
 static struct usb_serial *serial_table[SERIAL_TTY_MINORS];	/* initially all NULL */
 static spinlock_t table_lock;
-static LIST_HEAD(usb_serial_driver_list);
+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.
+*/
+
+void usb_serial_put(struct usb_serial *serial);
 
 struct usb_serial *usb_serial_get_by_index(unsigned index)
 {
@@ -100,8 +125,10 @@
 
 		*minor = i;
 		dbg("%s - minor base = %d", __FUNCTION__, *minor);
-		for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i)
-			serial_table[i] = serial;
+		for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i) {
+			kref_get(&serial->kref);
+			serial_table[i] = serial;
+		}
 		spin_unlock(&table_lock);
 		return serial;
 	}
@@ -120,49 +147,96 @@
 
 	spin_lock(&serial_table);
 	for (i = 0; i < serial->num_ports; ++i) {
-		serial_table[serial->minor + i] = NULL;
+		serial_table[serial->minor + i] = NULL;
+		usb_serial_put(serial);
 	}
 	spin_unlock(&serial_table);
-}
+}
+
+/* 
+ * serial_lock expects a valid usb_serial * serial pointer.
+ * serial_lock returns 1 if usb_serial is still active and the caller can use it.
+ *	 In this case the caller must call serial_unlock.
+ * serial_lock returns 0 if disconnection routine was already called on the device. 
+ *	 In this case the caller must not perform any IO operations, but should return an error code.
+ */
+int serial_lock(usb_serial * serial)
+{
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial->lock, flags);
+	if (serial->shutdown_called) {
+		ret = 0;
+	} else {
+		serial->lock_count++;
+		ret = 1;
+	}	 
+	spin_unlock_irqrestore(&serial->lock, flags);
+	return ret;
+}
+
+void serial_unlock(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(usb_serial * serial)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial->lock, flags);
+	serial->shutdown_called = 1;
+	while(serial->lock_count > 0) {
+		spin_unlock_irqrestore(&serial->lock, flags);
+		wait_event(serial->shutdown_wait, serial->lock_count == 0);
+		spin_lock_irqsave(&serial->lock, flags);
+	}
+	spin_unlock_irqrestore(&serial->lock, flags);
+}
 
 static void destroy_serial(struct kref *kref)
 {
 	struct usb_serial *serial;
 	struct usb_serial_port *port;
-	int i;
-
-	serial = to_usb_serial(kref);
-
-	dbg("%s - %s", __FUNCTION__, serial->type->description);
-
-	serial->type->shutdown(serial);
-
-	/* return the minor range that this device had */
-	return_serial(serial);
-
-	for (i = 0; i < serial->num_ports; ++i)
-		serial->port[i]->open_count = 0;
-
-	/* the ports are cleaned up and released in port_release() */
-	for (i = 0; i < serial->num_ports; ++i)
-		if (serial->port[i]->dev.parent != NULL) {
-			device_unregister(&serial->port[i]->dev);
-			serial->port[i] = NULL;
-		}
-
-	/* If this is a "fake" port, we have to clean it up here, as it will
-	 * not get cleaned up in port_release() as it was never registered with
-	 * the driver core */
-	if (serial->num_ports < serial->num_port_pointers) {
-		for (i = serial->num_ports; i < serial->num_port_pointers; ++i) {
-			port = serial->port[i];
-			if (!port)
-				continue;
-			port_free(port);
-		}
-	}
-
-	usb_put_dev(serial->dev);
+	int i;
+
+	serial = to_usb_serial(kref);
+
+	dbg("%s - %s", __FUNCTION__, serial->type->description);
+
+	/* the ports are cleaned up and released in port_release() */
+	for (i = 0; i < serial->num_ports; ++i)
+		if (serial->port[i]->dev.parent != NULL) {
+			device_unregister(&serial->port[i]->dev);
+			serial->port[i] = NULL;
+		}
+
+	/* If this is a "fake" port, we have to clean it up here, as it will
+	 * not get cleaned up in port_release() as it was never registered with
+	 * the driver core */
+	if (serial->num_ports < serial->num_port_pointers) {
+		for (i = serial->num_ports; i < serial->num_port_pointers; ++i) {
+			port = serial->port[i];
+			if (!port)
+				continue;
+			port_free(port);
+		}
+	}
+
+	usb_put_dev(serial->dev);
 
 	/* free up any memory that we allocated */
 	kfree (serial);
@@ -293,9 +367,14 @@
 		dbg("%s - port not opened", __FUNCTION__);
 		goto exit;
 	}
-
-	/* pass on to the driver specific version of this function */
-	retval = port->serial->type->write(port, buf, count);
+
+	if(serial_lock(port->serial)) {
+		/* pass on to the driver specific version of this function */
+		retval = port->serial->type->write(port, buf, count);
+		serial_unlock(port->serial);
+	} else {
+		retval = -ENODEV;
+	}
 
 exit:
 	return retval;
@@ -315,10 +394,14 @@
 		dbg("%s - port not open", __FUNCTION__);
 		goto exit;
 	}
-
-	/* pass on to the driver specific version of this function */
-	retval = port->serial->type->write_room(port);
-
+
+	if(serial_lock(port->serial)) {
+		/* pass on to the driver specific version of this function */
+		retval = port->serial->type->write_room(port);
+		serial_unlock(port->serial);
+	} else {
+		retval = -ENODEV;
+	}
 exit:
 	return retval;
 }
@@ -338,8 +421,13 @@
 		goto exit;
 	}
 
-	/* pass on to the driver specific version of this function */
-	retval = port->serial->type->chars_in_buffer(port);
+	if(serial_lock(port->serial)) {
+		/* pass on to the driver specific version of this function */
+		retval = port->serial->type->chars_in_buffer(port);
+		serial_unlock(port->serial);
+	} else {
+		retval = -ENODEV;
+	}
 
 exit:
 	return retval;
@@ -358,10 +446,14 @@
 		dbg ("%s - port not open", __FUNCTION__);
 		return;
 	}
+
+	if(serial_lock(port->serial)) {
+		/* pass on to the driver specific version of this function */
+		if (port->serial->type->throttle)
+			port->serial->type->throttle(port);
+		serial_unlock(port->serial);
+	}
 
-	/* pass on to the driver specific version of this function */
-	if (port->serial->type->throttle)
-		port->serial->type->throttle(port);
 }
 
 static void serial_unthrottle (struct tty_struct * tty)
@@ -377,10 +469,14 @@
 		dbg("%s - port not open", __FUNCTION__);
 		return;
 	}
+
+	if(serial_lock(port->serial)) {
+		/* pass on to the driver specific version of this function */
+		if (port->serial->type->unthrottle)
+			port->serial->type->unthrottle(port);
+		serial_unlock(port->serial);
+	}
 
-	/* pass on to the driver specific version of this function */
-	if (port->serial->type->unthrottle)
-		port->serial->type->unthrottle(port);
 }
 
 static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg)
@@ -397,12 +493,18 @@
 		dbg ("%s - port not open", __FUNCTION__);
 		goto exit;
 	}
+
+	if(serial_lock(port->serial)) {
+		/* 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);
+	} else {
+		retval = -ENODEV;
+	}
 
-	/* 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;
 
 exit:
 	return retval;
@@ -421,10 +523,14 @@
 		dbg("%s - port not open", __FUNCTION__);
 		return;
 	}
+
+	if(serial_lock(port->serial)) {
+		/* 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);
+	}
 
-	/* 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);
 }
 
 static void serial_break (struct tty_struct *tty, int break_state)
@@ -440,10 +546,13 @@
 		dbg("%s - port not open", __FUNCTION__);
 		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);
+
+	if(serial_lock(port->serial)) {
+		/* 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)
@@ -459,6 +568,8 @@
 	for (i = 0; i < SERIAL_TTY_MINORS && length < PAGE_SIZE; ++i) {
 		serial = usb_serial_get_by_index(i);
 		if (serial == NULL)
+			continue;
+		if(!serial_lock(serial))
 			continue;
 
 		length += sprintf (page+length, "%d:", i);
@@ -475,14 +586,16 @@
 		length += sprintf (page+length, " path:%s", tmp);
 			
 		length += sprintf (page+length, "\n");
-		if ((length + begin) > (off + count)) {
+		if ((length + begin) > (off + count)) {
+			serial_unlock(serial);
 			usb_serial_put(serial);
 			goto done;
 		}
 		if ((length + begin) < off) {
 			begin += length;
 			length = 0;
-		}
+		}
+		serial_unlock(serial);
 		usb_serial_put(serial);
 	}
 	*eof = 1;
@@ -496,6 +609,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;
@@ -506,17 +620,25 @@
 		dbg("%s - port not open", __FUNCTION__);
 		return -ENODEV;
 	}
-
-	if (port->serial->type->tiocmget)
-		return port->serial->type->tiocmget(port, file);
-
-	return -EINVAL;
+
+	if(serial_lock(port->serial)) {
+		if (port->serial->type->tiocmget)
+			retval = port->serial->type->tiocmget(port, file);
+		else
+			retval = -EINVAL;
+		serial_unlock(port->serial);
+	} else {
+		retval = -ENODEV;
+	}
+
+	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;
@@ -527,11 +649,18 @@
 		dbg("%s - port not open", __FUNCTION__);
 		return -ENODEV;
 	}
-
-	if (port->serial->type->tiocmset)
-		return port->serial->type->tiocmset(port, file, set, clear);
-
-	return -EINVAL;
+
+	if(serial_lock(port->serial)) {
+		if (port->serial->type->tiocmset)
+			retval = port->serial->type->tiocmset(port, file, set, clear);
+		else
+			retval = -EINVAL;
+		serial_unlock(port->serial);
+	} else {
+		retval = -ENODEV;
+	}
+
+	return retval;
 }
 
 /*
@@ -569,9 +698,9 @@
 	port_free(port);
 }
 
-static void kill_traffic(struct usb_serial_port *port)
-{
-	usb_kill_urb(port->read_urb);
+static void kill_traffic(struct usb_serial_port *port)
+{
+	usb_kill_urb(port->read_urb);
 	usb_kill_urb(port->write_urb);
 	usb_kill_urb(port->interrupt_in_urb);
 	usb_kill_urb(port->interrupt_out_urb);
@@ -580,17 +709,17 @@
 static void port_free(struct usb_serial_port *port)
 {
 	kill_traffic(port);
-	usb_free_urb(port->read_urb);
-	usb_free_urb(port->write_urb);
-	usb_free_urb(port->interrupt_in_urb);
-	usb_free_urb(port->interrupt_out_urb);
-	kfree(port->bulk_in_buffer);
-	kfree(port->bulk_out_buffer);
-	kfree(port->interrupt_in_buffer);
-	kfree(port->interrupt_out_buffer);
-	flush_scheduled_work();		/* port->work */
-	kfree(port);
-}
+	usb_free_urb(port->read_urb);
+	usb_free_urb(port->write_urb);
+	usb_free_urb(port->interrupt_in_urb);
+	usb_free_urb(port->interrupt_out_urb);
+	kfree(port->bulk_in_buffer);
+	kfree(port->bulk_out_buffer);
+	kfree(port->interrupt_in_buffer);
+	kfree(port->interrupt_out_buffer);
+	flush_scheduled_work();		/* port->work */
+	kfree(port);
+}
 
 static struct usb_serial * create_serial (struct usb_device *dev, 
 					  struct usb_interface *interface,
@@ -606,7 +735,12 @@
 	serial->dev = usb_get_dev(dev);
 	serial->type = driver;
 	serial->interface = interface;
-	kref_init(&serial->kref);
+	kref_init(&serial->kref);
+
+	spin_lock_init(&serial->lock);
+	serial->lock_count = 0;
+	serial->shutdown_called = 0;
+	init_waitqueue_head(&serial->shutdown_wait);
 
 	return serial;
 }
@@ -1027,7 +1161,13 @@
 	dbg ("%s", __FUNCTION__);
 
 	usb_set_intfdata (interface, NULL);
-	if (serial) {
+	if (serial) {
+		
+		/* return the minor range that this device had.
+		 * after that all future serial_open calls on this device will fail.
+		 */
+		return_serial(serial);
+
 		for (i = 0; i < serial->num_ports; ++i) {
 			port = serial->port[i];
 			if (port) {
@@ -1035,9 +1175,25 @@
 					tty_hangup(port->tty);
 				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);
+
+		for (i = 0; i < serial->num_ports; ++i)
+			serial->port[i]->open_count = 0;
+
+		/* 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");
