usbfs: Add a new disconnect-and-claim ioctl (v2)
authorHans de Goede <hdegoede@redhat.com>
Sat, 8 Sep 2012 18:02:05 +0000 (20:02 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 10 Sep 2012 18:10:37 +0000 (11:10 -0700)
Apps which deal with devices which also have a kernel driver, need to do
the following:
1) Check which driver is attached, so as to not detach the wrong driver
   (ie detaching usbfs while another instance of the app is using the device)
2) Detach the kernel driver
3) Claim the interface

Where moving from one step to the next for both 1-2 and 2-3 consists of
a (small) race window. So currently such apps are racy and people just live
with it.

This patch adds a new ioctl which makes it possible for apps to do this
in a race free manner. For flexibility apps can choose to:
1) Specify the driver to disconnect
2) Specify to disconnect any driver except for the one named by the app
3) Disconnect any driver

Note that if there is no driver attached, the ioctl will just act like the
regular claim-interface ioctl, this is by design, as returning an error for
this condition would open a new bag of race-conditions.

Changes in v2:
-Fix indentation of if blocks where the condition spans multiple lines

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/devio.c
include/linux/usbdevice_fs.h

index ebb8a9de8b5fe448c98e0c25ec394e68c784d5b5..e0356cb859b5910eaaf5aa6e0b324808c56f4c0a 100644 (file)
@@ -1928,6 +1928,38 @@ static int proc_get_capabilities(struct dev_state *ps, void __user *arg)
        return 0;
 }
 
+static int proc_disconnect_claim(struct dev_state *ps, void __user *arg)
+{
+       struct usbdevfs_disconnect_claim dc;
+       struct usb_interface *intf;
+
+       if (copy_from_user(&dc, arg, sizeof(dc)))
+               return -EFAULT;
+
+       intf = usb_ifnum_to_if(ps->dev, dc.interface);
+       if (!intf)
+               return -EINVAL;
+
+       if (intf->dev.driver) {
+               struct usb_driver *driver = to_usb_driver(intf->dev.driver);
+
+               if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) &&
+                               strncmp(dc.driver, intf->dev.driver->name,
+                                       sizeof(dc.driver)) != 0)
+                       return -EBUSY;
+
+               if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER) &&
+                               strncmp(dc.driver, intf->dev.driver->name,
+                                       sizeof(dc.driver)) == 0)
+                       return -EBUSY;
+
+               dev_dbg(&intf->dev, "disconnect by usbfs\n");
+               usb_driver_release_interface(driver, intf);
+       }
+
+       return claimintf(ps, dc.interface);
+}
+
 /*
  * NOTE:  All requests here that have interface numbers as parameters
  * are assuming that somehow the configuration has been prevented from
@@ -2101,6 +2133,9 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
        case USBDEVFS_GET_CAPABILITIES:
                ret = proc_get_capabilities(ps, p);
                break;
+       case USBDEVFS_DISCONNECT_CLAIM:
+               ret = proc_disconnect_claim(ps, p);
+               break;
        }
        usb_unlock_device(dev);
        if (ret >= 0)
index 3b74666be027d438ed4631d2e872a1f606c2225a..4abe28e41cbcd90ce9297ac65bafbcc9add4c861 100644 (file)
@@ -131,6 +131,19 @@ struct usbdevfs_hub_portinfo {
 #define USBDEVFS_CAP_NO_PACKET_SIZE_LIM                0x04
 #define USBDEVFS_CAP_BULK_SCATTER_GATHER       0x08
 
+/* USBDEVFS_DISCONNECT_CLAIM flags & struct */
+
+/* disconnect-and-claim if the driver matches the driver field */
+#define USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER    0x01
+/* disconnect-and-claim except when the driver matches the driver field */
+#define USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER        0x02
+
+struct usbdevfs_disconnect_claim {
+       unsigned int interface;
+       unsigned int flags;
+       char driver[USBDEVFS_MAXDRIVERNAME + 1];
+};
+
 #ifdef __KERNEL__
 #ifdef CONFIG_COMPAT
 #include <linux/compat.h>
@@ -211,5 +224,6 @@ struct usbdevfs_ioctl32 {
 #define USBDEVFS_CLAIM_PORT        _IOR('U', 24, unsigned int)
 #define USBDEVFS_RELEASE_PORT      _IOR('U', 25, unsigned int)
 #define USBDEVFS_GET_CAPABILITIES  _IOR('U', 26, __u32)
+#define USBDEVFS_DISCONNECT_CLAIM  _IOR('U', 27, struct usbdevfs_disconnect_claim)
 
 #endif /* _LINUX_USBDEVICE_FS_H */