forked from Minki/linux
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>
This commit is contained in:
parent
ce7cd137fc
commit
0458d5b4c9
@ -393,6 +393,9 @@ safest thing is to unmount all filesystems on removable media (such USB,
|
|||||||
Firewire, CompactFlash, MMC, external SATA, or even IDE hotplug bays)
|
Firewire, CompactFlash, MMC, external SATA, or even IDE hotplug bays)
|
||||||
before suspending; then remount them after resuming.
|
before suspending; then remount them after resuming.
|
||||||
|
|
||||||
|
There is a work-around for this problem. For more information, see
|
||||||
|
Documentation/usb/persist.txt.
|
||||||
|
|
||||||
Q: I upgraded the kernel from 2.6.15 to 2.6.16. Both kernels were
|
Q: I upgraded the kernel from 2.6.15 to 2.6.16. Both kernels were
|
||||||
compiled with the similar configuration files. Anyway I found that
|
compiled with the similar configuration files. Anyway I found that
|
||||||
suspend to disk (and resume) is much slower on 2.6.16 compared to
|
suspend to disk (and resume) is much slower on 2.6.16 compared to
|
||||||
|
144
Documentation/usb/persist.txt
Normal file
144
Documentation/usb/persist.txt
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
USB device persistence during system suspend
|
||||||
|
|
||||||
|
Alan Stern <stern@rowland.harvard.edu>
|
||||||
|
|
||||||
|
September 2, 2006 (Updated March 27, 2007)
|
||||||
|
|
||||||
|
|
||||||
|
What is the problem?
|
||||||
|
|
||||||
|
According to the USB specification, when a USB bus is suspended the
|
||||||
|
bus must continue to supply suspend current (around 1-5 mA). This
|
||||||
|
is so that devices can maintain their internal state and hubs can
|
||||||
|
detect connect-change events (devices being plugged in or unplugged).
|
||||||
|
The technical term is "power session".
|
||||||
|
|
||||||
|
If a USB device's power session is interrupted then the system is
|
||||||
|
required to behave as though the device has been unplugged. It's a
|
||||||
|
conservative approach; in the absence of suspend current the computer
|
||||||
|
has no way to know what has actually happened. Perhaps the same
|
||||||
|
device is still attached or perhaps it was removed and a different
|
||||||
|
device plugged into the port. The system must assume the worst.
|
||||||
|
|
||||||
|
By default, Linux behaves according to the spec. If a USB host
|
||||||
|
controller loses power during a system suspend, then when the system
|
||||||
|
wakes up all the devices attached to that controller are treated as
|
||||||
|
though they had disconnected. This is always safe and it is the
|
||||||
|
"officially correct" thing to do.
|
||||||
|
|
||||||
|
For many sorts of devices this behavior doesn't matter in the least.
|
||||||
|
If the kernel wants to believe that your USB keyboard was unplugged
|
||||||
|
while the system was asleep and a new keyboard was plugged in when the
|
||||||
|
system woke up, who cares? It'll still work the same when you type on
|
||||||
|
it.
|
||||||
|
|
||||||
|
Unfortunately problems _can_ arise, particularly with mass-storage
|
||||||
|
devices. The effect is exactly the same as if the device really had
|
||||||
|
been unplugged while the system was suspended. If you had a mounted
|
||||||
|
filesystem on the device, you're out of luck -- everything in that
|
||||||
|
filesystem is now inaccessible. This is especially annoying if your
|
||||||
|
root filesystem was located on the device, since your system will
|
||||||
|
instantly crash.
|
||||||
|
|
||||||
|
Loss of power isn't the only mechanism to worry about. Anything that
|
||||||
|
interrupts a power session will have the same effect. For example,
|
||||||
|
even though suspend current may have been maintained while the system
|
||||||
|
was asleep, on many systems during the initial stages of wakeup the
|
||||||
|
firmware (i.e., the BIOS) resets the motherboard's USB host
|
||||||
|
controllers. Result: all the power sessions are destroyed and again
|
||||||
|
it's as though you had unplugged all the USB devices. Yes, it's
|
||||||
|
entirely the BIOS's fault, but that doesn't do _you_ any good unless
|
||||||
|
you can convince the BIOS supplier to fix the problem (lots of luck!).
|
||||||
|
|
||||||
|
On many systems the USB host controllers will get reset after a
|
||||||
|
suspend-to-RAM. On almost all systems, no suspend current is
|
||||||
|
available during suspend-to-disk (also known as swsusp). You can
|
||||||
|
check the kernel log after resuming to see if either of these has
|
||||||
|
happened; look for lines saying "root hub lost power or was reset".
|
||||||
|
|
||||||
|
In practice, people are forced to unmount any filesystems on a USB
|
||||||
|
device before suspending. If the root filesystem is on a USB device,
|
||||||
|
the system can't be suspended at all. (All right, it _can_ be
|
||||||
|
suspended -- but it will crash as soon as it wakes up, which isn't
|
||||||
|
much better.)
|
||||||
|
|
||||||
|
|
||||||
|
What is the solution?
|
||||||
|
|
||||||
|
Setting CONFIG_USB_PERSIST will cause the kernel to work around these
|
||||||
|
issues. It enables a mode in which the core USB device data
|
||||||
|
structures are allowed to persist across a power-session disruption.
|
||||||
|
It works like this. If the kernel sees that a USB host controller is
|
||||||
|
not in the expected state during resume (i.e., if the controller was
|
||||||
|
reset or otherwise had lost power) then it applies a persistence check
|
||||||
|
to each of the USB devices below that controller. It doesn't try to
|
||||||
|
resume the device; that can't work once the power session is gone.
|
||||||
|
Instead it issues a USB port reset and then re-enumerates the device.
|
||||||
|
(This is exactly the same thing that happens whenever a USB device is
|
||||||
|
reset.) If the re-enumeration shows that the device now attached to
|
||||||
|
that port has the same descriptors as before, including the Vendor and
|
||||||
|
Product IDs, then the kernel continues to use the same device
|
||||||
|
structure. In effect, the kernel treats the device as though it had
|
||||||
|
merely been reset instead of unplugged.
|
||||||
|
|
||||||
|
If no device is now attached to the port, or if the descriptors are
|
||||||
|
different from what the kernel remembers, then the treatment is what
|
||||||
|
you would expect. The kernel destroys the old device structure and
|
||||||
|
behaves as though the old device had been unplugged and a new device
|
||||||
|
plugged in, just as it would without the CONFIG_USB_PERSIST option.
|
||||||
|
|
||||||
|
The end result is that the USB device remains available and usable.
|
||||||
|
Filesystem mounts and memory mappings are unaffected, and the world is
|
||||||
|
now a good and happy place.
|
||||||
|
|
||||||
|
|
||||||
|
Is this the best solution?
|
||||||
|
|
||||||
|
Perhaps not. Arguably, keeping track of mounted filesystems and
|
||||||
|
memory mappings across device disconnects should be handled by a
|
||||||
|
centralized Logical Volume Manager. Such a solution would allow you
|
||||||
|
to plug in a USB flash device, create a persistent volume associated
|
||||||
|
with it, unplug the flash device, plug it back in later, and still
|
||||||
|
have the same persistent volume associated with the device. As such
|
||||||
|
it would be more far-reaching than CONFIG_USB_PERSIST.
|
||||||
|
|
||||||
|
On the other hand, writing a persistent volume manager would be a big
|
||||||
|
job and using it would require significant input from the user. This
|
||||||
|
solution is much quicker and easier -- and it exists now, a giant
|
||||||
|
point in its favor!
|
||||||
|
|
||||||
|
Furthermore, the USB_PERSIST option applies to _all_ USB devices, not
|
||||||
|
just mass-storage devices. It might turn out to be equally useful for
|
||||||
|
other device types, such as network interfaces.
|
||||||
|
|
||||||
|
|
||||||
|
WARNING: Using CONFIG_USB_PERSIST can be dangerous!!
|
||||||
|
|
||||||
|
When recovering an interrupted power session the kernel does its best
|
||||||
|
to make sure the USB device hasn't been changed; that is, the same
|
||||||
|
device is still plugged into the port as before. But the checks
|
||||||
|
aren't guaranteed to be 100% accurate.
|
||||||
|
|
||||||
|
If you replace one USB device with another of the same type (same
|
||||||
|
manufacturer, same IDs, and so on) there's an excellent chance the
|
||||||
|
kernel won't detect the change. Serial numbers and other strings are
|
||||||
|
not compared. In many cases it wouldn't help if they were, because
|
||||||
|
manufacturers frequently omit serial numbers entirely in their
|
||||||
|
devices.
|
||||||
|
|
||||||
|
Furthermore it's quite possible to leave a USB device exactly the same
|
||||||
|
while changing its media. If you replace the flash memory card in a
|
||||||
|
USB card reader while the system is asleep, the kernel will have no
|
||||||
|
way to know you did it. The kernel will assume that nothing has
|
||||||
|
happened and will continue to use the partition tables, inodes, and
|
||||||
|
memory mappings for the old card.
|
||||||
|
|
||||||
|
If the kernel gets fooled in this way, it's almost certain to cause
|
||||||
|
data corruption and to crash your system. You'll have no one to blame
|
||||||
|
but yourself.
|
||||||
|
|
||||||
|
YOU HAVE BEEN WARNED! USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
|
That having been said, most of the time there shouldn't be any trouble
|
||||||
|
at all. The "persist" feature can be extremely useful. Make the most
|
||||||
|
of it.
|
@ -1015,7 +1015,7 @@ static void hid_pre_reset(struct usb_interface *intf)
|
|||||||
hid_suspend(intf, PMSG_ON);
|
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);
|
struct usb_device *dev = interface_to_usbdev (intf);
|
||||||
|
|
||||||
|
@ -86,6 +86,28 @@ config USB_SUSPEND
|
|||||||
|
|
||||||
If you are unsure about this, say N here.
|
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
|
config USB_OTG
|
||||||
bool
|
bool
|
||||||
depends on USB && EXPERIMENTAL
|
depends on USB && EXPERIMENTAL
|
||||||
|
@ -824,8 +824,9 @@ static int usb_resume_device(struct usb_device *udev)
|
|||||||
struct usb_device_driver *udriver;
|
struct usb_device_driver *udriver;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
|
||||||
if (udev->state == USB_STATE_NOTATTACHED ||
|
if (udev->state == USB_STATE_NOTATTACHED)
|
||||||
udev->state != USB_STATE_SUSPENDED)
|
goto done;
|
||||||
|
if (udev->state != USB_STATE_SUSPENDED && !udev->reset_resume)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Can't resume it if it doesn't have a driver. */
|
/* 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 */
|
/* 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;
|
struct usb_driver *driver;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
@ -902,21 +903,21 @@ static int usb_resume_interface(struct usb_interface *intf)
|
|||||||
}
|
}
|
||||||
driver = to_usb_driver(intf->dev.driver);
|
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);
|
status = driver->resume(intf);
|
||||||
if (status)
|
if (status)
|
||||||
dev_err(&intf->dev, "%s error %d\n",
|
dev_err(&intf->dev, "%s error %d\n",
|
||||||
"resume", status);
|
"resume", status);
|
||||||
else
|
} else
|
||||||
mark_active(intf);
|
|
||||||
} else {
|
|
||||||
dev_warn(&intf->dev, "no resume for driver %s?\n",
|
dev_warn(&intf->dev, "no resume for driver %s?\n",
|
||||||
driver->name);
|
driver->name);
|
||||||
mark_active(intf);
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
done:
|
||||||
// dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
|
// dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
|
||||||
|
if (status == 0)
|
||||||
|
mark_active(intf);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1063,7 +1064,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
|||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
while (--i >= 0) {
|
while (--i >= 0) {
|
||||||
intf = udev->actconfig->interface[i];
|
intf = udev->actconfig->interface[i];
|
||||||
usb_resume_interface(intf);
|
usb_resume_interface(intf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try another autosuspend when the interfaces aren't busy */
|
/* Try another autosuspend when the interfaces aren't busy */
|
||||||
@ -1162,20 +1163,21 @@ static int usb_resume_both(struct usb_device *udev)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/* Needed only for setting udev->dev.power.power_state.event
|
/* Needed for setting udev->dev.power.power_state.event,
|
||||||
* and for possible debugging message. */
|
* for possible debugging message, and for reset_resume. */
|
||||||
status = usb_resume_device(udev);
|
status = usb_resume_device(udev);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == 0 && udev->actconfig) {
|
if (status == 0 && udev->actconfig) {
|
||||||
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
|
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
|
||||||
intf = udev->actconfig->interface[i];
|
intf = udev->actconfig->interface[i];
|
||||||
usb_resume_interface(intf);
|
usb_resume_interface(intf, udev->reset_resume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
// dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
|
// dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
|
||||||
|
udev->reset_resume = 0;
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1510,8 +1512,15 @@ static int usb_resume(struct device *dev)
|
|||||||
if (!is_usb_device(dev)) /* Ignore PM for interfaces */
|
if (!is_usb_device(dev)) /* Ignore PM for interfaces */
|
||||||
return 0;
|
return 0;
|
||||||
udev = to_usb_device(dev);
|
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);
|
return usb_external_resume_device(udev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,10 @@ static int generic_resume(struct usb_device *udev)
|
|||||||
{
|
{
|
||||||
int rc;
|
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,
|
/* Root hubs don't have upstream ports to resume or reset,
|
||||||
* so the line above won't do much for them. We have to
|
* so the line above won't do much for them. We have to
|
||||||
|
@ -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)
|
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
|
||||||
{
|
{
|
||||||
struct usb_device *hdev = hub->hdev;
|
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_set_device_state(hdev->children[port1-1],
|
||||||
USB_STATE_NOTATTACHED);
|
USB_STATE_NOTATTACHED);
|
||||||
}
|
if (!hub->error)
|
||||||
ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
|
ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
|
||||||
if (ret)
|
if (ret)
|
||||||
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
||||||
port1, ret);
|
port1, ret);
|
||||||
|
|
||||||
return 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);
|
||||||
|
|
||||||
|
/* 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_device *hdev = hub->hdev;
|
||||||
|
int port1;
|
||||||
|
|
||||||
|
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
|
||||||
|
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 */
|
/* caller has locked the hub device */
|
||||||
static void hub_pre_reset(struct usb_interface *intf)
|
static void hub_pre_reset(struct usb_interface *intf)
|
||||||
{
|
{
|
||||||
struct usb_hub *hub = usb_get_intfdata(intf);
|
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||||
struct usb_device *hdev = hub->hdev;
|
|
||||||
int port1;
|
|
||||||
|
|
||||||
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
|
/* This routine doesn't run as part of a reset-resume, so it's safe
|
||||||
if (hdev->children[port1 - 1]) {
|
* to disconnect all the drivers below the hub.
|
||||||
usb_disconnect(&hdev->children[port1 - 1]);
|
*/
|
||||||
if (hub->error == 0)
|
disconnect_all_children(hub, 0);
|
||||||
hub_port_disable(hub, port1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hub_quiesce(hub);
|
hub_quiesce(hub);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* caller has locked the hub device */
|
/* 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);
|
struct usb_hub *hub = usb_get_intfdata(intf);
|
||||||
|
|
||||||
hub_activate(hub);
|
|
||||||
hub_power_on(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1053,33 +1129,64 @@ void usb_set_device_state(struct usb_device *udev,
|
|||||||
|
|
||||||
#ifdef CONFIG_PM
|
#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
|
* usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power
|
||||||
* @rhdev: struct usb_device for the root hub
|
* @rhdev: struct usb_device for the root hub
|
||||||
*
|
*
|
||||||
* The USB host controller driver calls this function when its 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
|
* is resumed and Vbus power has been interrupted or the controller
|
||||||
* has been reset. The routine marks all the children of the root hub
|
* has been reset. The routine marks @rhdev as having lost power. When
|
||||||
* as NOTATTACHED and marks logical connect-change events on their ports.
|
* 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)
|
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");
|
dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
|
||||||
|
rhdev->reset_resume = 1;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
|
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;
|
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_PM
|
||||||
|
|
||||||
#ifdef CONFIG_USB_SUSPEND
|
#ifdef CONFIG_USB_SUSPEND
|
||||||
@ -3018,7 +3102,7 @@ int usb_reset_composite_device(struct usb_device *udev,
|
|||||||
cintf->dev.driver) {
|
cintf->dev.driver) {
|
||||||
drv = to_usb_driver(cintf->dev.driver);
|
drv = to_usb_driver(cintf->dev.driver);
|
||||||
if (drv->post_reset)
|
if (drv->post_reset)
|
||||||
(drv->post_reset)(cintf);
|
(drv->post_reset)(cintf, 0);
|
||||||
}
|
}
|
||||||
if (cintf != iface)
|
if (cintf != iface)
|
||||||
up(&cintf->dev.sem);
|
up(&cintf->dev.sem);
|
||||||
|
@ -36,6 +36,7 @@ extern void usb_host_cleanup(void);
|
|||||||
extern void usb_autosuspend_work(struct work_struct *work);
|
extern void usb_autosuspend_work(struct work_struct *work);
|
||||||
extern int usb_port_suspend(struct usb_device *dev);
|
extern int usb_port_suspend(struct usb_device *dev);
|
||||||
extern int usb_port_resume(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,
|
extern int usb_external_suspend_device(struct usb_device *udev,
|
||||||
pm_message_t msg);
|
pm_message_t msg);
|
||||||
extern int usb_external_resume_device(struct usb_device *udev);
|
extern int usb_external_resume_device(struct usb_device *udev);
|
||||||
|
@ -236,7 +236,7 @@ static void storage_pre_reset(struct usb_interface *iface)
|
|||||||
mutex_lock(&us->dev_mutex);
|
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);
|
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
|
/* FIXME: Notify the subdrivers that they need to reinitialize
|
||||||
* the device */
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -403,6 +403,7 @@ struct usb_device {
|
|||||||
|
|
||||||
unsigned auto_pm:1; /* autosuspend/resume in progress */
|
unsigned auto_pm:1; /* autosuspend/resume in progress */
|
||||||
unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */
|
unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */
|
||||||
|
unsigned reset_resume:1; /* needs reset instead of resume */
|
||||||
unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
|
unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
|
||||||
unsigned autoresume_disabled:1; /* disabled by the user */
|
unsigned autoresume_disabled:1; /* disabled by the user */
|
||||||
#endif
|
#endif
|
||||||
@ -819,7 +820,10 @@ struct usbdrv_wrap {
|
|||||||
* @pre_reset: Called by usb_reset_composite_device() when the device
|
* @pre_reset: Called by usb_reset_composite_device() when the device
|
||||||
* is about to be reset.
|
* is about to be reset.
|
||||||
* @post_reset: Called by usb_reset_composite_device() after the device
|
* @post_reset: Called by usb_reset_composite_device() after the device
|
||||||
* has been reset.
|
* has been reset, or in lieu of @resume following a reset-resume
|
||||||
|
* (i.e., the device is reset instead of being resumed, as might
|
||||||
|
* happen if power was lost). The second argument tells which is
|
||||||
|
* the reason.
|
||||||
* @id_table: USB drivers use ID table to support hotplugging.
|
* @id_table: USB drivers use ID table to support hotplugging.
|
||||||
* Export this with MODULE_DEVICE_TABLE(usb,...). This must be set
|
* Export this with MODULE_DEVICE_TABLE(usb,...). This must be set
|
||||||
* or your driver's probe function will never get called.
|
* or your driver's probe function will never get called.
|
||||||
@ -861,7 +865,7 @@ struct usb_driver {
|
|||||||
int (*resume) (struct usb_interface *intf);
|
int (*resume) (struct usb_interface *intf);
|
||||||
|
|
||||||
void (*pre_reset) (struct usb_interface *intf);
|
void (*pre_reset) (struct usb_interface *intf);
|
||||||
void (*post_reset) (struct usb_interface *intf);
|
void (*post_reset) (struct usb_interface *intf, int reset_resume);
|
||||||
|
|
||||||
const struct usb_device_id *id_table;
|
const struct usb_device_id *id_table;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user