# This is a BitKeeper generated patch for the following project:
# Project Name: Linux kernel tree
# This patch format is intended for GNU patch command version 2.5 or higher.
# This patch includes the following deltas:
#	           ChangeSet	1.739   -> 1.740  
#	drivers/usb/core/drivers.c	1.8     -> 1.9    
#	drivers/usb/class/printer.c	1.28    -> 1.29   
#	 include/linux/usb.h	1.40    -> 1.41   
#	drivers/usb/core/devices.c	1.15    -> 1.16   
#	drivers/usb/core/hub.c	1.29    -> 1.30   
#	drivers/usb/core/devio.c	1.28    -> 1.29   
#	drivers/usb/core/usb.c	1.66    -> 1.67   
#	drivers/usb/media/pwc-if.c	1.22    -> 1.23   
#	drivers/usb/core/file.c	1.1     -> 1.2    
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/07/18	oliver@neukum.name	1.740
# [PATCH] USB: lots of locking and other SMP race fixes
# 
# This is a merge of a bunch of SMP and locking fixes for the USB code
# that Oliver has sent me (greg k-h) over the past few weeks.
# --------------------------------------------
#
diff -Nru a/drivers/usb/class/printer.c b/drivers/usb/class/printer.c
--- a/drivers/usb/class/printer.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/class/printer.c	Fri Jul 19 11:04:14 2002
@@ -655,7 +655,10 @@
 							  (count - writecount) : USBLP_BUF_SIZE;
 
 		if (copy_from_user(usblp->writeurb->transfer_buffer, buffer + writecount,
-				usblp->writeurb->transfer_buffer_length)) return -EFAULT;
+				usblp->writeurb->transfer_buffer_length)) {
+			up(&usblp->sem);
+			return writecount ? writecount : -EFAULT;
+		}
 
 		usblp->writeurb->dev = usblp->dev;
 		usblp->wcomplete = 0;
diff -Nru a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c
--- a/drivers/usb/core/devices.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/devices.c	Fri Jul 19 11:04:14 2002
@@ -152,8 +152,8 @@
 
 void usbdevfs_conn_disc_event(void)
 {
-	wake_up(&deviceconndiscwq);
 	conndiscevcnt++;
+	wake_up(&deviceconndiscwq);
 }
 
 static const char *class_decode(const int class)
@@ -239,6 +239,7 @@
 
 	if (start > end)
 		return start;
+	lock_kernel(); /* driver might be unloaded */
 	start += sprintf(start, format_iface,
 			 desc->bInterfaceNumber,
 			 desc->bAlternateSetting,
@@ -248,6 +249,7 @@
 			 desc->bInterfaceSubClass,
 			 desc->bInterfaceProtocol,
 			 iface->driver ? iface->driver->name : "(none)");
+	unlock_kernel();
 	return start;
 }
 
@@ -597,6 +599,13 @@
 			unlock_kernel();
 			return POLLIN;
 		}
+		
+		/* we may have dropped BKL - need to check for having lost the race */
+		if (file->private_data) {
+			kfree(st);
+			goto lost_race;
+		}
+
 		/*
 		 * need to prevent the module from being unloaded, since
 		 * proc_unregister does not call the release method and
@@ -606,6 +615,7 @@
 		file->private_data = st;
 		mask = POLLIN;
 	}
+lost_race:
 	if (file->f_mode & FMODE_READ)
                 poll_wait(file, &deviceconndiscwq, wait);
 	if (st->lastev != conndiscevcnt)
diff -Nru a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
--- a/drivers/usb/core/devio.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/devio.c	Fri Jul 19 11:04:14 2002
@@ -361,14 +361,14 @@
 	if (intf >= 8*sizeof(ps->ifclaimed))
 		return -EINVAL;
 	err = -EINVAL;
-	lock_kernel();
 	dev = ps->dev;
+	down(&dev->serialize);
 	if (dev && test_and_clear_bit(intf, &ps->ifclaimed)) {
 		iface = &dev->actconfig->interface[intf];
 		usb_driver_release_interface(&usbdevfs_driver, iface);
 		err = 0;
 	}
-	unlock_kernel();
+	up(&dev->serialize);
 	return err;
 }
 
@@ -722,14 +722,11 @@
 		if (test_bit(i, &ps->ifclaimed))
 			continue;
 
-		if (intf->driver) {
-			const struct usb_device_id *id;
-			down(&intf->driver->serialize);
-			intf->driver->disconnect(ps->dev, intf->private_data);
-			id = usb_match_id(ps->dev,intf,intf->driver->id_table);
-			intf->driver->probe(ps->dev, i, id);
-			up(&intf->driver->serialize);
+		lock_kernel();
+		if (intf->driver && ps->dev) {
+			usb_bind_driver(intf->driver,ps->dev, i);
 		}
+		unlock_kernel();
 	}
 
 	return 0;
@@ -1092,16 +1089,17 @@
 
        /* disconnect kernel driver from interface, leaving it unbound.  */
        case USBDEVFS_DISCONNECT:
+       	/* this function is voodoo. without locking it is a maybe thing */
+		lock_kernel();
                driver = ifp->driver;
                if (driver) {
-                       down (&driver->serialize);
                        dbg ("disconnect '%s' from dev %d interface %d",
                                driver->name, ps->dev->devnum, ctrl.ifno);
-                       driver->disconnect (ps->dev, ifp->private_data);
+		       usb_unbind_driver(ps->dev, ifp);
                        usb_driver_release_interface (driver, ifp);
-                       up (&driver->serialize);
                } else
 			retval = -EINVAL;
+		unlock_kernel();
                break;
 
        /* let kernel drivers try to (re)bind to the interface */
@@ -1111,18 +1109,28 @@
 
        /* talk directly to the interface's driver */
        default:
+		lock_kernel(); /* against module unload */
                driver = ifp->driver;
-               if (driver == 0 || driver->ioctl == 0)
-                       retval = -ENOSYS;
-		else {
-			if (ifp->driver->owner)
+               if (driver == 0 || driver->ioctl == 0) {
+			unlock_kernel();
+			retval = -ENOSYS;
+		} else {
+			if (ifp->driver->owner) {
 				__MOD_INC_USE_COUNT(ifp->driver->owner);
+				unlock_kernel();
+			}
 			/* ifno might usefully be passed ... */
                        retval = driver->ioctl (ps->dev, ctrl.ioctl_code, buf);
 			/* size = min_t(int, size, retval)? */
-			if (ifp->driver->owner)
+			if (ifp->driver->owner) {
 				__MOD_DEC_USE_COUNT(ifp->driver->owner);
+			} else {
+				unlock_kernel();
+			}
 		}
+		
+		if (retval == -ENOIOCTLCMD)
+			retval = -ENOTTY;
 	}
 
 	/* cleanup and return */
@@ -1139,7 +1147,7 @@
 static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
 	struct dev_state *ps = (struct dev_state *)file->private_data;
-	int ret = -ENOIOCTLCMD;
+	int ret = -ENOTTY;
 
 	if (!(file->f_mode & FMODE_WRITE))
 		return -EPERM;
diff -Nru a/drivers/usb/core/drivers.c b/drivers/usb/core/drivers.c
--- a/drivers/usb/core/drivers.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/drivers.c	Fri Jul 19 11:04:14 2002
@@ -66,6 +66,7 @@
 	start = page;
 	end = page + (PAGE_SIZE - 100);
 	pos = *ppos;
+	lock_kernel(); /* else drivers might be unloaded */
 	for (; tmp != &usb_driver_list; tmp = tmp->next) {
 		struct usb_driver *driver = list_entry(tmp, struct usb_driver, driver_list);
 		int minor = driver->fops ? driver->minor : -1;
@@ -80,6 +81,7 @@
 			break;
 		}
 	}
+	unlock_kernel();
 	if (start == page)
 		start += sprintf(start, "(none)\n");
 	len = start - page;
diff -Nru a/drivers/usb/core/file.c b/drivers/usb/core/file.c
--- a/drivers/usb/core/file.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/file.c	Fri Jul 19 11:04:14 2002
@@ -44,10 +44,13 @@
 
 	spin_lock (&minor_lock);
 	c = usb_minors[minor];
-	spin_unlock (&minor_lock);
 
-	if (!c || !(new_fops = fops_get(c)))
+	if (!c || !(new_fops = fops_get(c))) {
+		spin_unlock(&minor_lock);
 		return err;
+	}
+	spin_unlock(&minor_lock);
+
 	old_fops = file->f_op;
 	file->f_op = new_fops;
 	/* Curiouser and curiouser... NULL ->open() as "no device" ? */
diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/hub.c	Fri Jul 19 11:04:14 2002
@@ -1046,8 +1046,6 @@
 
 static int usb_hub_thread(void *__hub)
 {
-	lock_kernel();
-
 	/*
 	 * This thread doesn't need any user-level access,
 	 * so get rid of all our resources
@@ -1067,8 +1065,6 @@
 	} while (!signal_pending(current));
 
 	dbg("usb_hub_thread exiting");
-
-	unlock_kernel();
 	complete_and_exit(&khubd_exited, 0);
 }
 
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/core/usb.c	Fri Jul 19 11:04:14 2002
@@ -32,6 +32,7 @@
 #include <linux/init.h>
 #include <linux/spinlock.h>
 #include <linux/errno.h>
+#include <linux/smp_lock.h>
 
 #ifdef CONFIG_USB_DEBUG
 	#define DEBUG
@@ -117,6 +118,108 @@
 	up (&usb_bus_list_lock);
 }
 
+/**
+ *	usb_unbind_driver - disconnects a driver from a device
+ *	@device: usb device to be disconnected
+ *	@intf: interface of the device to be disconnected
+ *	Context: BKL held
+ *
+ *	Handles module usage count correctly
+ */
+
+void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf)
+{
+	struct usb_driver *driver;
+	void *priv;
+	int m;
+	
+
+	driver = intf->driver;
+	priv = intf->private_data;
+	
+	if (!driver)
+		return;
+
+	/* as soon as we increase the module use count we drop the BKL
+	   before that we must not sleep */
+	if (driver->owner) {
+		m = try_inc_mod_count(driver->owner);
+		if (m == 0) {
+			err("Dieing driver still bound to device.\n");
+			return;
+		}
+		unlock_kernel();
+	}
+	down(&driver->serialize); 	/* if we sleep here on an umanaged driver
+					   the holder of the lock guards against
+					   module unload */
+
+	driver->disconnect(device, priv);
+
+	up(&driver->serialize);
+	if (driver->owner) {
+		lock_kernel();
+		__MOD_DEC_USE_COUNT(driver->owner);
+	}
+}
+
+/**
+ *	usb_bind_driver - connect a driver to a device's interface
+ *	@driver: device driver to be bound to a devices interface
+ *	@dev: device to be bound
+ *	@ifnum: index number of the interface to be used
+ *
+ *	Does a save binding of a driver to a device's interface
+ *	Returns a pointer to the drivers private description of the binding
+ */
+
+void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int ifnum)
+{
+	int i,m;
+	void *private = NULL;
+	const struct usb_device_id *id;
+	struct usb_interface *interface;
+
+	if (driver->owner) {
+		m = try_inc_mod_count(driver->owner);
+		if (m == 0)
+			return NULL; /* this horse is dead - don't ride*/
+		unlock_kernel();
+	}
+
+	interface = &dev->actconfig->interface[ifnum];
+
+	id = driver->id_table;
+	/* new style driver? */
+	if (id) {
+		for (i = 0; i < interface->num_altsetting; i++) {
+		  	interface->act_altsetting = i;
+			id = usb_match_id(dev, interface, id);
+			if (id) {
+				down(&driver->serialize);
+				private = driver->probe(dev,ifnum,id);
+				up(&driver->serialize);
+				if (private != NULL)
+					break;
+			}
+		}
+
+		/* if driver not bound, leave defaults unchanged */
+		if (private == NULL)
+			interface->act_altsetting = 0;
+	} else { /* "old style" driver */
+		down(&driver->serialize);
+		private = driver->probe(dev, ifnum, NULL);
+		up(&driver->serialize);
+	}
+	if (driver->owner) {
+		lock_kernel();
+		__MOD_DEC_USE_COUNT(driver->owner);
+	}
+
+	return private;
+}
+
 /*
  * This function is part of a depth-first search down the device tree,
  * removing any instances of a device driver.
@@ -136,18 +239,12 @@
 
 	if (!dev->actconfig)
 		return;
-			
+
 	for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
 		struct usb_interface *interface = &dev->actconfig->interface[i];
-		
+
 		if (interface->driver == driver) {
-			if (driver->owner)
-				__MOD_INC_USE_COUNT(driver->owner);
-			down(&driver->serialize);
-			driver->disconnect(dev, interface->private_data);
-			up(&driver->serialize);
-			if (driver->owner)
-				__MOD_DEC_USE_COUNT(driver->owner);
+			usb_unbind_driver(dev, interface);
 			/* if driver->disconnect didn't release the interface */
 			if (interface->driver)
 				usb_driver_release_interface(driver, interface);
@@ -163,7 +260,7 @@
 /**
  * usb_deregister - unregister a USB driver
  * @driver: USB operations of the driver to unregister
- * Context: !in_interrupt ()
+ * Context: !in_interrupt (), must be called with BKL held
  *
  * Unlinks the specified driver from the internal USB driver list.
  * 
@@ -528,9 +625,7 @@
 	struct list_head *tmp;
 	struct usb_interface *interface;
 	void *private;
-	const struct usb_device_id *id;
 	struct usb_driver *driver;
-	int i;
 	
 	if ((!dev) || (ifnum >= dev->actconfig->bNumInterfaces)) {
 		err("bad find_interface_driver params");
@@ -545,37 +640,12 @@
 		goto out_err;
 
 	private = NULL;
+	lock_kernel();
 	for (tmp = usb_driver_list.next; tmp != &usb_driver_list;) {
 		driver = list_entry(tmp, struct usb_driver, driver_list);
 		tmp = tmp->next;
 
-		if (driver->owner)
-			__MOD_INC_USE_COUNT(driver->owner);
-		id = driver->id_table;
-		/* new style driver? */
-		if (id) {
-			for (i = 0; i < interface->num_altsetting; i++) {
-			  	interface->act_altsetting = i;
-				id = usb_match_id(dev, interface, id);
-				if (id) {
-					down(&driver->serialize);
-					private = driver->probe(dev,ifnum,id);
-					up(&driver->serialize);
-					if (private != NULL)
-						break;
-				}
-			}
-
-			/* if driver not bound, leave defaults unchanged */
-			if (private == NULL)
-				interface->act_altsetting = 0;
-		} else { /* "old style" driver */
-			down(&driver->serialize);
-			private = driver->probe(dev, ifnum, NULL);
-			up(&driver->serialize);
-		}
-		if (driver->owner)
-			__MOD_DEC_USE_COUNT(driver->owner);
+		private = usb_bind_driver(driver, dev, ifnum);
 
 		/* probe() may have changed the config on us */
 		interface = dev->actconfig->interface + ifnum;
@@ -583,9 +653,11 @@
 		if (private) {
 			usb_driver_claim_interface(driver, interface, private);
 			up(&dev->serialize);
+			unlock_kernel();
 			return 0;
 		}
 	}
+	unlock_kernel();
 
 out_err:
 	up(&dev->serialize);
@@ -1121,27 +1193,22 @@
 
 	info("USB disconnect on device %d", dev->devnum);
 
+	lock_kernel();
 	if (dev->actconfig) {
 		for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
 			struct usb_interface *interface = &dev->actconfig->interface[i];
 			struct usb_driver *driver = interface->driver;
 			if (driver) {
-				if (driver->owner)
-					__MOD_INC_USE_COUNT(driver->owner);
-				down(&driver->serialize);
-				driver->disconnect(dev, interface->private_data);
-				up(&driver->serialize);
+				usb_unbind_driver(dev, interface);
 				/* if driver->disconnect didn't release the interface */
 				if (interface->driver)
 					usb_driver_release_interface(driver, interface);
-				/* we don't need the driver any longer */
-				if (driver->owner)
-					__MOD_DEC_USE_COUNT(driver->owner);
 			}
 			/* remove our device node for this interface */
 			put_device(&interface->dev);
 		}
 	}
+	unlock_kernel();
 
 	/* Free up all the children.. */
 	for (i = 0; i < USB_MAXCHILDREN; i++) {
@@ -1475,6 +1542,8 @@
 EXPORT_SYMBOL(usb_reset_device);
 EXPORT_SYMBOL(usb_connect);
 EXPORT_SYMBOL(usb_disconnect);
+EXPORT_SYMBOL(usb_bind_driver);
+EXPORT_SYMBOL(usb_unbind_driver);
 
 EXPORT_SYMBOL(__usb_get_extra_descriptor);
 
diff -Nru a/drivers/usb/media/pwc-if.c b/drivers/usb/media/pwc-if.c
--- a/drivers/usb/media/pwc-if.c	Fri Jul 19 11:04:14 2002
+++ b/drivers/usb/media/pwc-if.c	Fri Jul 19 11:04:14 2002
@@ -1756,40 +1756,40 @@
 	pdev = (struct pwc_device *)ptr;
 	if (pdev == NULL) {
 		Err("pwc_disconnect() Called without private pointer.\n");
-		return;
+		goto out_err;
 	}
 	if (pdev->udev == NULL) {
 		Err("pwc_disconnect() already called for %p\n", pdev);
-		return;
+		goto out_err;
 	}
 	if (pdev->udev != udev) {
 		Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n");
-		return;
+		goto out_err;
 	}
 #ifdef PWC_MAGIC	
 	if (pdev->magic != PWC_MAGIC) {
 		Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n");
-		return;
+		goto out_err;
 	}
-#endif	
-	
+#endif
+
 	pdev->unplugged = 1;
 	if (pdev->vdev != NULL) {
-		video_unregister_device(pdev->vdev); 
+		video_unregister_device(pdev->vdev);
 		if (pdev->vopen) {
 			Info("Disconnected while device/video is open!\n");
-			
+
 			/* Wake up any processes that might be waiting for
 			   a frame, let them return an error condition
 			 */
 			wake_up(&pdev->frameq);
-			
+
 			/* Wait until we get a 'go' from _close(). This used
 			   to have a gigantic race condition, since we kfree()
-			   stuff here, but we have to wait until close() 
-			   is finished. 
+			   stuff here, but we have to wait until close()
+			   is finished.
 			 */
-			   
+
 			Trace(TRACE_PROBE, "Sleeping on remove_ok.\n");
 			add_wait_queue(&pdev->remove_ok, &wait);
 			set_current_state(TASK_UNINTERRUPTIBLE);
@@ -1815,6 +1815,7 @@
 			device_hint[hint].pdev = NULL;
 
 	pdev->udev = NULL;
+out_err:
 	unlock_kernel();
 	kfree(pdev);
 }
diff -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h	Fri Jul 19 11:04:14 2002
+++ b/include/linux/usb.h	Fri Jul 19 11:04:14 2002
@@ -431,6 +431,10 @@
 /* for when layers above USB add new non-USB drivers */
 extern void usb_scan_devices(void);
 
+/* for probe/disconnect with correct module usage counting */
+void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int ifnum);
+void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf);
+
 /* mostly for devices emulating SCSI over USB */
 extern int usb_reset_device(struct usb_device *dev);
 
