aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2007-05-04 11:52:20 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2007-07-12 16:29:47 -0700
commit0458d5b4c9cc4ca0f62625d0144ddc4b4bc97a3c (patch)
tree8b1fcb4f063ef4aa6f2e3cd41a60d986a1e432d4 /drivers
parentce7cd137fced114d49178b73d468b82096a107fb (diff)
USB: add USB-Persist facility
This patch (as886) adds the controversial USB-persist facility, allowing USB devices to persist across a power loss during system suspend. The facility is controlled by a new Kconfig option (with appropriate warnings about the potential dangers); when the option is off the behavior will remain the same as it is now. But when the option is on, people will be able to use suspend-to-disk and keep their USB filesystems intact -- something particularly valuable for small machines where the root filesystem is on a USB device! Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/hid/usbhid/hid-core.c2
-rw-r--r--drivers/usb/core/Kconfig22
-rw-r--r--drivers/usb/core/driver.c39
-rw-r--r--drivers/usb/core/generic.c5
-rw-r--r--drivers/usb/core/hub.c196
-rw-r--r--drivers/usb/core/usb.h1
-rw-r--r--drivers/usb/storage/usb.c8
7 files changed, 198 insertions, 75 deletions
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 3afa4a5035b..e221b0d1f66 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -1015,7 +1015,7 @@ static void hid_pre_reset(struct usb_interface *intf)
hid_suspend(intf, PMSG_ON);
}
-static void hid_post_reset(struct usb_interface *intf)
+static void hid_post_reset(struct usb_interface *intf, int reset_resume)
{
struct usb_device *dev = interface_to_usbdev (intf);
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index 346fc030c92..5113ef4cb7f 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -86,6 +86,28 @@ config USB_SUSPEND
If you are unsure about this, say N here.
+config USB_PERSIST
+ bool "USB device persistence during system suspend (DANGEROUS)"
+ depends on USB && PM && EXPERIMENTAL
+ default n
+ help
+ If you say Y here, USB device data structures will remain
+ persistent across system suspend, even if the USB bus loses
+ power. (This includes software-suspend, also known as swsusp,
+ or suspend-to-disk.) The devices will reappear as if by magic
+ when the system wakes up, with no need to unmount USB filesystems,
+ rmmod host-controller drivers, or do anything else.
+
+ WARNING: This option can be dangerous!
+
+ If a USB device is replaced by another of the same type while
+ the system is asleep, there's a good chance the kernel won't
+ detect the change. Likewise if the media in a USB storage
+ device is replaced. When this happens it's almost certain to
+ cause data corruption and maybe even crash your system.
+
+ If you are unsure, say N here.
+
config USB_OTG
bool
depends on USB && EXPERIMENTAL
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index e8b447e06c5..12dd986bdff 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -824,8 +824,9 @@ static int usb_resume_device(struct usb_device *udev)
struct usb_device_driver *udriver;
int status = 0;
- if (udev->state == USB_STATE_NOTATTACHED ||
- udev->state != USB_STATE_SUSPENDED)
+ if (udev->state == USB_STATE_NOTATTACHED)
+ goto done;
+ if (udev->state != USB_STATE_SUSPENDED && !udev->reset_resume)
goto done;
/* Can't resume it if it doesn't have a driver. */
@@ -882,7 +883,7 @@ done:
}
/* Caller has locked intf's usb_device's pm_mutex */
-static int usb_resume_interface(struct usb_interface *intf)
+static int usb_resume_interface(struct usb_interface *intf, int reset_resume)
{
struct usb_driver *driver;
int status = 0;
@@ -902,21 +903,21 @@ static int usb_resume_interface(struct usb_interface *intf)
}
driver = to_usb_driver(intf->dev.driver);
- if (driver->resume) {
+ if (reset_resume && driver->post_reset)
+ driver->post_reset(intf, reset_resume);
+ else if (driver->resume) {
status = driver->resume(intf);
if (status)
dev_err(&intf->dev, "%s error %d\n",
"resume", status);
- else
- mark_active(intf);
- } else {
+ } else
dev_warn(&intf->dev, "no resume for driver %s?\n",
driver->name);
- mark_active(intf);
- }
done:
// dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
+ if (status == 0)
+ mark_active(intf);
return status;
}
@@ -1063,7 +1064,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
if (status != 0) {
while (--i >= 0) {
intf = udev->actconfig->interface[i];
- usb_resume_interface(intf);
+ usb_resume_interface(intf, 0);
}
/* Try another autosuspend when the interfaces aren't busy */
@@ -1162,20 +1163,21 @@ static int usb_resume_both(struct usb_device *udev)
}
} else {
- /* Needed only for setting udev->dev.power.power_state.event
- * and for possible debugging message. */
+ /* Needed for setting udev->dev.power.power_state.event,
+ * for possible debugging message, and for reset_resume. */
status = usb_resume_device(udev);
}
if (status == 0 && udev->actconfig) {
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
intf = udev->actconfig->interface[i];
- usb_resume_interface(intf);
+ usb_resume_interface(intf, udev->reset_resume);
}
}
done:
// dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+ udev->reset_resume = 0;
return status;
}
@@ -1510,8 +1512,15 @@ static int usb_resume(struct device *dev)
if (!is_usb_device(dev)) /* Ignore PM for interfaces */
return 0;
udev = to_usb_device(dev);
- if (udev->autoresume_disabled)
- return -EPERM;
+
+ /* If autoresume is disabled then we also want to prevent resume
+ * during system wakeup. However, a "persistent-device" reset-resume
+ * after power loss counts as a wakeup event. So allow a
+ * reset-resume to occur if remote wakeup is enabled. */
+ if (udev->autoresume_disabled) {
+ if (!(udev->reset_resume && udev->do_remote_wakeup))
+ return -EPERM;
+ }
return usb_external_resume_device(udev);
}
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index 7cbf992adcc..d363b0ea734 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -217,7 +217,10 @@ static int generic_resume(struct usb_device *udev)
{
int rc;
- rc = usb_port_resume(udev);
+ if (udev->reset_resume)
+ rc = usb_reset_suspended_device(udev);
+ else
+ rc = usb_port_resume(udev);
/* Root hubs don't have upstream ports to resume or reset,
* so the line above won't do much for them. We have to
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 77a6627b18d..51d2d304568 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -553,45 +553,121 @@ static int hub_hub_status(struct usb_hub *hub,
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
{
struct usb_device *hdev = hub->hdev;
- int ret;
+ int ret = 0;
- if (hdev->children[port1-1] && set_state) {
+ if (hdev->children[port1-1] && set_state)
usb_set_device_state(hdev->children[port1-1],
USB_STATE_NOTATTACHED);
- }
- ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
+ if (!hub->error)
+ ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
if (ret)
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
- port1, ret);
-
+ port1, ret);
return ret;
}
+/*
+ * Disable a port and mark a logical connnect-change event, so that some
+ * time later khubd will disconnect() any existing usb_device on the port
+ * and will re-enumerate if there actually is a device attached.
+ */
+static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
+{
+ dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1);
+ hub_port_disable(hub, port1, 1);
-/* caller has locked the hub device */
-static void hub_pre_reset(struct usb_interface *intf)
+ /* FIXME let caller ask to power down the port:
+ * - some devices won't enumerate without a VBUS power cycle
+ * - SRP saves power that way
+ * - ... new call, TBD ...
+ * That's easy if this hub can switch power per-port, and
+ * khubd reactivates the port later (timer, SRP, etc).
+ * Powerdown must be optional, because of reset/DFU.
+ */
+
+ set_bit(port1, hub->change_bits);
+ kick_khubd(hub);
+}
+
+static void disconnect_all_children(struct usb_hub *hub, int logical)
{
- struct usb_hub *hub = usb_get_intfdata(intf);
struct usb_device *hdev = hub->hdev;
int port1;
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
- if (hdev->children[port1 - 1]) {
- usb_disconnect(&hdev->children[port1 - 1]);
- if (hub->error == 0)
- hub_port_disable(hub, port1, 0);
+ if (hdev->children[port1-1]) {
+ if (logical)
+ hub_port_logical_disconnect(hub, port1);
+ else
+ usb_disconnect(&hdev->children[port1-1]);
+ }
+ }
+}
+
+#ifdef CONFIG_USB_PERSIST
+
+#define USB_PERSIST 1
+
+/* For "persistent-device" resets we must mark the child devices for reset
+ * and turn off a possible connect-change status (so khubd won't disconnect
+ * them later).
+ */
+static void mark_children_for_reset_resume(struct usb_hub *hub)
+{
+ struct usb_device *hdev = hub->hdev;
+ int port1;
+
+ for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
+ struct usb_device *child = hdev->children[port1-1];
+
+ if (child) {
+ child->reset_resume = 1;
+ clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_CONNECTION);
}
}
+}
+
+#else
+
+#define USB_PERSIST 0
+
+static inline void mark_children_for_reset_resume(struct usb_hub *hub)
+{ }
+
+#endif /* CONFIG_USB_PERSIST */
+
+/* caller has locked the hub device */
+static void hub_pre_reset(struct usb_interface *intf)
+{
+ struct usb_hub *hub = usb_get_intfdata(intf);
+
+ /* This routine doesn't run as part of a reset-resume, so it's safe
+ * to disconnect all the drivers below the hub.
+ */
+ disconnect_all_children(hub, 0);
hub_quiesce(hub);
}
/* caller has locked the hub device */
-static void hub_post_reset(struct usb_interface *intf)
+static void hub_post_reset(struct usb_interface *intf, int reset_resume)
{
struct usb_hub *hub = usb_get_intfdata(intf);
- hub_activate(hub);
hub_power_on(hub);
+ if (reset_resume) {
+ if (USB_PERSIST)
+ mark_children_for_reset_resume(hub);
+ else {
+ /* Reset-resume doesn't call pre_reset, so we have to
+ * disconnect the children here. But we may not lock
+ * the child devices, so we have to do a "logical"
+ * disconnect.
+ */
+ disconnect_all_children(hub, 1);
+ }
+ }
+ hub_activate(hub);
}
@@ -1054,32 +1130,63 @@ void usb_set_device_state(struct usb_device *udev,
#ifdef CONFIG_PM
/**
+ * usb_reset_suspended_device - reset a suspended device instead of resuming it
+ * @udev: device to be reset instead of resumed
+ *
+ * If a host controller doesn't maintain VBUS suspend current during a
+ * system sleep or is reset when the system wakes up, all the USB
+ * power sessions below it will be broken. This is especially troublesome
+ * for mass-storage devices containing mounted filesystems, since the
+ * device will appear to have disconnected and all the memory mappings
+ * to it will be lost.
+ *
+ * As an alternative, this routine attempts to recover power sessions for
+ * devices that are still present by resetting them instead of resuming
+ * them. If all goes well, the devices will appear to persist across the
+ * the interruption of the power sessions.
+ *
+ * This facility is inherently dangerous. Although usb_reset_device()
+ * makes every effort to insure that the same device is present after the
+ * reset as before, it cannot provide a 100% guarantee. Furthermore it's
+ * quite possible for a device to remain unaltered but its media to be
+ * changed. If the user replaces a flash memory card while the system is
+ * asleep, he will have only himself to blame when the filesystem on the
+ * new card is corrupted and the system crashes.
+ */
+int usb_reset_suspended_device(struct usb_device *udev)
+{
+ int rc = 0;
+
+ dev_dbg(&udev->dev, "usb %sresume\n", "reset-");
+
+ /* After we're done the device won't be suspended any more.
+ * In addition, the reset won't work if udev->state is SUSPENDED.
+ */
+ usb_set_device_state(udev, udev->actconfig
+ ? USB_STATE_CONFIGURED
+ : USB_STATE_ADDRESS);
+
+ /* Root hubs don't need to be (and can't be) reset */
+ if (udev->parent)
+ rc = usb_reset_device(udev);
+ return rc;
+}
+
+/**
* usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power
* @rhdev: struct usb_device for the root hub
*
* The USB host controller driver calls this function when its root hub
* is resumed and Vbus power has been interrupted or the controller
- * has been reset. The routine marks all the children of the root hub
- * as NOTATTACHED and marks logical connect-change events on their ports.
+ * has been reset. The routine marks @rhdev as having lost power. When
+ * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST
+ * is enabled then it will carry out power-session recovery, otherwise
+ * it will disconnect all the child devices.
*/
void usb_root_hub_lost_power(struct usb_device *rhdev)
{
- struct usb_hub *hub;
- int port1;
- unsigned long flags;
-
dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
-
- spin_lock_irqsave(&device_state_lock, flags);
- hub = hdev_to_hub(rhdev);
- for (port1 = 1; port1 <= rhdev->maxchild; ++port1) {
- if (rhdev->children[port1 - 1]) {
- recursively_mark_NOTATTACHED(
- rhdev->children[port1 - 1]);
- set_bit(port1, hub->change_bits);
- }
- }
- spin_unlock_irqrestore(&device_state_lock, flags);
+ rhdev->reset_resume = 1;
}
EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
@@ -1513,29 +1620,6 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
return status;
}
-/*
- * Disable a port and mark a logical connnect-change event, so that some
- * time later khubd will disconnect() any existing usb_device on the port
- * and will re-enumerate if there actually is a device attached.
- */
-static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
-{
- dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1);
- hub_port_disable(hub, port1, 1);
-
- /* FIXME let caller ask to power down the port:
- * - some devices won't enumerate without a VBUS power cycle
- * - SRP saves power that way
- * - ... new call, TBD ...
- * That's easy if this hub can switch power per-port, and
- * khubd reactivates the port later (timer, SRP, etc).
- * Powerdown must be optional, because of reset/DFU.
- */
-
- set_bit(port1, hub->change_bits);
- kick_khubd(hub);
-}
-
#ifdef CONFIG_PM
#ifdef CONFIG_USB_SUSPEND
@@ -3018,7 +3102,7 @@ int usb_reset_composite_device(struct usb_device *udev,
cintf->dev.driver) {
drv = to_usb_driver(cintf->dev.driver);
if (drv->post_reset)
- (drv->post_reset)(cintf);
+ (drv->post_reset)(cintf, 0);
}
if (cintf != iface)
up(&cintf->dev.sem);
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 6f361df374f..1a486288673 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -36,6 +36,7 @@ extern void usb_host_cleanup(void);
extern void usb_autosuspend_work(struct work_struct *work);
extern int usb_port_suspend(struct usb_device *dev);
extern int usb_port_resume(struct usb_device *dev);
+extern int usb_reset_suspended_device(struct usb_device *udev);
extern int usb_external_suspend_device(struct usb_device *udev,
pm_message_t msg);
extern int usb_external_resume_device(struct usb_device *udev);
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
index df5dc186aef..be4cd8fe4ce 100644
--- a/drivers/usb/storage/usb.c
+++ b/drivers/usb/storage/usb.c
@@ -236,7 +236,7 @@ static void storage_pre_reset(struct usb_interface *iface)
mutex_lock(&us->dev_mutex);
}
-static void storage_post_reset(struct usb_interface *iface)
+static void storage_post_reset(struct usb_interface *iface, int reset_resume)
{
struct us_data *us = usb_get_intfdata(iface);
@@ -249,7 +249,11 @@ static void storage_post_reset(struct usb_interface *iface)
/* FIXME: Notify the subdrivers that they need to reinitialize
* the device */
- mutex_unlock(&us->dev_mutex);
+
+ /* If this is a reset-resume then the pre_reset routine wasn't
+ * called, so we don't need to unlock the mutex. */
+ if (!reset_resume)
+ mutex_unlock(&us->dev_mutex);
}
/*