patch-2.4.0-test5 linux/drivers/usb/usb.c
Next file: linux/drivers/video/S3triofb.c
Previous file: linux/drivers/usb/usb-ohci.c
Back to the patch index
Back to the overall index
- Lines: 306
- Date:
Wed Jul 26 16:05:44 2000
- Orig file:
v2.4.0-test4/linux/drivers/usb/usb.c
- Orig date:
Fri Jul 14 12:12:14 2000
diff -u --recursive --new-file v2.4.0-test4/linux/drivers/usb/usb.c linux/drivers/usb/usb.c
@@ -25,6 +25,21 @@
#include <linux/bitops.h>
#include <linux/malloc.h>
#include <linux/interrupt.h> /* for in_interrupt() */
+
+
+#if defined(CONFIG_KMOD) && defined(CONFIG_HOTPLUG)
+#include <linux/kmod.h>
+#include <linux/sched.h>
+#include <asm/uaccess.h>
+
+#define __KERNEL_SYSCALLS__
+#include <linux/unistd.h>
+
+/* waitpid() call glue uses this */
+static int errno;
+#endif
+
+
#ifdef CONFIG_USB_DEBUG
#define DEBUG
#else
@@ -428,7 +443,7 @@
void usb_driver_release_interface(struct usb_driver *driver, struct usb_interface *iface)
{
/* this should never happen, don't release something that's not ours */
- if (iface->driver != driver || !iface)
+ if (!iface || iface->driver != driver)
return;
iface->driver = NULL;
@@ -481,6 +496,222 @@
return -1;
}
+
+#if defined(CONFIG_KMOD) && defined(CONFIG_HOTPLUG)
+
+/*
+ * USB hotplugging invokes what /proc/sys/kernel/hotplug says
+ * (normally /sbin/hotplug) when USB devices get added or removed.
+ */
+
+static int exec_helper (void *arg)
+{
+ void **params = (void **) arg;
+ char *path = (char *) params [0];
+ char **argv = (char **) params [1];
+ char **envp = (char **) params [2];
+ return exec_usermodehelper (path, argv, envp);
+}
+
+int call_usermodehelper (char *path, char **argv, char **envp)
+{
+ void *params [3] = { path, argv, envp };
+ int pid, pid2, retval;
+ mm_segment_t fs;
+
+ if ((pid = kernel_thread (exec_helper, (void *) params, 0)) < 0) {
+ err ("failed fork of %s, errno = %d", argv [0], -pid);
+ return -1;
+ }
+
+ /* set signal mask? */
+ fs = get_fs ();
+ set_fs (KERNEL_DS); /* retval is in kernel space. */
+ pid2 = waitpid (pid, &retval, __WCLONE); /* "errno" gets assigned */
+ set_fs (fs);
+ /* restore signal mask? */
+
+ if (pid2 != pid) {
+ err ("waitpid(%d) failed, returned %d\n", pid, pid2);
+ return -1;
+ }
+ return retval;
+}
+
+static int to_bcd (char *buf, __u16 *bcdValue)
+{
+ int retval = 0;
+ char *value = (char *) bcdValue;
+ int temp;
+
+ /* digits are 0-9 then ":;<=>?" for devices using
+ * non-bcd (non-standard!) values here ... */
+
+ /* No leading (or later, trailing) zeroes since scripts do
+ * literal matches, and that's how they're doing them. */
+ if ((temp = value [1] & 0xf0) != 0) {
+ temp >>= 4;
+ temp += '0';
+ *buf++ = (char) temp;
+ retval++;
+ }
+
+ temp = value [1] & 0x0f;
+ temp += '0';
+ *buf++ = (char) temp;
+ retval++;
+
+ *buf++ = '.';
+ retval++;
+
+ temp = value [0] & 0xf0;
+ temp >>= 4;
+ temp += '0';
+ *buf++ = (char) temp;
+ retval++;
+
+ if ((temp = value [0] & 0x0f) != 0) {
+ temp += '0';
+ *buf++ = (char) temp;
+ retval++;
+ }
+ *buf++ = 0;
+
+ return retval;
+}
+
+/*
+ * This invokes a user mode policy agent, typically helping to load driver
+ * or other modules, configure the device, or both.
+ *
+ * Some synchronization is important: removes can't start processing
+ * before the add-device processing completes, and vice versa. That keeps
+ * a stack of USB-related identifiers stable while they're in use. If we
+ * know that agents won't complete after they return (such as by forking
+ * a process that completes later), it's enough to just waitpid() for the
+ * agent -- as is currently done.
+ *
+ * The reason: we know we're called either from khubd (the typical case)
+ * or from root hub initialization (init, kapmd, modprobe, etc). In both
+ * cases, we know no other thread can recycle our address, since we must
+ * already have been serialized enough to prevent that.
+ */
+static void call_policy (char *verb, struct usb_device *dev)
+{
+ char *argv [3], **envp, *buf, *scratch;
+ int i = 0, value;
+
+ if (!hotplug_path [0])
+ return;
+ if (in_interrupt ()) {
+ dbg ("In_interrupt");
+ return;
+ }
+ if (!current->fs->root) {
+ /* statically linked USB is initted rather early */
+ dbg ("call_policy %s, num %d -- no FS yet", verb, dev->devnum);
+ return;
+ }
+ if (dev->devnum < 0) {
+ dbg ("device already deleted ??");
+ return;
+ }
+ if (!(envp = (char **) kmalloc (20 * sizeof (char *), GFP_KERNEL))) {
+ dbg ("enomem");
+ return;
+ }
+ if (!(buf = kmalloc (256, GFP_KERNEL))) {
+ kfree (envp);
+ dbg ("enomem2");
+ return;
+ }
+
+ /* only one standardized param to hotplug command: type */
+ argv [0] = hotplug_path;
+ argv [1] = "usb";
+ argv [2] = 0;
+
+ /* minimal command environment */
+ envp [i++] = "HOME=/";
+ envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+
+#ifdef DEBUG
+ /* hint that policy agent should enter no-stdout debug mode */
+ envp [i++] = "DEBUG=kernel";
+#endif
+ /* extensible set of named bus-specific parameters,
+ * supporting multiple driver selection algorithms.
+ */
+ scratch = buf;
+
+ /* action: add, remove */
+ envp [i++] = scratch;
+ scratch += sprintf (scratch, "ACTION=%s", verb) + 1;
+
+#ifdef CONFIG_USB_DEVICEFS
+ /* If this is available, userspace programs can directly read
+ * all the device descriptors we don't tell them about. Or
+ * even act as usermode drivers.
+ *
+ * XXX how little intelligence can we hardwire?
+ * (a) mount point: /devfs, /dev, /proc/bus/usb etc.
+ * (b) naming convention: bus1/device3, 001/003 etc.
+ */
+ envp [i++] = "DEVFS=/proc/bus/usb";
+ envp [i++] = scratch;
+ scratch += sprintf (scratch, "DEVICE=/proc/bus/usb/%03d/%03d",
+ dev->bus->busnum, dev->devnum) + 1;
+#endif
+
+ /* per-device configuration hacks are often necessary */
+ envp [i++] = scratch;
+ scratch += sprintf (scratch, "PRODUCT=%x/%x/",
+ dev->descriptor.idVendor,
+ dev->descriptor.idProduct);
+ scratch += to_bcd (scratch, &dev->descriptor.bcdDevice) + 1;
+
+ /* otherwise, use a simple (so far) generic driver binding model */
+ envp [i++] = scratch;
+ if (dev->descriptor.bDeviceClass == 0) {
+ int alt = dev->actconfig->interface [0].act_altsetting;
+
+ /* simple/common case: one config, one interface, one driver
+ * unsimple cases: everything else
+ */
+ scratch += sprintf (scratch, "INTERFACE=%d/%d/%d",
+ dev->actconfig->interface [0].altsetting [alt].bInterfaceClass,
+ dev->actconfig->interface [0].altsetting [alt].bInterfaceSubClass,
+ dev->actconfig->interface [0].altsetting [alt].bInterfaceProtocol)
+ + 1;
+ /* INTERFACE-0, INTERFACE-1, ... ? */
+ } else {
+ /* simple/common case: generic device, handled generically */
+ scratch += sprintf (scratch, "TYPE=%d/%d/%d",
+ dev->descriptor.bDeviceClass,
+ dev->descriptor.bDeviceSubClass,
+ dev->descriptor.bDeviceProtocol) + 1;
+ }
+ envp [i++] = 0;
+ /* assert: (scratch - buf) < sizeof buf */
+
+ /* NOTE: user mode daemons can call the agents too */
+
+ dbg ("kusbd: %s %s %d", argv [0], verb, dev->devnum);
+ value = call_usermodehelper (argv [0], argv, envp);
+ kfree (buf);
+ kfree (envp);
+ dbg ("kusbd policy returned 0x%x", value);
+}
+
+#else
+
+static inline void
+call_policy (char *verb, struct usb_device *dev)
+{ }
+
+#endif /* KMOD && HOTPLUG */
+
+
/*
* This entrypoint gets called for each new device.
*
@@ -506,7 +737,10 @@
dbg("unhandled interfaces on device");
if (!claimed) {
- warn("This device is not recognized by any installed USB driver.");
+ warn("USB device %d (prod/vend 0x%x/0x%x) is not claimed by any active driver.",
+ dev->devnum,
+ dev->descriptor.idVendor,
+ dev->descriptor.idProduct);
#ifdef DEBUG
usb_show_device(dev);
#endif
@@ -1195,6 +1429,8 @@
info("USB disconnect on device %d", dev->devnum);
+ call_policy ("remove", dev);
+
if (dev->actconfig) {
for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
struct usb_interface *interface = &dev->actconfig->interface[i];
@@ -1712,6 +1948,9 @@
/* find drivers willing to handle this device */
usb_find_drivers(dev);
+ /* userspace may load modules and/or configure further */
+ call_policy ("add", dev);
+
return 0;
}
@@ -1720,12 +1959,17 @@
int minor = MINOR(inode->i_rdev);
struct usb_driver *c = usb_minors[minor/16];
int err = -ENODEV;
- struct file_operations *old_fops;
+ struct file_operations *old_fops, *new_fops = NULL;
- if (!c || !c->fops)
+ /*
+ * No load-on-demand? Randy, could you ACK that it's really not
+ * supposed to be done? -- AV
+ */
+ if (!c || !(new_fops = fops_get(c->fops)))
return err;
old_fops = file->f_op;
- file->f_op = fops_get(c->fops);
+ file->f_op = new_fops;
+ /* Curiouser and curiouser... NULL ->open() as "no device" ? */
if (file->f_op->open)
err = file->f_op->open(inode,file);
if (err) {
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)