aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2009-10-27 15:20:13 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-12-11 14:55:18 -0500
commit253e05724f9230910344357b1142ad8642ff9f5a (patch)
treeff29ac01c681b183db2ad4b18b95b6c3dbe0ca0b /drivers/usb
parentd697cdda43939a80432863e2e26df6701ce72b63 (diff)
USB: add a "remove hardware" sysfs attribute
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>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/hub.c50
-rw-r--r--drivers/usb/core/sysfs.c23
-rw-r--r--drivers/usb/core/usb.h1
3 files changed, 72 insertions, 2 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 708c63826100..5413d712cae0 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -60,6 +60,8 @@ struct usb_hub {
60 status change */ 60 status change */
61 unsigned long busy_bits[1]; /* ports being reset or 61 unsigned long busy_bits[1]; /* ports being reset or
62 resumed */ 62 resumed */
63 unsigned long removed_bits[1]; /* ports with a "removed"
64 device present */
63#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ 65#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
64#error event_bits[] is too short! 66#error event_bits[] is too short!
65#endif 67#endif
@@ -635,6 +637,33 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
635 kick_khubd(hub); 637 kick_khubd(hub);
636} 638}
637 639
640/**
641 * usb_remove_device - disable a device's port on its parent hub
642 * @udev: device to be disabled and removed
643 * Context: @udev locked, must be able to sleep.
644 *
645 * After @udev's port has been disabled, khubd is notified and it will
646 * see that the device has been disconnected. When the device is
647 * physically unplugged and something is plugged in, the events will
648 * be received and processed normally.
649 */
650int usb_remove_device(struct usb_device *udev)
651{
652 struct usb_hub *hub;
653 struct usb_interface *intf;
654
655 if (!udev->parent) /* Can't remove a root hub */
656 return -EINVAL;
657 hub = hdev_to_hub(udev->parent);
658 intf = to_usb_interface(hub->intfdev);
659
660 usb_autopm_get_interface(intf);
661 set_bit(udev->portnum, hub->removed_bits);
662 hub_port_logical_disconnect(hub, udev->portnum);
663 usb_autopm_put_interface(intf);
664 return 0;
665}
666
638enum hub_activation_type { 667enum hub_activation_type {
639 HUB_INIT, HUB_INIT2, HUB_INIT3, 668 HUB_INIT, HUB_INIT2, HUB_INIT3,
640 HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, 669 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)
730 USB_PORT_FEAT_C_ENABLE); 759 USB_PORT_FEAT_C_ENABLE);
731 } 760 }
732 761
762 /* We can forget about a "removed" device when there's a
763 * physical disconnect or the connect status changes.
764 */
765 if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
766 (portchange & USB_PORT_STAT_C_CONNECTION))
767 clear_bit(port1, hub->removed_bits);
768
733 if (!udev || udev->state == USB_STATE_NOTATTACHED) { 769 if (!udev || udev->state == USB_STATE_NOTATTACHED) {
734 /* Tell khubd to disconnect the device or 770 /* Tell khubd to disconnect the device or
735 * check for a new connection 771 * check for a new connection
@@ -2965,6 +3001,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
2965 usb_disconnect(&hdev->children[port1-1]); 3001 usb_disconnect(&hdev->children[port1-1]);
2966 clear_bit(port1, hub->change_bits); 3002 clear_bit(port1, hub->change_bits);
2967 3003
3004 /* We can forget about a "removed" device when there's a physical
3005 * disconnect or the connect status changes.
3006 */
3007 if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
3008 (portchange & USB_PORT_STAT_C_CONNECTION))
3009 clear_bit(port1, hub->removed_bits);
3010
2968 if (portchange & (USB_PORT_STAT_C_CONNECTION | 3011 if (portchange & (USB_PORT_STAT_C_CONNECTION |
2969 USB_PORT_STAT_C_ENABLE)) { 3012 USB_PORT_STAT_C_ENABLE)) {
2970 status = hub_port_debounce(hub, port1); 3013 status = hub_port_debounce(hub, port1);
@@ -2978,8 +3021,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
2978 } 3021 }
2979 } 3022 }
2980 3023
2981 /* Return now if debouncing failed or nothing is connected */ 3024 /* Return now if debouncing failed or nothing is connected or
2982 if (!(portstatus & USB_PORT_STAT_CONNECTION)) { 3025 * the device was "removed".
3026 */
3027 if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
3028 test_bit(port1, hub->removed_bits)) {
2983 3029
2984 /* maybe switch power back on (e.g. root hub was reset) */ 3030 /* maybe switch power back on (e.g. root hub was reset) */
2985 if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 3031 if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index 7ec3041ae79e..470e2413a9cf 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -508,6 +508,28 @@ static ssize_t usb_dev_authorized_store(struct device *dev,
508static DEVICE_ATTR(authorized, 0644, 508static DEVICE_ATTR(authorized, 0644,
509 usb_dev_authorized_show, usb_dev_authorized_store); 509 usb_dev_authorized_show, usb_dev_authorized_store);
510 510
511/* "Safely remove a device" */
512static ssize_t usb_remove_store(struct device *dev,
513 struct device_attribute *attr,
514 const char *buf, size_t count)
515{
516 struct usb_device *udev = to_usb_device(dev);
517 int rc = 0;
518
519 usb_lock_device(udev);
520 if (udev->state != USB_STATE_NOTATTACHED) {
521
522 /* To avoid races, first unconfigure and then remove */
523 usb_set_configuration(udev, -1);
524 rc = usb_remove_device(udev);
525 }
526 if (rc == 0)
527 rc = count;
528 usb_unlock_device(udev);
529 return rc;
530}
531static DEVICE_ATTR(remove, 0200, NULL, usb_remove_store);
532
511 533
512static struct attribute *dev_attrs[] = { 534static struct attribute *dev_attrs[] = {
513 /* current configuration's attributes */ 535 /* current configuration's attributes */
@@ -533,6 +555,7 @@ static struct attribute *dev_attrs[] = {
533 &dev_attr_maxchild.attr, 555 &dev_attr_maxchild.attr,
534 &dev_attr_quirks.attr, 556 &dev_attr_quirks.attr,
535 &dev_attr_authorized.attr, 557 &dev_attr_authorized.attr,
558 &dev_attr_remove.attr,
536 NULL, 559 NULL,
537}; 560};
538static struct attribute_group dev_attr_grp = { 561static struct attribute_group dev_attr_grp = {
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 9a8b15e6377a..4c36c7f512a0 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -24,6 +24,7 @@ extern void usb_disable_device(struct usb_device *dev, int skip_ep0);
24extern int usb_deauthorize_device(struct usb_device *); 24extern int usb_deauthorize_device(struct usb_device *);
25extern int usb_authorize_device(struct usb_device *); 25extern int usb_authorize_device(struct usb_device *);
26extern void usb_detect_quirks(struct usb_device *udev); 26extern void usb_detect_quirks(struct usb_device *udev);
27extern int usb_remove_device(struct usb_device *udev);
27 28
28extern int usb_get_device_descriptor(struct usb_device *dev, 29extern int usb_get_device_descriptor(struct usb_device *dev,
29 unsigned int size); 30 unsigned int size);