Commit 253e0572 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Cc: David Zeuthen <davidz@redhat.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d697cdda
...@@ -60,6 +60,8 @@ struct usb_hub { ...@@ -60,6 +60,8 @@ struct usb_hub {
status change */ status change */
unsigned long busy_bits[1]; /* ports being reset or unsigned long busy_bits[1]; /* ports being reset or
resumed */ resumed */
unsigned long removed_bits[1]; /* ports with a "removed"
device present */
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
#error event_bits[] is too short! #error event_bits[] is too short!
#endif #endif
...@@ -635,6 +637,33 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) ...@@ -635,6 +637,33 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
kick_khubd(hub); 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 { enum hub_activation_type {
HUB_INIT, HUB_INIT2, HUB_INIT3, HUB_INIT, HUB_INIT2, HUB_INIT3,
HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, 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,6 +759,13 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
USB_PORT_FEAT_C_ENABLE); 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) { if (!udev || udev->state == USB_STATE_NOTATTACHED) {
/* Tell khubd to disconnect the device or /* Tell khubd to disconnect the device or
* check for a new connection * check for a new connection
...@@ -2965,6 +3001,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -2965,6 +3001,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
usb_disconnect(&hdev->children[port1-1]); usb_disconnect(&hdev->children[port1-1]);
clear_bit(port1, hub->change_bits); 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 | if (portchange & (USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE)) { USB_PORT_STAT_C_ENABLE)) {
status = hub_port_debounce(hub, port1); status = hub_port_debounce(hub, port1);
...@@ -2978,8 +3021,11 @@ static void hub_port_connect_change(struct usb_hub *hub, int 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 */ /* Return now if debouncing failed or nothing is connected or
if (!(portstatus & USB_PORT_STAT_CONNECTION)) { * 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) */ /* maybe switch power back on (e.g. root hub was reset) */
if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
......
...@@ -508,6 +508,28 @@ static ssize_t usb_dev_authorized_store(struct device *dev, ...@@ -508,6 +508,28 @@ static ssize_t usb_dev_authorized_store(struct device *dev,
static DEVICE_ATTR(authorized, 0644, static DEVICE_ATTR(authorized, 0644,
usb_dev_authorized_show, usb_dev_authorized_store); 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[] = { static struct attribute *dev_attrs[] = {
/* current configuration's attributes */ /* current configuration's attributes */
...@@ -533,6 +555,7 @@ static struct attribute *dev_attrs[] = { ...@@ -533,6 +555,7 @@ static struct attribute *dev_attrs[] = {
&dev_attr_maxchild.attr, &dev_attr_maxchild.attr,
&dev_attr_quirks.attr, &dev_attr_quirks.attr,
&dev_attr_authorized.attr, &dev_attr_authorized.attr,
&dev_attr_remove.attr,
NULL, NULL,
}; };
static struct attribute_group dev_attr_grp = { static struct attribute_group dev_attr_grp = {
......
...@@ -24,6 +24,7 @@ extern void usb_disable_device(struct usb_device *dev, int skip_ep0); ...@@ -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_deauthorize_device(struct usb_device *);
extern int usb_authorize_device(struct usb_device *); extern int usb_authorize_device(struct usb_device *);
extern void usb_detect_quirks(struct usb_device *udev); 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, extern int usb_get_device_descriptor(struct usb_device *dev,
unsigned int size); unsigned int size);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment