# 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.560   -> 1.561  
#	drivers/usb/Config.help	1.11    -> 1.12   
#	 include/linux/usb.h	1.29    -> 1.30   
#	drivers/usb/core/usb.c	1.46    -> 1.47   
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/04/25	greg@kroah.com	1.561
# added CONFIG_USB_DYNAMIC_MINORS support to the USB core
# 
# Here's a patch that finishes off my previous patch that enabled us to
# use less than 16 minor numbers per USB device that uses the USB major
# number.  This patch allows all such devices to share all 256 minor
# numbers at once, much like the usbserial core shares the USB serial
# major with all usb-serial drivers.  This also solves Oliver's problem of
# having 30 printers :)
# --------------------------------------------
#
diff -Nru a/drivers/usb/Config.help b/drivers/usb/Config.help
--- a/drivers/usb/Config.help	Thu Apr 25 16:26:56 2002
+++ b/drivers/usb/Config.help	Thu Apr 25 16:26:56 2002
@@ -69,3 +69,11 @@
   If you say N here, these conditions will cause warning messages
   about USB bandwidth usage to be logged and some devices or
   drivers may not work correctly.
+
+CONFIG_USB_DYNAMIC_MINORS
+  If you say Y here, the USB subsystem will use dynamic minor
+  allocation for any device that uses the USB major number.
+  This means that you can have more than 16 of a single type
+  of device (like USB printers).
+
+  If you are unsure about this, say N here.
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c	Thu Apr 25 16:26:56 2002
+++ b/drivers/usb/core/usb.c	Thu Apr 25 16:26:57 2002
@@ -11,6 +11,7 @@
  	more docs, etc)
  * (C) Copyright Yggdrasil Computing, Inc. 2000
  *     (usb_device_id matching changes by Adam J. Richter)
+ * (C) Copyright Greg Kroah-Hartman 2002
  *
  * NOTE! This is not actually a driver at all, rather this is
  * just a collection of helper routines that implement the
@@ -59,7 +60,47 @@
 
 devfs_handle_t usb_devfs_handle;	/* /dev/usb dir. */
 
-static struct usb_driver *usb_minors[256];
+#define MAX_USB_MINORS	256
+static struct usb_driver *usb_minors[MAX_USB_MINORS];
+static spinlock_t minor_lock = SPIN_LOCK_UNLOCKED;
+
+static int usb_register_minors (struct usb_driver *driver, int num_minors, int start_minor)
+{
+	int i;
+
+	dbg("registering %d minors, starting at %d", num_minors, start_minor);
+
+	if (start_minor + num_minors >= MAX_USB_MINORS)
+		return -EINVAL;
+
+	spin_lock (&minor_lock);
+	for (i = start_minor; i < (start_minor + num_minors); ++i)
+		if (usb_minors[i]) {
+			spin_unlock (&minor_lock);
+			err("minor %d is already in use, error registering %s driver",
+			    i, driver->name);
+			return -EINVAL;
+		}
+		
+	for (i = start_minor; i < (start_minor + num_minors); ++i)
+		usb_minors[i] = driver;
+
+	spin_unlock (&minor_lock);
+	return 0;
+}
+
+static void usb_deregister_minors (struct usb_driver *driver, int num_minors, int start_minor)
+{
+	int i;
+
+	dbg ("%s is removing %d minors starting at %d", driver->name,
+	     num_minors, start_minor);
+
+	spin_lock (&minor_lock);
+	for (i = start_minor; i < (start_minor + num_minors); ++i)
+		usb_minors[i] = NULL;
+	spin_unlock (&minor_lock);
+}
 
 /**
  *	usb_register - register a USB driver
@@ -72,18 +113,15 @@
  */
 int usb_register(struct usb_driver *new_driver)
 {
-	int i;
-
+	int retval = 0;
+	
+#ifndef CONFIG_USB_DYNAMIC_MINORS
 	if (new_driver->fops != NULL) {
-		for (i = new_driver->minor; i < new_driver->minor + new_driver->num_minors; ++i) {
-			if (usb_minors[i]) {
-				err("error registering %s driver", new_driver->name);
-				return -EINVAL;
-			}
-		}
-		for (i = new_driver->minor; i < new_driver->minor + new_driver->num_minors; ++i)
-			usb_minors[i] = new_driver;
+		retval = usb_register_minors (new_driver, new_driver->num_minors, new_driver->minor);
+		if (retval)
+			return retval;
 	}
+#endif
 
 	info("registered new driver %s", new_driver->name);
 
@@ -96,9 +134,94 @@
 
 	usbfs_update_special();
 
-	return 0;
+	return retval;
 }
 
+
+/**
+ * usb_register_dev - register a USB device, and ask for a minor number
+ * @new_driver: USB operations for the driver
+ * @num_minors: number of minor numbers requested for this device
+ * @start_minor: place to put the new starting minor number
+ *
+ * Used to ask the USB core for a new minor number for a device that has
+ * just showed up.  This is used to dynamically allocate minor numbers
+ * from the pool of USB reserved minor numbers.
+ *
+ * This should be called by all drivers that use the USB major number.
+ * This only returns a good value of CONFIG_USB_DYNAMIC_MINORS is
+ * selected by the user.
+ *
+ * usb_deregister_dev() should be called when the driver is done with
+ * the minor numbers given out by this function.
+ *
+ * Returns a negative error code on failure and 0 on success, alone with
+ * a value that the driver should use in start_minor.
+ */
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+int usb_register_dev (struct usb_driver *new_driver, int num_minors, int *start_minor)
+{
+	int i;
+	int j;
+	int good_spot;
+	int retval = -EINVAL;
+
+	dbg ("%s is asking for %d minors", new_driver->name, num_minors);
+
+	if (new_driver->fops == NULL)
+		goto exit;
+
+	*start_minor = 0; 
+	spin_lock (&minor_lock);
+	for (i = 0; i < MAX_USB_MINORS; ++i) {
+		if (usb_minors[i])
+			continue;
+
+		good_spot = 1;
+		for (j = 1; j <= num_minors-1; ++j)
+			if (usb_minors[i+j]) {
+				good_spot = 0;
+				break;
+			}
+		if (good_spot == 0)
+			continue;
+
+		*start_minor = i;
+		spin_unlock (&minor_lock);
+		retval = usb_register_minors (new_driver, num_minors, *start_minor);
+		if (retval) {
+			/* someone snuck in here, so let's start looking all over again */
+			spin_lock (&minor_lock);
+			i = 0;
+			continue;
+		}
+		goto exit;
+	}
+	spin_unlock (&minor_lock);
+exit:
+	return retval;
+}
+
+/**
+ * usb_deregister_dev - deregister a USB device's dynamic minor.
+ * @driver: USB operations for the driver
+ * @num_minors: number of minor numbers to put back.
+ * @start_minor: the starting minor number
+ *
+ * Used in conjunction with usb_register_dev().  This function is called
+ * when the USB driver is finished with the minor numbers gotten from a
+ * call to usb_register_dev() (usually when the device is disconnected
+ * from the system.)
+ * 
+ * This should be called by all drivers that use the USB major number.
+ */
+void usb_deregister_dev (struct usb_driver *driver, int num_minors, int start_minor)
+{
+	usb_deregister_minors (driver, num_minors, start_minor);
+}
+#endif	/* CONFIG_USB_DYNAMIC_MINORS */
+
+
 /**
  *	usb_scan_devices - scans all unclaimed USB interfaces
  *	Context: !in_interrupt ()
@@ -177,12 +300,13 @@
 void usb_deregister(struct usb_driver *driver)
 {
 	struct list_head *tmp;
-	int i;
 
 	info("deregistering driver %s", driver->name);
+
+#ifndef CONFIG_USB_DYNAMIC_MINORS
 	if (driver->fops != NULL)
-		for (i = driver->minor; i < driver->minor + driver->num_minors; ++i)
-			usb_minors[i] = NULL;
+		usb_deregister_minors (driver, driver->num_minors, driver->minor);
+#endif
 
 	/*
 	 * first we remove the driver, to be sure it doesn't get used by
@@ -2524,14 +2648,14 @@
 static int usb_open(struct inode * inode, struct file * file)
 {
 	int minor = minor(inode->i_rdev);
-	struct usb_driver *c = usb_minors[minor];
+	struct usb_driver *c;
 	int err = -ENODEV;
 	struct file_operations *old_fops, *new_fops = NULL;
 
-	/*
-	 * No load-on-demand? Randy, could you ACK that it's really not
-	 * supposed to be done?					-- AV
-	 */
+	spin_lock (&minor_lock);
+	c = usb_minors[minor];
+	spin_unlock (&minor_lock);
+
 	if (!c || !(new_fops = fops_get(c->fops)))
 		return err;
 	old_fops = file->f_op;
@@ -2622,6 +2746,11 @@
 EXPORT_SYMBOL(usb_register);
 EXPORT_SYMBOL(usb_deregister);
 EXPORT_SYMBOL(usb_scan_devices);
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+EXPORT_SYMBOL(usb_register_dev);
+EXPORT_SYMBOL(usb_deregister_dev);
+#endif
 
 EXPORT_SYMBOL(usb_alloc_dev);
 EXPORT_SYMBOL(usb_free_dev);
diff -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h	Thu Apr 25 16:26:56 2002
+++ b/include/linux/usb.h	Thu Apr 25 16:26:56 2002
@@ -2,6 +2,7 @@
 #define __LINUX_USB_H
 
 #include <linux/device.h>
+#include <linux/errno.h>
 
 /* USB constants */
 
@@ -775,6 +776,14 @@
  */
 extern int usb_register(struct usb_driver *);
 extern void usb_deregister(struct usb_driver *);
+
+#ifndef CONFIG_USB_DYNAMIC_MINORS
+static inline int usb_register_dev(struct usb_driver *new_driver, int num_minors, int *start_minor) { return -ENODEV; }
+static inline void usb_deregister_dev(struct usb_driver *driver, int num_minors, int start_minor) {}
+#else
+extern int usb_register_dev(struct usb_driver *new_driver, int num_minors, int *start_minor);
+extern void usb_deregister_dev(struct usb_driver *driver, int num_minors, int start_minor);
+#endif
 
 /* -------------------------------------------------------------------------- */
 
