USB: add API for userspace drivers to "claim" ports
authorAlan Stern <stern@rowland.harvard.edu>
Mon, 29 Jun 2009 14:56:54 +0000 (10:56 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:22 +0000 (06:46 -0700)
This patch (as1258) implements a feature that users have been asking
for: It gives programs the ability to "claim" a port on a hub, via a
new usbfs ioctl.  A device plugged into a "claimed" port will not be
touched by the kernel beyond the immediate necessities of
initialization and enumeration.

In particular, when a device is plugged into a "claimed" port, the
kernel will not select and install a configuration.  And when a config
is installed by usbfs or sysfs, the kernel will not probe any drivers
for any of the interfaces.  (However the kernel will fetch various
string descriptors during enumeration.  One could argue that this
isn't really necessary, but the strings are exported in sysfs.)

The patch does not guarantee exclusive access to these devices; it is
still possible for more than one program to open the device file
concurrently.  Programs are responsible for coordinating access among
themselves.

A demonstration program showing how to use the new interface can be
found in an attachment to

http://marc.info/?l=linux-usb&m=124345857431452&w=2

The patch also makes a small simplification to the hub driver,
replacing a bunch of more-or-less useless variants of "out of memory"
with a single message.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/devio.c
drivers/usb/core/driver.c
drivers/usb/core/generic.c
drivers/usb/core/hub.c
drivers/usb/core/usb.h
include/linux/usbdevice_fs.h

index 4247ecc..165de5d 100644 (file)
@@ -52,6 +52,7 @@
 
 #include "hcd.h"       /* for usbcore internals */
 #include "usb.h"
+#include "hub.h"
 
 #define USB_MAXBUS                     64
 #define USB_DEVICE_MAX                 USB_MAXBUS * 128
@@ -655,6 +656,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
        struct async *as;
 
        usb_lock_device(dev);
+       usb_hub_release_all_ports(dev, ps);
 
        /* Protect against simultaneous open */
        mutex_lock(&usbfs_mutex);
@@ -1548,6 +1550,29 @@ static int proc_ioctl_compat(struct dev_state *ps, compat_uptr_t arg)
 }
 #endif
 
+static int proc_claim_port(struct dev_state *ps, void __user *arg)
+{
+       unsigned portnum;
+       int rc;
+
+       if (get_user(portnum, (unsigned __user *) arg))
+               return -EFAULT;
+       rc = usb_hub_claim_port(ps->dev, portnum, ps);
+       if (rc == 0)
+               snoop(&ps->dev->dev, "port %d claimed by process %d: %s\n",
+                       portnum, task_pid_nr(current), current->comm);
+       return rc;
+}
+
+static int proc_release_port(struct dev_state *ps, void __user *arg)
+{
+       unsigned portnum;
+
+       if (get_user(portnum, (unsigned __user *) arg))
+               return -EFAULT;
+       return usb_hub_release_port(ps->dev, portnum, ps);
+}
+
 /*
  * NOTE:  All requests here that have interface numbers as parameters
  * are assuming that somehow the configuration has been prevented from
@@ -1689,6 +1714,16 @@ static int usbdev_ioctl(struct inode *inode, struct file *file,
                snoop(&dev->dev, "%s: IOCTL\n", __func__);
                ret = proc_ioctl_default(ps, p);
                break;
+
+       case USBDEVFS_CLAIM_PORT:
+               snoop(&dev->dev, "%s: CLAIM_PORT\n", __func__);
+               ret = proc_claim_port(ps, p);
+               break;
+
+       case USBDEVFS_RELEASE_PORT:
+               snoop(&dev->dev, "%s: RELEASE_PORT\n", __func__);
+               ret = proc_release_port(ps, p);
+               break;
        }
        usb_unlock_device(dev);
        if (ret >= 0)
index 69e5773..1bad4e5 100644 (file)
@@ -207,6 +207,9 @@ static int usb_probe_interface(struct device *dev)
 
        intf->needs_binding = 0;
 
+       if (usb_device_is_owned(udev))
+               return -ENODEV;
+
        if (udev->authorized == 0) {
                dev_err(&intf->dev, "Device is not authorized for usage\n");
                return -ENODEV;
index 30ecac3..05e6d31 100644 (file)
@@ -158,7 +158,9 @@ static int generic_probe(struct usb_device *udev)
        /* Choose and set the configuration.  This registers the interfaces
         * with the driver core and lets interface drivers bind to them.
         */
-       if (udev->authorized == 0)
+       if (usb_device_is_owned(udev))
+               ;               /* Don't configure if the device is owned */
+       else if (udev->authorized == 0)
                dev_err(&udev->dev, "Device is not authorized for usage\n");
        else {
                c = usb_choose_configuration(udev);
index 71f86c6..cb54420 100644 (file)
@@ -78,6 +78,7 @@ struct usb_hub {
        u8                      indicator[USB_MAXCHILDREN];
        struct delayed_work     leds;
        struct delayed_work     init_work;
+       void                    **port_owners;
 };
 
 
@@ -860,19 +861,17 @@ static int hub_configure(struct usb_hub *hub,
        u16 wHubCharacteristics;
        unsigned int pipe;
        int maxp, ret;
-       char *message;
+       char *message = "out of memory";
 
        hub->buffer = usb_buffer_alloc(hdev, sizeof(*hub->buffer), GFP_KERNEL,
                        &hub->buffer_dma);
        if (!hub->buffer) {
-               message = "can't allocate hub irq buffer";
                ret = -ENOMEM;
                goto fail;
        }
 
        hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL);
        if (!hub->status) {
-               message = "can't kmalloc hub status buffer";
                ret = -ENOMEM;
                goto fail;
        }
@@ -880,7 +879,6 @@ static int hub_configure(struct usb_hub *hub,
 
        hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL);
        if (!hub->descriptor) {
-               message = "can't kmalloc hub descriptor";
                ret = -ENOMEM;
                goto fail;
        }
@@ -904,6 +902,12 @@ static int hub_configure(struct usb_hub *hub,
        dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild,
                (hdev->maxchild == 1) ? "" : "s");
 
+       hub->port_owners = kzalloc(hdev->maxchild * sizeof(void *), GFP_KERNEL);
+       if (!hub->port_owners) {
+               ret = -ENOMEM;
+               goto fail;
+       }
+
        wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
 
        if (wHubCharacteristics & HUB_CHAR_COMPOUND) {
@@ -1082,7 +1086,6 @@ static int hub_configure(struct usb_hub *hub,
 
        hub->urb = usb_alloc_urb(0, GFP_KERNEL);
        if (!hub->urb) {
-               message = "couldn't allocate interrupt urb";
                ret = -ENOMEM;
                goto fail;
        }
@@ -1131,11 +1134,13 @@ static void hub_disconnect(struct usb_interface *intf)
        hub_quiesce(hub, HUB_DISCONNECT);
 
        usb_set_intfdata (intf, NULL);
+       hub->hdev->maxchild = 0;
 
        if (hub->hdev->speed == USB_SPEED_HIGH)
                highspeed_hubs--;
 
        usb_free_urb(hub->urb);
+       kfree(hub->port_owners);
        kfree(hub->descriptor);
        kfree(hub->status);
        usb_buffer_free(hub->hdev, sizeof(*hub->buffer), hub->buffer,
@@ -1250,6 +1255,79 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data)
        }
 }
 
+/*
+ * Allow user programs to claim ports on a hub.  When a device is attached
+ * to one of these "claimed" ports, the program will "own" the device.
+ */
+static int find_port_owner(struct usb_device *hdev, unsigned port1,
+               void ***ppowner)
+{
+       if (hdev->state == USB_STATE_NOTATTACHED)
+               return -ENODEV;
+       if (port1 == 0 || port1 > hdev->maxchild)
+               return -EINVAL;
+
+       /* This assumes that devices not managed by the hub driver
+        * will always have maxchild equal to 0.
+        */
+       *ppowner = &(hdev_to_hub(hdev)->port_owners[port1 - 1]);
+       return 0;
+}
+
+/* In the following three functions, the caller must hold hdev's lock */
+int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, void *owner)
+{
+       int rc;
+       void **powner;
+
+       rc = find_port_owner(hdev, port1, &powner);
+       if (rc)
+               return rc;
+       if (*powner)
+               return -EBUSY;
+       *powner = owner;
+       return rc;
+}
+
+int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void *owner)
+{
+       int rc;
+       void **powner;
+
+       rc = find_port_owner(hdev, port1, &powner);
+       if (rc)
+               return rc;
+       if (*powner != owner)
+               return -ENOENT;
+       *powner = NULL;
+       return rc;
+}
+
+void usb_hub_release_all_ports(struct usb_device *hdev, void *owner)
+{
+       int n;
+       void **powner;
+
+       n = find_port_owner(hdev, 1, &powner);
+       if (n == 0) {
+               for (; n < hdev->maxchild; (++n, ++powner)) {
+                       if (*powner == owner)
+                               *powner = NULL;
+               }
+       }
+}
+
+/* The caller must hold udev's lock */
+bool usb_device_is_owned(struct usb_device *udev)
+{
+       struct usb_hub *hub;
+
+       if (udev->state == USB_STATE_NOTATTACHED || !udev->parent)
+               return false;
+       hub = hdev_to_hub(udev->parent);
+       return !!hub->port_owners[udev->portnum - 1];
+}
+
 
 static void recursively_mark_NOTATTACHED(struct usb_device *udev)
 {
index c0e0ae2..9a8b15e 100644 (file)
@@ -37,6 +37,13 @@ extern int usb_match_device(struct usb_device *dev,
 extern void usb_forced_unbind_intf(struct usb_interface *intf);
 extern void usb_rebind_intf(struct usb_interface *intf);
 
+extern int usb_hub_claim_port(struct usb_device *hdev, unsigned port,
+               void *owner);
+extern int usb_hub_release_port(struct usb_device *hdev, unsigned port,
+               void *owner);
+extern void usb_hub_release_all_ports(struct usb_device *hdev, void *owner);
+extern bool usb_device_is_owned(struct usb_device *udev);
+
 extern int  usb_hub_init(void);
 extern void usb_hub_cleanup(void);
 extern int usb_major_init(void);
index 0044d9b..00ceebe 100644 (file)
@@ -175,4 +175,6 @@ struct usbdevfs_ioctl32 {
 #define USBDEVFS_CLEAR_HALT        _IOR('U', 21, unsigned int)
 #define USBDEVFS_DISCONNECT        _IO('U', 22)
 #define USBDEVFS_CONNECT           _IO('U', 23)
+#define USBDEVFS_CLAIM_PORT        _IOR('U', 24, unsigned int)
+#define USBDEVFS_RELEASE_PORT      _IOR('U', 25, unsigned int)
 #endif /* _LINUX_USBDEVICE_FS_H */