USB: add a "remove hardware" sysfs attribute
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 27 Oct 2009 19:20:13 +0000 (15:20 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 11 Dec 2009 19:55:18 +0000 (11:55 -0800)
This patch (as1297) adds a "remove" attribute to each USB device's
directory in sysfs.  Writing to this attribute causes the device to be
deconfigured (the same as writing 0 to the "bConfigurationValue"
attribute) and then tells the hub driver to disable the device's
upstream port.  The device remains locked during these activities so
there is no possibility of it getting reconfigured in between.  The
port will remain disabled until after the device is unplugged.

The purpose of this is to provide a means for user programs to imitate
the "Safely remove hardware" applet in Windows.  Some devices do
expect their ports to be disabled before they are unplugged, and they
provide visual feedback to users indicating when they can safely be
unplugged.

The security implications are minimal.  Writing to the "remove"
attribute is no more dangerous than writing to the
"bConfigurationValue" attribute.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: David Zeuthen <davidz@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/hub.c
drivers/usb/core/sysfs.c
drivers/usb/core/usb.h

index 708c638..5413d71 100644 (file)
@@ -60,6 +60,8 @@ struct usb_hub {
                                                        status change */
        unsigned long           busy_bits[1];   /* ports being reset or
                                                        resumed */
+       unsigned long           removed_bits[1]; /* ports with a "removed"
+                                                       device present */
 #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
 #error event_bits[] is too short!
 #endif
@@ -635,6 +637,33 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
        kick_khubd(hub);
 }
 
+/**
+ * usb_remove_device - disable a device's port on its parent hub
+ * @udev: device to be disabled and removed
+ * Context: @udev locked, must be able to sleep.
+ *
+ * After @udev's port has been disabled, khubd is notified and it will
+ * see that the device has been disconnected.  When the device is
+ * physically unplugged and something is plugged in, the events will
+ * be received and processed normally.
+ */
+int usb_remove_device(struct usb_device *udev)
+{
+       struct usb_hub *hub;
+       struct usb_interface *intf;
+
+       if (!udev->parent)      /* Can't remove a root hub */
+               return -EINVAL;
+       hub = hdev_to_hub(udev->parent);
+       intf = to_usb_interface(hub->intfdev);
+
+       usb_autopm_get_interface(intf);
+       set_bit(udev->portnum, hub->removed_bits);
+       hub_port_logical_disconnect(hub, udev->portnum);
+       usb_autopm_put_interface(intf);
+       return 0;
+}
+
 enum hub_activation_type {
        HUB_INIT, HUB_INIT2, HUB_INIT3,
        HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
@@ -730,6 +759,13 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                                        USB_PORT_FEAT_C_ENABLE);
                }
 
+               /* We can forget about a "removed" device when there's a
+                * physical disconnect or the connect status changes.
+                */
+               if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
+                               (portchange & USB_PORT_STAT_C_CONNECTION))
+                       clear_bit(port1, hub->removed_bits);
+
                if (!udev || udev->state == USB_STATE_NOTATTACHED) {
                        /* Tell khubd to disconnect the device or
                         * check for a new connection
@@ -2965,6 +3001,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
                usb_disconnect(&hdev->children[port1-1]);
        clear_bit(port1, hub->change_bits);
 
+       /* We can forget about a "removed" device when there's a physical
+        * disconnect or the connect status changes.
+        */
+       if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
+                       (portchange & USB_PORT_STAT_C_CONNECTION))
+               clear_bit(port1, hub->removed_bits);
+
        if (portchange & (USB_PORT_STAT_C_CONNECTION |
                                USB_PORT_STAT_C_ENABLE)) {
                status = hub_port_debounce(hub, port1);
@@ -2978,8 +3021,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
                }
        }
 
-       /* Return now if debouncing failed or nothing is connected */
-       if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
+       /* Return now if debouncing failed or nothing is connected or
+        * the device was "removed".
+        */
+       if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
+                       test_bit(port1, hub->removed_bits)) {
 
                /* maybe switch power back on (e.g. root hub was reset) */
                if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
index 7ec3041..470e241 100644 (file)
@@ -508,6 +508,28 @@ static ssize_t usb_dev_authorized_store(struct device *dev,
 static DEVICE_ATTR(authorized, 0644,
            usb_dev_authorized_show, usb_dev_authorized_store);
 
+/* "Safely remove a device" */
+static ssize_t usb_remove_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device *udev = to_usb_device(dev);
+       int rc = 0;
+
+       usb_lock_device(udev);
+       if (udev->state != USB_STATE_NOTATTACHED) {
+
+               /* To avoid races, first unconfigure and then remove */
+               usb_set_configuration(udev, -1);
+               rc = usb_remove_device(udev);
+       }
+       if (rc == 0)
+               rc = count;
+       usb_unlock_device(udev);
+       return rc;
+}
+static DEVICE_ATTR(remove, 0200, NULL, usb_remove_store);
+
 
 static struct attribute *dev_attrs[] = {
        /* current configuration's attributes */
@@ -533,6 +555,7 @@ static struct attribute *dev_attrs[] = {
        &dev_attr_maxchild.attr,
        &dev_attr_quirks.attr,
        &dev_attr_authorized.attr,
+       &dev_attr_remove.attr,
        NULL,
 };
 static struct attribute_group dev_attr_grp = {
index 9a8b15e..4c36c7f 100644 (file)
@@ -24,6 +24,7 @@ extern void usb_disable_device(struct usb_device *dev, int skip_ep0);
 extern int usb_deauthorize_device(struct usb_device *);
 extern int usb_authorize_device(struct usb_device *);
 extern void usb_detect_quirks(struct usb_device *udev);
+extern int usb_remove_device(struct usb_device *udev);
 
 extern int usb_get_device_descriptor(struct usb_device *dev,
                unsigned int size);