summaryrefslogtreecommitdiff
path: root/drivers/usb/core
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/Kconfig25
-rw-r--r--drivers/usb/core/config.c42
-rw-r--r--drivers/usb/core/devices.c26
-rw-r--r--drivers/usb/core/driver.c162
-rw-r--r--drivers/usb/core/file.c29
-rw-r--r--drivers/usb/core/generic.c29
-rw-r--r--drivers/usb/core/hcd-pci.c3
-rw-r--r--drivers/usb/core/hcd.c126
-rw-r--r--drivers/usb/core/hcd.h14
-rw-r--r--drivers/usb/core/hub.c651
-rw-r--r--drivers/usb/core/message.c38
-rw-r--r--drivers/usb/core/quirks.c18
-rw-r--r--drivers/usb/core/sysfs.c109
-rw-r--r--drivers/usb/core/urb.c105
-rw-r--r--drivers/usb/core/usb.c12
-rw-r--r--drivers/usb/core/usb.h14
16 files changed, 867 insertions, 536 deletions
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index 346fc030c929..97b09f282705 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -86,6 +86,31 @@ 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 and enable the "power/persist" attribute
+ for a USB device, the device's data structures will remain
+ persistent across system suspend, even if the USB bus loses
+ power. (This includes hibernation, 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/config.c b/drivers/usb/core/config.c
index dd3482328ad2..cb69aa1e02e8 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -85,15 +85,21 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
memcpy(&endpoint->desc, d, n);
INIT_LIST_HEAD(&endpoint->urb_list);
- /* If the bInterval value is outside the legal range,
- * set it to a default value: 32 ms */
+ /* Fix up bInterval values outside the legal range. Use 32 ms if no
+ * proper value can be guessed. */
i = 0; /* i = min, j = max, n = default */
j = 255;
if (usb_endpoint_xfer_int(d)) {
i = 1;
switch (to_usb_device(ddev)->speed) {
case USB_SPEED_HIGH:
- n = 9; /* 32 ms = 2^(9-1) uframes */
+ /* Many device manufacturers are using full-speed
+ * bInterval values in high-speed interrupt endpoint
+ * descriptors. Try to fix those and fall back to a
+ * 32 ms default value otherwise. */
+ n = fls(d->bInterval*8);
+ if (n == 0)
+ n = 9; /* 32 ms = 2^(9-1) uframes */
j = 16;
break;
default: /* USB_SPEED_FULL or _LOW */
@@ -124,6 +130,21 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
endpoint->desc.bInterval = n;
}
+ /* Some buggy low-speed devices have Bulk endpoints, which is
+ * explicitly forbidden by the USB spec. In an attempt to make
+ * them usable, we will try treating them as Interrupt endpoints.
+ */
+ if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&
+ usb_endpoint_xfer_bulk(d)) {
+ dev_warn(ddev, "config %d interface %d altsetting %d "
+ "endpoint 0x%X is Bulk; changing to Interrupt\n",
+ cfgno, inum, asnum, d->bEndpointAddress);
+ endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;
+ endpoint->desc.bInterval = 1;
+ if (le16_to_cpu(endpoint->desc.wMaxPacketSize) > 8)
+ endpoint->desc.wMaxPacketSize = cpu_to_le16(8);
+ }
+
/* Skip over any Class Specific or Vendor Specific descriptors;
* find the next endpoint or interface descriptor */
endpoint->extra = buffer;
@@ -274,6 +295,7 @@ static int usb_parse_configuration(struct device *ddev, int cfgidx,
struct usb_descriptor_header *header;
int len, retval;
u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
+ unsigned iad_num = 0;
memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
if (config->desc.bDescriptorType != USB_DT_CONFIG ||
@@ -351,6 +373,20 @@ static int usb_parse_configuration(struct device *ddev, int cfgidx,
++n;
}
+ } else if (header->bDescriptorType ==
+ USB_DT_INTERFACE_ASSOCIATION) {
+ if (iad_num == USB_MAXIADS) {
+ dev_warn(ddev, "found more Interface "
+ "Association Descriptors "
+ "than allocated for in "
+ "configuration %d\n", cfgno);
+ } else {
+ config->intf_assoc[iad_num] =
+ (struct usb_interface_assoc_descriptor
+ *)header;
+ iad_num++;
+ }
+
} else if (header->bDescriptorType == USB_DT_DEVICE ||
header->bDescriptorType == USB_DT_CONFIG)
dev_warn(ddev, "config %d contains an unexpected "
diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c
index 6753ca059ee4..87c794d60aa0 100644
--- a/drivers/usb/core/devices.c
+++ b/drivers/usb/core/devices.c
@@ -102,6 +102,10 @@ static const char *format_config =
/* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */
"C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n";
+static const char *format_iad =
+/* A: FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */
+ "A: FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n";
+
static const char *format_iface =
/* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/
"I:%c If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n";
@@ -146,6 +150,7 @@ static const struct class_info clas_info[] =
{USB_CLASS_STILL_IMAGE, "still"},
{USB_CLASS_CSCID, "scard"},
{USB_CLASS_CONTENT_SEC, "c-sec"},
+ {USB_CLASS_VIDEO, "video"},
{-1, "unk."} /* leave as last */
};
@@ -286,6 +291,21 @@ static char *usb_dump_interface(
return start;
}
+static char *usb_dump_iad_descriptor(char *start, char *end,
+ const struct usb_interface_assoc_descriptor *iad)
+{
+ if (start > end)
+ return start;
+ start += sprintf(start, format_iad,
+ iad->bFirstInterface,
+ iad->bInterfaceCount,
+ iad->bFunctionClass,
+ class_decode(iad->bFunctionClass),
+ iad->bFunctionSubClass,
+ iad->bFunctionProtocol);
+ return start;
+}
+
/* TBD:
* 0. TBDs
* 1. marking active interface altsettings (code lists all, but should mark
@@ -322,6 +342,12 @@ static char *usb_dump_config (
if (!config) /* getting these some in 2.3.7; none in 2.3.6 */
return start + sprintf(start, "(null Cfg. desc.)\n");
start = usb_dump_config_descriptor(start, end, &config->desc, active);
+ for (i = 0; i < USB_MAXIADS; i++) {
+ if (config->intf_assoc[i] == NULL)
+ break;
+ start = usb_dump_iad_descriptor(start, end,
+ config->intf_assoc[i]);
+ }
for (i = 0; i < config->desc.bNumInterfaces; i++) {
intfc = config->intf_cache[i];
interface = config->interface[i];
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 2619986e5300..73c49362cd47 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -24,10 +24,19 @@
#include <linux/device.h>
#include <linux/usb.h>
+#include <linux/usb/quirks.h>
#include <linux/workqueue.h>
#include "hcd.h"
#include "usb.h"
+#define VERBOSE_DEBUG 0
+
+#if VERBOSE_DEBUG
+#define dev_vdbg dev_dbg
+#else
+#define dev_vdbg(dev, fmt, args...) do { } while (0)
+#endif
+
#ifdef CONFIG_HOTPLUG
/*
@@ -802,18 +811,17 @@ static int usb_suspend_device(struct usb_device *udev, pm_message_t msg)
udev->state == USB_STATE_SUSPENDED)
goto done;
- /* For devices that don't have a driver, we do a standard suspend. */
- if (udev->dev.driver == NULL) {
+ /* For devices that don't have a driver, we do a generic suspend. */
+ if (udev->dev.driver)
+ udriver = to_usb_device_driver(udev->dev.driver);
+ else {
udev->do_remote_wakeup = 0;
- status = usb_port_suspend(udev);
- goto done;
+ udriver = &usb_generic_driver;
}
-
- udriver = to_usb_device_driver(udev->dev.driver);
status = udriver->suspend(udev, msg);
-done:
- // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+ done:
+ dev_vdbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
if (status == 0)
udev->dev.power.power_state.event = msg.event;
return status;
@@ -825,8 +833,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. */
@@ -835,11 +844,14 @@ static int usb_resume_device(struct usb_device *udev)
goto done;
}
+ if (udev->quirks & USB_QUIRK_RESET_RESUME)
+ udev->reset_resume = 1;
+
udriver = to_usb_device_driver(udev->dev.driver);
status = udriver->resume(udev);
-done:
- // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+ done:
+ dev_vdbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
if (status == 0) {
udev->autoresume_disabled = 0;
udev->dev.power.power_state.event = PM_EVENT_ON;
@@ -877,15 +889,13 @@ static int usb_suspend_interface(struct usb_interface *intf, pm_message_t msg)
mark_quiesced(intf);
}
-done:
- // dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
- if (status == 0)
- intf->dev.power.power_state.event = msg.event;
+ done:
+ dev_vdbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
return status;
}
/* 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;
@@ -905,23 +915,37 @@ static int usb_resume_interface(struct usb_interface *intf)
}
driver = to_usb_driver(intf->dev.driver);
- if (driver->resume) {
- status = driver->resume(intf);
- if (status)
- dev_err(&intf->dev, "%s error %d\n",
- "resume", status);
- else
- mark_active(intf);
+ if (reset_resume) {
+ if (driver->reset_resume) {
+ status = driver->reset_resume(intf);
+ if (status)
+ dev_err(&intf->dev, "%s error %d\n",
+ "reset_resume", status);
+ } else {
+ // status = -EOPNOTSUPP;
+ dev_warn(&intf->dev, "no %s for driver %s?\n",
+ "reset_resume", driver->name);
+ }
} else {
- dev_warn(&intf->dev, "no resume for driver %s?\n",
- driver->name);
- mark_active(intf);
+ if (driver->resume) {
+ status = driver->resume(intf);
+ if (status)
+ dev_err(&intf->dev, "%s error %d\n",
+ "resume", status);
+ } else {
+ // status = -EOPNOTSUPP;
+ dev_warn(&intf->dev, "no %s for driver %s?\n",
+ "resume", driver->name);
+ }
}
done:
- // dev_dbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
+ dev_vdbg(&intf->dev, "%s: status %d\n", __FUNCTION__, status);
if (status == 0)
- intf->dev.power.power_state.event = PM_EVENT_ON;
+ mark_active(intf);
+
+ /* FIXME: Unbind the driver and reprobe if the resume failed
+ * (not possible if auto_pm is set) */
return status;
}
@@ -958,6 +982,18 @@ static int autosuspend_check(struct usb_device *udev)
"for autosuspend\n");
return -EOPNOTSUPP;
}
+
+ /* Don't allow autosuspend if the device will need
+ * a reset-resume and any of its interface drivers
+ * doesn't include support.
+ */
+ if (udev->quirks & USB_QUIRK_RESET_RESUME) {
+ struct usb_driver *driver;
+
+ driver = to_usb_driver(intf->dev.driver);
+ if (!driver->reset_resume)
+ return -EOPNOTSUPP;
+ }
}
}
@@ -974,7 +1010,7 @@ static int autosuspend_check(struct usb_device *udev)
* or for the past.
*/
queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend,
- suspend_time - jiffies);
+ round_jiffies_relative(suspend_time - jiffies));
}
return -EAGAIN;
}
@@ -1054,14 +1090,21 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
break;
}
}
- if (status == 0)
+ if (status == 0) {
+
+ /* Non-root devices don't need to do anything for FREEZE
+ * or PRETHAW. */
+ if (udev->parent && (msg.event == PM_EVENT_FREEZE ||
+ msg.event == PM_EVENT_PRETHAW))
+ goto done;
status = usb_suspend_device(udev, msg);
+ }
/* If the suspend failed, resume interfaces that did get suspended */
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 */
@@ -1076,7 +1119,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
done:
- // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+ dev_vdbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
return status;
}
@@ -1131,7 +1174,8 @@ static int usb_resume_both(struct usb_device *udev)
status = usb_autoresume_device(parent);
if (status == 0) {
status = usb_resume_device(udev);
- if (status) {
+ if (status || udev->state ==
+ USB_STATE_NOTATTACHED) {
usb_autosuspend_device(parent);
/* It's possible usb_resume_device()
@@ -1152,28 +1196,25 @@ static int usb_resume_both(struct usb_device *udev)
/* We can't progagate beyond the USB subsystem,
* so if a root hub's controller is suspended
* then we're stuck. */
- if (udev->dev.parent->power.power_state.event !=
- PM_EVENT_ON)
- status = -EHOSTUNREACH;
- else
- status = usb_resume_device(udev);
+ status = usb_resume_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);
+ dev_vdbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status);
+ udev->reset_resume = 0;
return status;
}
@@ -1240,8 +1281,8 @@ void usb_autosuspend_device(struct usb_device *udev)
int status;
status = usb_autopm_do_device(udev, -1);
- // dev_dbg(&udev->dev, "%s: cnt %d\n",
- // __FUNCTION__, udev->pm_usage_cnt);
+ dev_vdbg(&udev->dev, "%s: cnt %d\n",
+ __FUNCTION__, udev->pm_usage_cnt);
}
/**
@@ -1260,8 +1301,8 @@ void usb_autosuspend_device(struct usb_device *udev)
void usb_try_autosuspend_device(struct usb_device *udev)
{
usb_autopm_do_device(udev, 0);
- // dev_dbg(&udev->dev, "%s: cnt %d\n",
- // __FUNCTION__, udev->pm_usage_cnt);
+ dev_vdbg(&udev->dev, "%s: cnt %d\n",
+ __FUNCTION__, udev->pm_usage_cnt);
}
/**
@@ -1288,8 +1329,8 @@ int usb_autoresume_device(struct usb_device *udev)
int status;
status = usb_autopm_do_device(udev, 1);
- // dev_dbg(&udev->dev, "%s: status %d cnt %d\n",
- // __FUNCTION__, status, udev->pm_usage_cnt);
+ dev_vdbg(&udev->dev, "%s: status %d cnt %d\n",
+ __FUNCTION__, status, udev->pm_usage_cnt);
return status;
}
@@ -1361,8 +1402,8 @@ void usb_autopm_put_interface(struct usb_interface *intf)
int status;
status = usb_autopm_do_interface(intf, -1);
- // dev_dbg(&intf->dev, "%s: status %d cnt %d\n",
- // __FUNCTION__, status, intf->pm_usage_cnt);
+ dev_vdbg(&intf->dev, "%s: status %d cnt %d\n",
+ __FUNCTION__, status, intf->pm_usage_cnt);
}
EXPORT_SYMBOL_GPL(usb_autopm_put_interface);
@@ -1405,8 +1446,8 @@ int usb_autopm_get_interface(struct usb_interface *intf)
int status;
status = usb_autopm_do_interface(intf, 1);
- // dev_dbg(&intf->dev, "%s: status %d cnt %d\n",
- // __FUNCTION__, status, intf->pm_usage_cnt);
+ dev_vdbg(&intf->dev, "%s: status %d cnt %d\n",
+ __FUNCTION__, status, intf->pm_usage_cnt);
return status;
}
EXPORT_SYMBOL_GPL(usb_autopm_get_interface);
@@ -1427,8 +1468,8 @@ int usb_autopm_set_interface(struct usb_interface *intf)
int status;
status = usb_autopm_do_interface(intf, 0);
- // dev_dbg(&intf->dev, "%s: status %d cnt %d\n",
- // __FUNCTION__, status, intf->pm_usage_cnt);
+ dev_vdbg(&intf->dev, "%s: status %d cnt %d\n",
+ __FUNCTION__, status, intf->pm_usage_cnt);
return status;
}
EXPORT_SYMBOL_GPL(usb_autopm_set_interface);
@@ -1508,8 +1549,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/file.c b/drivers/usb/core/file.c
index 01c857ac27af..5d860bc9b421 100644
--- a/drivers/usb/core/file.c
+++ b/drivers/usb/core/file.c
@@ -16,15 +16,15 @@
*/
#include <linux/module.h>
-#include <linux/spinlock.h>
#include <linux/errno.h>
+#include <linux/rwsem.h>
#include <linux/usb.h>
#include "usb.h"
#define MAX_USB_MINORS 256
static const struct file_operations *usb_minors[MAX_USB_MINORS];
-static DEFINE_SPINLOCK(minor_lock);
+static DECLARE_RWSEM(minor_rwsem);
static int usb_open(struct inode * inode, struct file * file)
{
@@ -33,14 +33,11 @@ static int usb_open(struct inode * inode, struct file * file)
int err = -ENODEV;
const struct file_operations *old_fops, *new_fops = NULL;
- spin_lock (&minor_lock);
+ down_read(&minor_rwsem);
c = usb_minors[minor];
- if (!c || !(new_fops = fops_get(c))) {
- spin_unlock(&minor_lock);
- return err;
- }
- spin_unlock(&minor_lock);
+ if (!c || !(new_fops = fops_get(c)))
+ goto done;
old_fops = file->f_op;
file->f_op = new_fops;
@@ -52,6 +49,8 @@ static int usb_open(struct inode * inode, struct file * file)
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
+ done:
+ up_read(&minor_rwsem);
return err;
}
@@ -166,7 +165,7 @@ int usb_register_dev(struct usb_interface *intf,
if (class_driver->fops == NULL)
goto exit;
- spin_lock (&minor_lock);
+ down_write(&minor_rwsem);
for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) {
if (usb_minors[minor])
continue;
@@ -176,7 +175,7 @@ int usb_register_dev(struct usb_interface *intf,
retval = 0;
break;
}
- spin_unlock (&minor_lock);
+ up_write(&minor_rwsem);
if (retval)
goto exit;
@@ -197,9 +196,9 @@ int usb_register_dev(struct usb_interface *intf,
intf->usb_dev = device_create(usb_class->class, &intf->dev,
MKDEV(USB_MAJOR, minor), "%s", temp);
if (IS_ERR(intf->usb_dev)) {
- spin_lock (&minor_lock);
+ down_write(&minor_rwsem);
usb_minors[intf->minor] = NULL;
- spin_unlock (&minor_lock);
+ up_write(&minor_rwsem);
retval = PTR_ERR(intf->usb_dev);
}
exit:
@@ -236,9 +235,9 @@ void usb_deregister_dev(struct usb_interface *intf,
dbg ("removing %d minor", intf->minor);
- spin_lock (&minor_lock);
+ down_write(&minor_rwsem);
usb_minors[intf->minor] = NULL;
- spin_unlock (&minor_lock);
+ up_write(&minor_rwsem);
snprintf(name, BUS_ID_SIZE, class_driver->name, intf->minor - minor_base);
device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor));
@@ -247,5 +246,3 @@ void usb_deregister_dev(struct usb_interface *intf,
destroy_usb_class();
}
EXPORT_SYMBOL(usb_deregister_dev);
-
-
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index 9bbcb20e2d94..b2fc2b115256 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -19,6 +19,7 @@
#include <linux/usb.h>
#include "usb.h"
+#include "hcd.h"
static inline const char *plural(int n)
{
@@ -193,16 +194,34 @@ static void generic_disconnect(struct usb_device *udev)
static int generic_suspend(struct usb_device *udev, pm_message_t msg)
{
- /* USB devices enter SUSPEND state through their hubs, but can be
- * marked for FREEZE as soon as their children are already idled.
- * But those semantics are useless, so we equate the two (sigh).
+ int rc;
+
+ /* Normal USB devices suspend through their upstream port.
+ * Root hubs don't have upstream ports to suspend,
+ * so we have to shut down their downstream HC-to-USB
+ * interfaces manually by doing a bus (or "global") suspend.
*/
- return usb_port_suspend(udev);
+ if (!udev->parent)
+ rc = hcd_bus_suspend(udev);
+ else
+ rc = usb_port_suspend(udev);
+ return rc;
}
static int generic_resume(struct usb_device *udev)
{
- return usb_port_resume(udev);
+ int rc;
+
+ /* Normal USB devices resume/reset through their upstream port.
+ * Root hubs don't have upstream ports to resume or reset,
+ * so we have to start up their downstream HC-to-USB
+ * interfaces manually by doing a bus (or "global") resume.
+ */
+ if (!udev->parent)
+ rc = hcd_bus_resume(udev);
+ else
+ rc = usb_port_resume(udev);
+ return rc;
}
#endif /* CONFIG_PM */
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index edf4300a3f7a..5cf6d5f9acbd 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -207,7 +207,8 @@ int usb_hcd_pci_suspend (struct pci_dev *dev, pm_message_t message)
* We must ignore the FREEZE vs SUSPEND distinction here, because
* otherwise the swsusp will save (and restore) garbage state.
*/
- if (hcd->self.root_hub->dev.power.power_state.event == PM_EVENT_ON)
+ if (!(hcd->state == HC_STATE_SUSPENDED ||
+ hcd->state == HC_STATE_HALT))
return -EBUSY;
if (hcd->driver->suspend) {
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 8969e42434b9..963520fbef90 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -582,10 +582,12 @@ void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
}
/* The USB 2.0 spec says 256 ms. This is close enough and won't
- * exceed that limit if HZ is 100. */
+ * exceed that limit if HZ is 100. The math is more clunky than
+ * maybe expected, this is to make sure that all timers for USB devices
+ * fire at the same time to give the CPU a break inbetween */
if (hcd->uses_new_polling ? hcd->poll_rh :
(length == 0 && hcd->status_urb != NULL))
- mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250));
+ mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}
EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status);
@@ -614,8 +616,8 @@ static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
urb->hcpriv = hcd; /* indicate it's queued */
if (!hcd->uses_new_polling)
- mod_timer (&hcd->rh_timer, jiffies +
- msecs_to_jiffies(250));
+ mod_timer (&hcd->rh_timer,
+ (jiffies/(HZ/4) + 1) * (HZ/4));
/* If a status change has already occurred, report it ASAP */
else if (hcd->poll_pending)
@@ -901,17 +903,32 @@ EXPORT_SYMBOL (usb_calc_bus_time);
/*-------------------------------------------------------------------------*/
-static void urb_unlink (struct urb *urb)
+static void urb_unlink(struct usb_hcd *hcd, struct urb *urb)
{
unsigned long flags;
+ int at_root_hub = (urb->dev == hcd->self.root_hub);
/* clear all state linking urb to this dev (and hcd) */
-
spin_lock_irqsave (&hcd_data_lock, flags);
list_del_init (&urb->urb_list);
spin_unlock_irqrestore (&hcd_data_lock, flags);
-}
+ if (hcd->self.uses_dma && !at_root_hub) {
+ if (usb_pipecontrol (urb->pipe)
+ && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
+ dma_unmap_single (hcd->self.controller, urb->setup_dma,
+ sizeof (struct usb_ctrlrequest),
+ DMA_TO_DEVICE);
+ if (urb->transfer_buffer_length != 0
+ && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
+ dma_unmap_single (hcd->self.controller,
+ urb->transfer_dma,
+ urb->transfer_buffer_length,
+ usb_pipein (urb->pipe)
+ ? DMA_FROM_DEVICE
+ : DMA_TO_DEVICE);
+ }
+}
/* may be called in any context with a valid urb->dev usecount
* caller surrenders "ownership" of urb
@@ -948,19 +965,9 @@ int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
else switch (hcd->state) {
case HC_STATE_RUNNING:
case HC_STATE_RESUMING:
-doit:
list_add_tail (&urb->urb_list, &ep->urb_list);
status = 0;
break;
- case HC_STATE_SUSPENDED:
- /* HC upstream links (register access, wakeup signaling) can work
- * even when the downstream links (and DMA etc) are quiesced; let
- * usbcore talk to the root hub.
- */
- if (hcd->self.controller->power.power_state.event == PM_EVENT_ON
- && urb->dev->parent == NULL)
- goto doit;
- /* FALL THROUGH */
default:
status = -ESHUTDOWN;
break;
@@ -1014,7 +1021,7 @@ doit:
status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags);
done:
if (unlikely (status)) {
- urb_unlink (urb);
+ urb_unlink(hcd, urb);
atomic_dec (&urb->use_count);
if (urb->reject)
wake_up (&usb_kill_urb_queue);
@@ -1255,42 +1262,59 @@ rescan:
#ifdef CONFIG_PM
-int hcd_bus_suspend (struct usb_bus *bus)
+int hcd_bus_suspend(struct usb_device *rhdev)
{
- struct usb_hcd *hcd;
- int status;
+ struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self);
+ int status;
+ int old_state = hcd->state;
- hcd = container_of (bus, struct usb_hcd, self);
- if (!hcd->driver->bus_suspend)
- return -ENOENT;
- hcd->state = HC_STATE_QUIESCING;
- status = hcd->driver->bus_suspend (hcd);
- if (status == 0)
+ dev_dbg(&rhdev->dev, "bus %s%s\n",
+ rhdev->auto_pm ? "auto-" : "", "suspend");
+ if (!hcd->driver->bus_suspend) {
+ status = -ENOENT;
+ } else {
+ hcd->state = HC_STATE_QUIESCING;
+ status = hcd->driver->bus_suspend(hcd);
+ }
+ if (status == 0) {
+ usb_set_device_state(rhdev, USB_STATE_SUSPENDED);
hcd->state = HC_STATE_SUSPENDED;
- else
- dev_dbg(&bus->root_hub->dev, "%s fail, err %d\n",
+ } else {
+ hcd->state = old_state;
+ dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
"suspend", status);
+ }
return status;
}
-int hcd_bus_resume (struct usb_bus *bus)
+int hcd_bus_resume(struct usb_device *rhdev)
{
- struct usb_hcd *hcd;
- int status;
+ struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self);
+ int status;
+ int old_state = hcd->state;
- hcd = container_of (bus, struct usb_hcd, self);
+ dev_dbg(&rhdev->dev, "usb %s%s\n",
+ rhdev->auto_pm ? "auto-" : "", "resume");
if (!hcd->driver->bus_resume)
return -ENOENT;
if (hcd->state == HC_STATE_RUNNING)
return 0;
+
hcd->state = HC_STATE_RESUMING;
- status = hcd->driver->bus_resume (hcd);
- if (status == 0)
+ status = hcd->driver->bus_resume(hcd);
+ if (status == 0) {
+ /* TRSMRCY = 10 msec */
+ msleep(10);
+ usb_set_device_state(rhdev, rhdev->actconfig
+ ? USB_STATE_CONFIGURED
+ : USB_STATE_ADDRESS);
hcd->state = HC_STATE_RUNNING;
- else {
- dev_dbg(&bus->root_hub->dev, "%s fail, err %d\n",
+ } else {
+ hcd->state = old_state;
+ dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
"resume", status);
- usb_hc_died(hcd);
+ if (status != -ESHUTDOWN)
+ usb_hc_died(hcd);
}
return status;
}
@@ -1384,30 +1408,10 @@ EXPORT_SYMBOL (usb_bus_start_enum);
*/
void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb)
{
- int at_root_hub;
-
- at_root_hub = (urb->dev == hcd->self.root_hub);
- urb_unlink (urb);
-
- /* lower level hcd code should use *_dma exclusively if the
- * host controller does DMA */
- if (hcd->self.uses_dma && !at_root_hub) {
- if (usb_pipecontrol (urb->pipe)
- && !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
- dma_unmap_single (hcd->self.controller, urb->setup_dma,
- sizeof (struct usb_ctrlrequest),
- DMA_TO_DEVICE);
- if (urb->transfer_buffer_length != 0
- && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
- dma_unmap_single (hcd->self.controller,
- urb->transfer_dma,
- urb->transfer_buffer_length,
- usb_pipein (urb->pipe)
- ? DMA_FROM_DEVICE
- : DMA_TO_DEVICE);
- }
-
+ urb_unlink(hcd, urb);
usbmon_urb_complete (&hcd->self, urb);
+ usb_unanchor_urb(urb);
+
/* pass ownership to the completion handler */
urb->complete (urb);
atomic_dec (&urb->use_count);
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index ef50fa494e47..b5ebb73c2332 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -364,23 +364,13 @@ extern int usb_find_interface_driver (struct usb_device *dev,
#ifdef CONFIG_PM
extern void usb_hcd_resume_root_hub (struct usb_hcd *hcd);
extern void usb_root_hub_lost_power (struct usb_device *rhdev);
-extern int hcd_bus_suspend (struct usb_bus *bus);
-extern int hcd_bus_resume (struct usb_bus *bus);
+extern int hcd_bus_suspend(struct usb_device *rhdev);
+extern int hcd_bus_resume(struct usb_device *rhdev);
#else
static inline void usb_hcd_resume_root_hub(struct usb_hcd *hcd)
{
return;
}
-
-static inline int hcd_bus_suspend(struct usb_bus *bus)
-{
- return 0;
-}
-
-static inline int hcd_bus_resume (struct usb_bus *bus)
-{
- return 0;
-}
#endif /* CONFIG_PM */
/*
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 24f10a19dbdb..fd74c50b1804 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -31,9 +31,16 @@
#include "hcd.h"
#include "hub.h"
+#ifdef CONFIG_USB_PERSIST
+#define USB_PERSIST 1
+#else
+#define USB_PERSIST 0
+#endif
+
struct usb_hub {
struct device *intfdev; /* the "interface" device */
struct usb_device *hdev;
+ struct kref kref;
struct urb *urb; /* for interrupt polling pipe */
/* buffer for urb ... with extra space in case of babble */
@@ -66,6 +73,7 @@ struct usb_hub {
unsigned limited_power:1;
unsigned quiescing:1;
unsigned activating:1;
+ unsigned disconnected:1;
unsigned has_indicators:1;
u8 indicator[USB_MAXCHILDREN];
@@ -321,7 +329,7 @@ static void kick_khubd(struct usb_hub *hub)
to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
spin_lock_irqsave(&hub_event_lock, flags);
- if (list_empty(&hub->event_list)) {
+ if (!hub->disconnected & list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
wake_up(&khubd_wait);
}
@@ -330,6 +338,7 @@ static void kick_khubd(struct usb_hub *hub)
void usb_kick_khubd(struct usb_device *hdev)
{
+ /* FIXME: What if hdev isn't bound to the hub driver? */
kick_khubd(hdev_to_hub(hdev));
}
@@ -400,9 +409,10 @@ static void hub_tt_kevent (struct work_struct *work)
struct usb_hub *hub =
container_of(work, struct usb_hub, tt.kevent);
unsigned long flags;
+ int limit = 100;
spin_lock_irqsave (&hub->tt.lock, flags);
- while (!list_empty (&hub->tt.clear_list)) {
+ while (--limit && !list_empty (&hub->tt.clear_list)) {
struct list_head *temp;
struct usb_tt_clear *clear;
struct usb_device *hdev = hub->hdev;
@@ -550,48 +560,68 @@ 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);
+
+ /* 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);
+}
/* caller has locked the hub device */
-static void hub_pre_reset(struct usb_interface *intf)
+static int hub_pre_reset(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
struct usb_device *hdev = hub->hdev;
- int port1;
+ int i;
- 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);
- }
+ /* Disconnect all the children */
+ for (i = 0; i < hdev->maxchild; ++i) {
+ if (hdev->children[i])
+ usb_disconnect(&hdev->children[i]);
}
hub_quiesce(hub);
+ return 0;
}
/* caller has locked the hub device */
-static void hub_post_reset(struct usb_interface *intf)
+static int hub_post_reset(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
- hub_activate(hub);
hub_power_on(hub);
+ hub_activate(hub);
+ return 0;
}
-
static int hub_configure(struct usb_hub *hub,
struct usb_endpoint_descriptor *endpoint)
{
@@ -845,43 +875,42 @@ fail:
return ret;
}
+static void hub_release(struct kref *kref)
+{
+ struct usb_hub *hub = container_of(kref, struct usb_hub, kref);
+
+ usb_put_intf(to_usb_interface(hub->intfdev));
+ kfree(hub);
+}
+
static unsigned highspeed_hubs;
static void hub_disconnect(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata (intf);
- struct usb_device *hdev;
+
+ /* Take the hub off the event list and don't let it be added again */
+ spin_lock_irq(&hub_event_lock);
+ list_del_init(&hub->event_list);
+ hub->disconnected = 1;
+ spin_unlock_irq(&hub_event_lock);
/* Disconnect all children and quiesce the hub */
hub->error = 0;
hub_pre_reset(intf);
usb_set_intfdata (intf, NULL);
- hdev = hub->hdev;
- if (hdev->speed == USB_SPEED_HIGH)
+ if (hub->hdev->speed == USB_SPEED_HIGH)
highspeed_hubs--;
usb_free_urb(hub->urb);
- hub->urb = NULL;
-
- spin_lock_irq(&hub_event_lock);
- list_del_init(&hub->event_list);
- spin_unlock_irq(&hub_event_lock);
-
kfree(hub->descriptor);
- hub->descriptor = NULL;
-
kfree(hub->status);
- hub->status = NULL;
-
- if (hub->buffer) {
- usb_buffer_free(hdev, sizeof(*hub->buffer), hub->buffer,
- hub->buffer_dma);
- hub->buffer = NULL;
- }
+ usb_buffer_free(hub->hdev, sizeof(*hub->buffer), hub->buffer,
+ hub->buffer_dma);
- kfree(hub);
+ kref_put(&hub->kref, hub_release);
}
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
@@ -929,10 +958,12 @@ descriptor_error:
return -ENOMEM;
}
+ kref_init(&hub->kref);
INIT_LIST_HEAD(&hub->event_list);
hub->intfdev = &intf->dev;
hub->hdev = hdev;
INIT_DELAYED_WORK(&hub->leds, led_work);
+ usb_get_intf(intf);
usb_set_intfdata (intf, hub);
intf->needs_remote_wakeup = 1;
@@ -982,49 +1013,6 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data)
}
-/* grab device/port lock, returning index of that port (zero based).
- * protects the upstream link used by this device from concurrent
- * tree operations like suspend, resume, reset, and disconnect, which
- * apply to everything downstream of a given port.
- */
-static int locktree(struct usb_device *udev)
-{
- int t;
- struct usb_device *hdev;
-
- if (!udev)
- return -ENODEV;
-
- /* root hub is always the first lock in the series */
- hdev = udev->parent;
- if (!hdev) {
- usb_lock_device(udev);
- return 0;
- }
-
- /* on the path from root to us, lock everything from
- * top down, dropping parent locks when not needed
- */
- t = locktree(hdev);
- if (t < 0)
- return t;
-
- /* everything is fail-fast once disconnect
- * processing starts
- */
- if (udev->state == USB_STATE_NOTATTACHED) {
- usb_unlock_device(hdev);
- return -ENODEV;
- }
-
- /* when everyone grabs locks top->bottom,
- * non-overlapping work may be concurrent
- */
- usb_lock_device(udev);
- usb_unlock_device(hdev);
- return udev->portnum;
-}
-
static void recursively_mark_NOTATTACHED(struct usb_device *udev)
{
int i;
@@ -1089,46 +1077,6 @@ void usb_set_device_state(struct usb_device *udev,
spin_unlock_irqrestore(&device_state_lock, flags);
}
-
-#ifdef CONFIG_PM
-
-/**
- * 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.
- */
-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");
-
- /* Make sure no potential wakeup events get lost,
- * by forcing the root hub to be resumed.
- */
- rhdev->dev.power.prev_state.event = PM_EVENT_ON;
-
- 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);
-
-#endif /* CONFIG_PM */
-
static void choose_address(struct usb_device *udev)
{
int devnum;
@@ -1269,7 +1217,6 @@ static inline void show_string(struct usb_device *udev, char *id, char *string)
#ifdef CONFIG_USB_OTG
#include "otg_whitelist.h"
-static int __usb_port_suspend(struct usb_device *, int port1);
#endif
/**
@@ -1375,11 +1322,11 @@ int usb_new_device(struct usb_device *udev)
* (Includes HNP test device.)
*/
if (udev->bus->b_hnp_enable || udev->bus->is_b_host) {
- err = __usb_port_suspend(udev, udev->bus->otg_port);
+ err = usb_port_suspend(udev);
if (err < 0)
dev_dbg(&udev->dev, "HNP fail, %d\n", err);
}
- err = -ENODEV;
+ err = -ENOTSUPP;
goto fail;
}
#endif
@@ -1476,9 +1423,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (!(portstatus & USB_PORT_STAT_CONNECTION))
return -ENOTCONN;
- /* bomb out completely if something weird happened */
+ /* bomb out completely if the connection bounced */
if ((portchange & USB_PORT_STAT_C_CONNECTION))
- return -EINVAL;
+ return -ENOTCONN;
/* if we`ve finished resetting, then break out of the loop */
if (!(portstatus & USB_PORT_STAT_RESET) &&
@@ -1557,34 +1504,24 @@ 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
/*
+ * usb_port_suspend - suspend a usb device's upstream port
+ * @udev: device that's no longer in active use, not a root hub
+ * Context: must be able to sleep; device not locked; pm locks held
+ *
+ * Suspends a USB device that isn't in active use, conserving power.
+ * Devices may wake out of a suspend, if anything important happens,
+ * using the remote wakeup mechanism. They may also be taken out of
+ * suspend by the host, using usb_port_resume(). It's also routine
+ * to disconnect devices while they are suspended.
+ *
+ * This only affects the USB hardware for a device; its interfaces
+ * (and, for hubs, child devices) must already have been suspended.
+ *
* Selective port suspend reduces power; most suspended devices draw
* less than 500 uA. It's also used in OTG, along with remote wakeup.
* All devices below the suspended port are also suspended.
@@ -1593,11 +1530,35 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
* also support "remote wakeup", where the device can activate the USB
* tree above them to deliver data, such as a keypress or packet. In
* some cases, this wakes the USB host.
+ *
+ * Suspending OTG devices may trigger HNP, if that's been enabled
+ * between a pair of dual-role devices. That will change roles, such
+ * as from A-Host to A-Peripheral or from B-Host back to B-Peripheral.
+ *
+ * Devices on USB hub ports have only one "suspend" state, corresponding
+ * to ACPI D2, "may cause the device to lose some context".
+ * State transitions include:
+ *
+ * - suspend, resume ... when the VBUS power link stays live
+ * - suspend, disconnect ... VBUS lost
+ *
+ * Once VBUS drop breaks the circuit, the port it's using has to go through
+ * normal re-enumeration procedures, starting with enabling VBUS power.
+ * Other than re-initializing the hub (plug/unplug, except for root hubs),
+ * Linux (2.6) currently has NO mechanisms to initiate that: no khubd
+ * timer, no SRP, no requests through sysfs.
+ *
+ * If CONFIG_USB_SUSPEND isn't enabled, devices only really suspend when
+ * the root hub for their bus goes into global suspend ... so we don't
+ * (falsely) update the device power state to say it suspended.
+ *
+ * Returns 0 on success, else negative errno.
*/
-static int hub_port_suspend(struct usb_hub *hub, int port1,
- struct usb_device *udev)
+int usb_port_suspend(struct usb_device *udev)
{
- int status;
+ struct usb_hub *hub = hdev_to_hub(udev->parent);
+ int port1 = udev->portnum;
+ int status;
// dev_dbg(hub->intfdev, "suspend port %d\n", port1);
@@ -1614,17 +1575,15 @@ static int hub_port_suspend(struct usb_hub *hub, int port1,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (status)
- dev_dbg(&udev->dev,
- "won't remote wakeup, status %d\n",
- status);
+ dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",
+ status);
}
/* see 7.1.7.6 */
status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND);
if (status) {
- dev_dbg(hub->intfdev,
- "can't suspend port %d, status %d\n",
- port1, status);
+ dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n",
+ port1, status);
/* paranoia: "should not happen" */
(void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
@@ -1642,85 +1601,24 @@ static int hub_port_suspend(struct usb_hub *hub, int port1,
}
/*
- * Devices on USB hub ports have only one "suspend" state, corresponding
- * to ACPI D2, "may cause the device to lose some context".
- * State transitions include:
- *
- * - suspend, resume ... when the VBUS power link stays live
- * - suspend, disconnect ... VBUS lost
- *
- * Once VBUS drop breaks the circuit, the port it's using has to go through
- * normal re-enumeration procedures, starting with enabling VBUS power.
- * Other than re-initializing the hub (plug/unplug, except for root hubs),
- * Linux (2.6) currently has NO mechanisms to initiate that: no khubd
- * timer, no SRP, no requests through sysfs.
- *
- * If CONFIG_USB_SUSPEND isn't enabled, devices only really suspend when
- * the root hub for their bus goes into global suspend ... so we don't
- * (falsely) update the device power state to say it suspended.
- */
-static int __usb_port_suspend (struct usb_device *udev, int port1)
-{
- int status = 0;
-
- /* caller owns the udev device lock */
- if (port1 < 0)
- return port1;
-
- /* we change the device's upstream USB link,
- * but root hubs have no upstream USB link.
- */
- if (udev->parent)
- status = hub_port_suspend(hdev_to_hub(udev->parent), port1,
- udev);
- else {
- dev_dbg(&udev->dev, "usb %ssuspend\n",
- udev->auto_pm ? "auto-" : "");
- usb_set_device_state(udev, USB_STATE_SUSPENDED);
- }
- return status;
-}
-
-/*
- * usb_port_suspend - suspend a usb device's upstream port
- * @udev: device that's no longer in active use
- * Context: must be able to sleep; device not locked; pm locks held
- *
- * Suspends a USB device that isn't in active use, conserving power.
- * Devices may wake out of a suspend, if anything important happens,
- * using the remote wakeup mechanism. They may also be taken out of
- * suspend by the host, using usb_port_resume(). It's also routine
- * to disconnect devices while they are suspended.
- *
- * This only affects the USB hardware for a device; its interfaces
- * (and, for hubs, child devices) must already have been suspended.
- *
- * Suspending OTG devices may trigger HNP, if that's been enabled
- * between a pair of dual-role devices. That will change roles, such
- * as from A-Host to A-Peripheral or from B-Host back to B-Peripheral.
- *
- * Returns 0 on success, else negative errno.
- */
-int usb_port_suspend(struct usb_device *udev)
-{
- return __usb_port_suspend(udev, udev->portnum);
-}
-
-/*
* If the USB "suspend" state is in use (rather than "global suspend"),
* many devices will be individually taken out of suspend state using
- * special" resume" signaling. These routines kick in shortly after
+ * special "resume" signaling. This routine kicks in shortly after
* hardware resume signaling is finished, either because of selective
* resume (by host) or remote wakeup (by device) ... now see what changed
* in the tree that's rooted at this device.
+ *
+ * If @udev->reset_resume is set then the device is reset before the
+ * status check is done.
*/
static int finish_port_resume(struct usb_device *udev)
{
- int status;
+ int status = 0;
u16 devstatus;
/* caller owns the udev device lock */
- dev_dbg(&udev->dev, "finish resume\n");
+ dev_dbg(&udev->dev, "finish %sresume\n",
+ udev->reset_resume ? "reset-" : "");
/* usb ch9 identifies four variants of SUSPENDED, based on what
* state the device resumes to. Linux currently won't see the
@@ -1731,22 +1629,30 @@ static int finish_port_resume(struct usb_device *udev)
? USB_STATE_CONFIGURED
: USB_STATE_ADDRESS);
+ /* 10.5.4.5 says not to reset a suspended port if the attached
+ * device is enabled for remote wakeup. Hence the reset
+ * operation is carried out here, after the port has been
+ * resumed.
+ */
+ if (udev->reset_resume)
+ status = usb_reset_device(udev);
+
/* 10.5.4.5 says be sure devices in the tree are still there.
* For now let's assume the device didn't go crazy on resume,
* and device drivers will know about any resume quirks.
*/
- status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
- if (status >= 0)
- status = (status == 2 ? 0 : -ENODEV);
+ if (status == 0) {
+ status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
+ if (status >= 0)
+ status = (status == 2 ? 0 : -ENODEV);
+ }
- if (status)
- dev_dbg(&udev->dev,
- "gone after usb resume? status %d\n",
- status);
- else if (udev->actconfig) {
+ if (status) {
+ dev_dbg(&udev->dev, "gone after usb resume? status %d\n",
+ status);
+ } else if (udev->actconfig) {
le16_to_cpus(&devstatus);
- if ((devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP))
- && udev->parent) {
+ if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) {
status = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE,
@@ -1759,19 +1665,52 @@ static int finish_port_resume(struct usb_device *udev)
"wakeup, status %d\n", status);
}
status = 0;
-
- } else if (udev->devnum <= 0) {
- dev_dbg(&udev->dev, "bogus resume!\n");
- status = -EINVAL;
}
return status;
}
-static int
-hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
+/*
+ * usb_port_resume - re-activate a suspended usb device's upstream port
+ * @udev: device to re-activate, not a root hub
+ * Context: must be able to sleep; device not locked; pm locks held
+ *
+ * This will re-activate the suspended device, increasing power usage
+ * while letting drivers communicate again with its endpoints.
+ * USB resume explicitly guarantees that the power session between
+ * the host and the device is the same as it was when the device
+ * suspended.
+ *
+ * If CONFIG_USB_PERSIST and @udev->reset_resume are both set then this
+ * routine won't check that the port is still enabled. Furthermore,
+ * if @udev->reset_resume is set then finish_port_resume() above will
+ * reset @udev. The end result is that a broken power session can be
+ * recovered and @udev will appear to persist across a loss of VBUS power.
+ *
+ * For example, 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. Using the USB_PERSIST facility, the device can be
+ * made to appear as if it had not disconnected.
+ *
+ * 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.
+ *
+ * Returns 0 on success, else negative errno.
+ */
+int usb_port_resume(struct usb_device *udev)
{
- int status;
- u16 portchange, portstatus;
+ struct usb_hub *hub = hdev_to_hub(udev->parent);
+ int port1 = udev->portnum;
+ int status;
+ u16 portchange, portstatus;
+ unsigned mask_flags, want_flags;
/* Skip the initial Clear-Suspend step for a remote wakeup */
status = hub_port_status(hub, port1, &portstatus, &portchange);
@@ -1786,30 +1725,31 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
status = clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_SUSPEND);
if (status) {
- dev_dbg(hub->intfdev,
- "can't resume port %d, status %d\n",
- port1, status);
+ dev_dbg(hub->intfdev, "can't resume port %d, status %d\n",
+ port1, status);
} else {
/* drive resume for at least 20 msec */
- if (udev)
- dev_dbg(&udev->dev, "usb %sresume\n",
- udev->auto_pm ? "auto-" : "");
+ dev_dbg(&udev->dev, "usb %sresume\n",
+ udev->auto_pm ? "auto-" : "");
msleep(25);
-#define LIVE_FLAGS ( USB_PORT_STAT_POWER \
- | USB_PORT_STAT_ENABLE \
- | USB_PORT_STAT_CONNECTION)
-
/* Virtual root hubs can trigger on GET_PORT_STATUS to
* stop resume signaling. Then finish the resume
* sequence.
*/
status = hub_port_status(hub, port1, &portstatus, &portchange);
-SuspendCleared:
- if (status < 0
- || (portstatus & LIVE_FLAGS) != LIVE_FLAGS
- || (portstatus & USB_PORT_STAT_SUSPEND) != 0
- ) {
+
+ SuspendCleared:
+ if (USB_PERSIST && udev->reset_resume)
+ want_flags = USB_PORT_STAT_POWER
+ | USB_PORT_STAT_CONNECTION;
+ else
+ want_flags = USB_PORT_STAT_POWER
+ | USB_PORT_STAT_CONNECTION
+ | USB_PORT_STAT_ENABLE;
+ mask_flags = want_flags | USB_PORT_STAT_SUSPEND;
+
+ if (status < 0 || (portstatus & mask_flags) != want_flags) {
dev_dbg(hub->intfdev,
"port %d status %04x.%04x after resume, %d\n",
port1, portchange, portstatus, status);
@@ -1821,51 +1761,19 @@ SuspendCleared:
USB_PORT_FEAT_C_SUSPEND);
/* TRSMRCY = 10 msec */
msleep(10);
- if (udev)
- status = finish_port_resume(udev);
}
}
- if (status < 0)
- hub_port_logical_disconnect(hub, port1);
clear_bit(port1, hub->busy_bits);
if (!hub->hdev->parent && !hub->busy_bits[0])
usb_enable_root_hub_irq(hub->hdev->bus);
- return status;
-}
-
-/*
- * usb_port_resume - re-activate a suspended usb device's upstream port
- * @udev: device to re-activate
- * Context: must be able to sleep; device not locked; pm locks held
- *
- * This will re-activate the suspended device, increasing power usage
- * while letting drivers communicate again with its endpoints.
- * USB resume explicitly guarantees that the power session between
- * the host and the device is the same as it was when the device
- * suspended.
- *
- * Returns 0 on success, else negative errno.
- */
-int usb_port_resume(struct usb_device *udev)
-{
- int status;
-
- /* we change the device's upstream USB link,
- * but root hubs have no upstream USB link.
- */
- if (udev->parent) {
- // NOTE this fails if parent is also suspended...
- status = hub_port_resume(hdev_to_hub(udev->parent),
- udev->portnum, udev);
- } else {
- dev_dbg(&udev->dev, "usb %sresume\n",
- udev->auto_pm ? "auto-" : "");
+ if (status == 0)
status = finish_port_resume(udev);
- }
- if (status < 0)
+ if (status < 0) {
dev_dbg(&udev->dev, "can't resume, status %d\n", status);
+ hub_port_logical_disconnect(hub, port1);
+ }
return status;
}
@@ -1892,21 +1800,16 @@ int usb_port_suspend(struct usb_device *udev)
return 0;
}
-static inline int
-finish_port_resume(struct usb_device *udev)
-{
- return 0;
-}
-
-static inline int
-hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
-{
- return 0;
-}
-
int usb_port_resume(struct usb_device *udev)
{
- return 0;
+ int status = 0;
+
+ /* However we may need to do a reset-resume */
+ if (udev->reset_resume) {
+ dev_dbg(&udev->dev, "reset-resume\n");
+ status = usb_reset_device(udev);
+ }
+ return status;
}
static inline int remote_wakeup(struct usb_device *udev)
@@ -1921,7 +1824,6 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
struct usb_hub *hub = usb_get_intfdata (intf);
struct usb_device *hdev = hub->hdev;
unsigned port1;
- int status = 0;
/* fail if children aren't already suspended */
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
@@ -1947,49 +1849,75 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
/* stop khubd and related activity */
hub_quiesce(hub);
-
- /* "global suspend" of the downstream HC-to-USB interface */
- if (!hdev->parent) {
- status = hcd_bus_suspend(hdev->bus);
- if (status != 0) {
- dev_dbg(&hdev->dev, "'global' suspend %d\n", status);
- hub_activate(hub);
- }
- }
- return status;
+ return 0;
}
static int hub_resume(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata (intf);
- struct usb_device *hdev = hub->hdev;
- int status;
dev_dbg(&intf->dev, "%s\n", __FUNCTION__);
- /* "global resume" of the downstream HC-to-USB interface */
- if (!hdev->parent) {
- struct usb_bus *bus = hdev->bus;
- if (bus) {
- status = hcd_bus_resume (bus);
- if (status) {
- dev_dbg(&intf->dev, "'global' resume %d\n",
- status);
- return status;
+ /* tell khubd to look for changes on this hub */
+ hub_activate(hub);
+ return 0;
+}
+
+static int hub_reset_resume(struct usb_interface *intf)
+{
+ struct usb_hub *hub = usb_get_intfdata(intf);
+ struct usb_device *hdev = hub->hdev;
+ int port1;
+
+ hub_power_on(hub);
+
+ for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
+ struct usb_device *child = hdev->children[port1-1];
+
+ if (child) {
+
+ /* For "USB_PERSIST"-enabled children we must
+ * mark the child device for reset-resume and
+ * turn off the connect-change status to prevent
+ * khubd from disconnecting it later.
+ */
+ if (USB_PERSIST && child->persist_enabled) {
+ child->reset_resume = 1;
+ clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_CONNECTION);
+
+ /* Otherwise we must disconnect the child,
+ * but as we may not lock the child device here
+ * we have to do a "logical" disconnect.
+ */
+ } else {
+ hub_port_logical_disconnect(hub, port1);
}
- } else
- return -EOPNOTSUPP;
- if (status == 0) {
- /* TRSMRCY = 10 msec */
- msleep(10);
}
}
- /* tell khubd to look for changes on this hub */
hub_activate(hub);
return 0;
}
+/**
+ * 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 @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)
+{
+ dev_warn(&rhdev->dev, "root hub lost power or was reset\n");
+ rhdev->reset_resume = 1;
+}
+EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
+
#else /* CONFIG_PM */
static inline int remote_wakeup(struct usb_device *udev)
@@ -1997,8 +1925,9 @@ static inline int remote_wakeup(struct usb_device *udev)
return 0;
}
-#define hub_suspend NULL
-#define hub_resume NULL
+#define hub_suspend NULL
+#define hub_resume NULL
+#define hub_reset_resume NULL
#endif
@@ -2461,19 +2390,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
return;
}
-#ifdef CONFIG_USB_SUSPEND
- /* If something is connected, but the port is suspended, wake it up. */
- if (portstatus & USB_PORT_STAT_SUSPEND) {
- status = hub_port_resume(hub, port1, NULL);
- if (status < 0) {
- dev_dbg(hub_dev,
- "can't clear suspend on port %d; %d\n",
- port1, status);
- goto done;
- }
- }
-#endif
-
for (i = 0; i < SET_CONFIG_TRIES; i++) {
struct usb_device *udev;
@@ -2584,7 +2500,7 @@ loop:
ep0_reinit(udev);
release_address(udev);
usb_put_dev(udev);
- if (status == -ENOTCONN)
+ if ((status == -ENOTCONN) || (status == -ENOTSUPP))
break;
}
@@ -2625,10 +2541,12 @@ static void hub_events(void)
list_del_init(tmp);
hub = list_entry(tmp, struct usb_hub, event_list);
- hdev = hub->hdev;
- intf = to_usb_interface(hub->intfdev);
- hub_dev = &intf->dev;
+ kref_get(&hub->kref);
+ spin_unlock_irq(&hub_event_lock);
+ hdev = hub->hdev;
+ hub_dev = hub->intfdev;
+ intf = to_usb_interface(hub_dev);
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
hdev->state, hub->descriptor
? hub->descriptor->bNbrPorts
@@ -2637,16 +2555,10 @@ static void hub_events(void)
(u16) hub->change_bits[0],
(u16) hub->event_bits[0]);
- usb_get_intf(intf);
- spin_unlock_irq(&hub_event_lock);
-
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
- if (locktree(hdev) < 0) {
- usb_put_intf(intf);
- continue;
- }
- if (hub != usb_get_intfdata(intf))
+ usb_lock_device(hdev);
+ if (unlikely(hub->disconnected))
goto loop;
/* If the hub has died, clean up after it */
@@ -2809,13 +2721,14 @@ loop_autopm:
usb_autopm_enable(intf);
loop:
usb_unlock_device(hdev);
- usb_put_intf(intf);
+ kref_put(&hub->kref, hub_release);
} /* end while (1) */
}
static int hub_thread(void *__unused)
{
+ set_freezable();
do {
hub_events();
wait_event_interruptible(khubd_wait,
@@ -2844,6 +2757,7 @@ static struct usb_driver hub_driver = {
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
+ .reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.ioctl = hub_ioctl,
@@ -2946,6 +2860,11 @@ static int config_descriptors_changed(struct usb_device *udev)
* this from a driver probe() routine after downloading new firmware.
* For calls that might not occur during probe(), drivers should lock
* the device using usb_lock_device_for_reset().
+ *
+ * Locking exception: This routine may also be called from within an
+ * autoresume handler. Such usage won't conflict with other tasks
+ * holding the device lock because these tasks should always call
+ * usb_autopm_resume_device(), thereby preventing any unwanted autoresume.
*/
int usb_reset_device(struct usb_device *udev)
{
@@ -2976,7 +2895,7 @@ int usb_reset_device(struct usb_device *udev)
* Other endpoints will be handled by re-enumeration. */
ep0_reinit(udev);
ret = hub_port_init(parent_hub, udev, port1, i);
- if (ret >= 0)
+ if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
break;
}
clear_bit(port1, parent_hub->busy_bits);
@@ -3092,6 +3011,7 @@ int usb_reset_composite_device(struct usb_device *udev,
drv = to_usb_driver(cintf->dev.driver);
if (drv->pre_reset)
(drv->pre_reset)(cintf);
+ /* FIXME: Unbind if pre_reset returns an error or isn't defined */
}
}
}
@@ -3110,6 +3030,7 @@ int usb_reset_composite_device(struct usb_device *udev,
drv = to_usb_driver(cintf->dev.driver);
if (drv->post_reset)
(drv->post_reset)(cintf);
+ /* FIXME: Unbind if post_reset returns an error or isn't defined */
}
if (cintf != iface)
up(&cintf->dev.sem);
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index f9fed34bf7d8..530e854961ce 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -404,8 +404,6 @@ int usb_sg_init (
io->urbs [i]->complete = sg_complete;
io->urbs [i]->context = io;
- io->urbs [i]->status = -EINPROGRESS;
- io->urbs [i]->actual_length = 0;
/*
* Some systems need to revert to PIO when DMA is temporarily
@@ -499,7 +497,8 @@ void usb_sg_wait (struct usb_sg_request *io)
/* queue the urbs. */
spin_lock_irq (&io->lock);
- for (i = 0; i < entries && !io->status; i++) {
+ i = 0;
+ while (i < entries && !io->status) {
int retval;
io->urbs [i]->dev = io->dev;
@@ -516,7 +515,6 @@ void usb_sg_wait (struct usb_sg_request *io)
case -ENOMEM:
io->urbs[i]->dev = NULL;
retval = 0;
- i--;
yield ();
break;
@@ -527,6 +525,7 @@ void usb_sg_wait (struct usb_sg_request *io)
* URBs are queued at once; N milliseconds?
*/
case 0:
+ ++i;
cpu_relax ();
break;
@@ -1385,6 +1384,36 @@ struct device_type usb_if_device_type = {
.uevent = usb_if_uevent,
};
+static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev,
+ struct usb_host_config *config,
+ u8 inum)
+{
+ struct usb_interface_assoc_descriptor *retval = NULL;
+ struct usb_interface_assoc_descriptor *intf_assoc;
+ int first_intf;
+ int last_intf;
+ int i;
+
+ for (i = 0; (i < USB_MAXIADS && config->intf_assoc[i]); i++) {
+ intf_assoc = config->intf_assoc[i];
+ if (intf_assoc->bInterfaceCount == 0)
+ continue;
+
+ first_intf = intf_assoc->bFirstInterface;
+ last_intf = first_intf + (intf_assoc->bInterfaceCount - 1);
+ if (inum >= first_intf && inum <= last_intf) {
+ if (!retval)
+ retval = intf_assoc;
+ else
+ dev_err(&dev->dev, "Interface #%d referenced"
+ " by multiple IADs\n", inum);
+ }
+ }
+
+ return retval;
+}
+
+
/*
* usb_set_configuration - Makes a particular device setting be current
* @dev: the device whose configuration is being updated
@@ -1531,6 +1560,7 @@ free_interfaces:
intfc = cp->intf_cache[i];
intf->altsetting = intfc->altsetting;
intf->num_altsetting = intfc->num_altsetting;
+ intf->intf_assoc = find_iad(dev, cp, i);
kref_get(&intfc->ref);
alt = usb_altnum_to_altsetting(intf, 0);
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 739f520908aa..aa21b38a31ce 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -30,10 +30,28 @@
static const struct usb_device_id usb_quirk_list[] = {
/* HP 5300/5370C scanner */
{ USB_DEVICE(0x03f0, 0x0701), .driver_info = USB_QUIRK_STRING_FETCH_255 },
+ /* Benq S2W 3300U */
+ { USB_DEVICE(0x04a5, 0x20b0), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ /* Seiko Epson Corp. Perfection 1200 */
+ { USB_DEVICE(0x04b8, 0x0104), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
/* Seiko Epson Corp - Perfection 1670 */
{ USB_DEVICE(0x04b8, 0x011f), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ /* Samsung ML-2510 Series printer */
+ { USB_DEVICE(0x04e8, 0x327e), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
/* Elsa MicroLink 56k (V.250) */
{ USB_DEVICE(0x05cc, 0x2267), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ /* Ultima Electronics Corp.*/
+ { USB_DEVICE(0x05d8, 0x4005), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ /* Umax [hex] Astra 3400U */
+ { USB_DEVICE(0x1606, 0x0060), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+
+ /* Philips PSC805 audio device */
+ { USB_DEVICE(0x0471, 0x0155), .driver_info = USB_QUIRK_RESET_RESUME },
+
+ /* RIM Blackberry */
+ { USB_DEVICE(0x0fca, 0x0001), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ { USB_DEVICE(0x0fca, 0x0004), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
+ { USB_DEVICE(0x0fca, 0x0006), .driver_info = USB_QUIRK_NO_AUTOSUSPEND },
{ } /* terminating entry must be last */
};
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index be37c863fdfb..d47ae89154a7 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -169,6 +169,73 @@ show_quirks(struct device *dev, struct device_attribute *attr, char *buf)
}
static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL);
+
+#if defined(CONFIG_USB_PERSIST) || defined(CONFIG_USB_SUSPEND)
+static const char power_group[] = "power";
+#endif
+
+#ifdef CONFIG_USB_PERSIST
+
+static ssize_t
+show_persist(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_device *udev = to_usb_device(dev);
+
+ return sprintf(buf, "%d\n", udev->persist_enabled);
+}
+
+static ssize_t
+set_persist(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_device *udev = to_usb_device(dev);
+ int value;
+
+ /* Hubs are always enabled for USB_PERSIST */
+ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
+ return -EPERM;
+
+ if (sscanf(buf, "%d", &value) != 1)
+ return -EINVAL;
+ usb_pm_lock(udev);
+ udev->persist_enabled = !!value;
+ usb_pm_unlock(udev);
+ return count;
+}
+
+static DEVICE_ATTR(persist, S_IRUGO | S_IWUSR, show_persist, set_persist);
+
+static int add_persist_attributes(struct device *dev)
+{
+ int rc = 0;
+
+ if (is_usb_device(dev)) {
+ struct usb_device *udev = to_usb_device(dev);
+
+ /* Hubs are automatically enabled for USB_PERSIST */
+ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
+ udev->persist_enabled = 1;
+ rc = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_persist.attr,
+ power_group);
+ }
+ return rc;
+}
+
+static void remove_persist_attributes(struct device *dev)
+{
+ sysfs_remove_file_from_group(&dev->kobj,
+ &dev_attr_persist.attr,
+ power_group);
+}
+
+#else
+
+#define add_persist_attributes(dev) 0
+#define remove_persist_attributes(dev) do {} while (0)
+
+#endif /* CONFIG_USB_PERSIST */
+
#ifdef CONFIG_USB_SUSPEND
static ssize_t
@@ -276,8 +343,6 @@ set_level(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level);
-static char power_group[] = "power";
-
static int add_power_attributes(struct device *dev)
{
int rc = 0;
@@ -311,6 +376,7 @@ static void remove_power_attributes(struct device *dev)
#endif /* CONFIG_USB_SUSPEND */
+
/* Descriptor fields */
#define usb_descriptor_attr_le16(field, format_string) \
static ssize_t \
@@ -384,6 +450,10 @@ int usb_create_sysfs_dev_files(struct usb_device *udev)
if (retval)
return retval;
+ retval = add_persist_attributes(dev);
+ if (retval)
+ goto error;
+
retval = add_power_attributes(dev);
if (retval)
goto error;
@@ -421,9 +491,29 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev)
device_remove_file(dev, &dev_attr_product);
device_remove_file(dev, &dev_attr_serial);
remove_power_attributes(dev);
+ remove_persist_attributes(dev);
sysfs_remove_group(&dev->kobj, &dev_attr_grp);
}
+/* Interface Accociation Descriptor fields */
+#define usb_intf_assoc_attr(field, format_string) \
+static ssize_t \
+show_iad_##field (struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct usb_interface *intf = to_usb_interface (dev); \
+ \
+ return sprintf (buf, format_string, \
+ intf->intf_assoc->field); \
+} \
+static DEVICE_ATTR(iad_##field, S_IRUGO, show_iad_##field, NULL);
+
+usb_intf_assoc_attr (bFirstInterface, "%02x\n")
+usb_intf_assoc_attr (bInterfaceCount, "%02d\n")
+usb_intf_assoc_attr (bFunctionClass, "%02x\n")
+usb_intf_assoc_attr (bFunctionSubClass, "%02x\n")
+usb_intf_assoc_attr (bFunctionProtocol, "%02x\n")
+
/* Interface fields */
#define usb_intf_attr(field, format_string) \
static ssize_t \
@@ -487,6 +577,18 @@ static ssize_t show_modalias(struct device *dev,
}
static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL);
+static struct attribute *intf_assoc_attrs[] = {
+ &dev_attr_iad_bFirstInterface.attr,
+ &dev_attr_iad_bInterfaceCount.attr,
+ &dev_attr_iad_bFunctionClass.attr,
+ &dev_attr_iad_bFunctionSubClass.attr,
+ &dev_attr_iad_bFunctionProtocol.attr,
+ NULL,
+};
+static struct attribute_group intf_assoc_attr_grp = {
+ .attrs = intf_assoc_attrs,
+};
+
static struct attribute *intf_attrs[] = {
&dev_attr_bInterfaceNumber.attr,
&dev_attr_bAlternateSetting.attr,
@@ -538,6 +640,8 @@ int usb_create_sysfs_intf_files(struct usb_interface *intf)
alt->string = usb_cache_string(udev, alt->desc.iInterface);
if (alt->string)
retval = device_create_file(dev, &dev_attr_interface);
+ if (intf->intf_assoc)
+ retval = sysfs_create_group(&dev->kobj, &intf_assoc_attr_grp);
usb_create_intf_ep_files(intf, udev);
return 0;
}
@@ -549,4 +653,5 @@ void usb_remove_sysfs_intf_files(struct usb_interface *intf)
usb_remove_intf_ep_files(intf);
device_remove_file(dev, &dev_attr_interface);
sysfs_remove_group(&dev->kobj, &intf_attr_grp);
+ sysfs_remove_group(&intf->dev.kobj, &intf_assoc_attr_grp);
}
diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c
index 94ea9727ff55..52ec44b828f3 100644
--- a/drivers/usb/core/urb.c
+++ b/drivers/usb/core/urb.c
@@ -4,6 +4,7 @@
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/usb.h>
+#include <linux/wait.h>
#include "hcd.h"
#define to_urb(d) container_of(d, struct urb, kref)
@@ -11,6 +12,10 @@
static void urb_destroy(struct kref *kref)
{
struct urb *urb = to_urb(kref);
+
+ if (urb->transfer_flags & URB_FREE_BUFFER)
+ kfree(urb->transfer_buffer);
+
kfree(urb);
}
@@ -34,6 +39,7 @@ void usb_init_urb(struct urb *urb)
memset(urb, 0, sizeof(*urb));
kref_init(&urb->kref);
spin_lock_init(&urb->lock);
+ INIT_LIST_HEAD(&urb->anchor_list);
}
}
@@ -100,8 +106,60 @@ struct urb * usb_get_urb(struct urb *urb)
kref_get(&urb->kref);
return urb;
}
-
-
+
+/**
+ * usb_anchor_urb - anchors an URB while it is processed
+ * @urb: pointer to the urb to anchor
+ * @anchor: pointer to the anchor
+ *
+ * This can be called to have access to URBs which are to be executed
+ * without bothering to track them
+ */
+void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&anchor->lock, flags);
+ usb_get_urb(urb);
+ list_add_tail(&urb->anchor_list, &anchor->urb_list);
+ urb->anchor = anchor;
+ spin_unlock_irqrestore(&anchor->lock, flags);
+}
+EXPORT_SYMBOL_GPL(usb_anchor_urb);
+
+/**
+ * usb_unanchor_urb - unanchors an URB
+ * @urb: pointer to the urb to anchor
+ *
+ * Call this to stop the system keeping track of this URB
+ */
+void usb_unanchor_urb(struct urb *urb)
+{
+ unsigned long flags;
+ struct usb_anchor *anchor;
+
+ if (!urb)
+ return;
+
+ anchor = urb->anchor;
+ if (!anchor)
+ return;
+
+ spin_lock_irqsave(&anchor->lock, flags);
+ if (unlikely(anchor != urb->anchor)) {
+ /* we've lost the race to another thread */
+ spin_unlock_irqrestore(&anchor->lock, flags);
+ return;
+ }
+ urb->anchor = NULL;
+ list_del(&urb->anchor_list);
+ spin_unlock_irqrestore(&anchor->lock, flags);
+ usb_put_urb(urb);
+ if (list_empty(&anchor->urb_list))
+ wake_up(&anchor->wait);
+}
+EXPORT_SYMBOL_GPL(usb_unanchor_urb);
+
/*-------------------------------------------------------------------*/
/**
@@ -478,6 +536,48 @@ void usb_kill_urb(struct urb *urb)
spin_unlock_irq(&urb->lock);
}
+/**
+ * usb_kill_anchored_urbs - cancel transfer requests en masse
+ * @anchor: anchor the requests are bound to
+ *
+ * this allows all outstanding URBs to be killed starting
+ * from the back of the queue
+ */
+void usb_kill_anchored_urbs(struct usb_anchor *anchor)
+{
+ struct urb *victim;
+
+ spin_lock_irq(&anchor->lock);
+ while (!list_empty(&anchor->urb_list)) {
+ victim = list_entry(anchor->urb_list.prev, struct urb, anchor_list);
+ /* we must make sure the URB isn't freed before we kill it*/
+ usb_get_urb(victim);
+ spin_unlock_irq(&anchor->lock);
+ /* this will unanchor the URB */
+ usb_kill_urb(victim);
+ usb_put_urb(victim);
+ spin_lock_irq(&anchor->lock);
+ }
+ spin_unlock_irq(&anchor->lock);
+}
+EXPORT_SYMBOL_GPL(usb_kill_anchored_urbs);
+
+/**
+ * usb_wait_anchor_empty_timeout - wait for an anchor to be unused
+ * @anchor: the anchor you want to become unused
+ * @timeout: how long you are willing to wait in milliseconds
+ *
+ * Call this is you want to be sure all an anchor's
+ * URBs have finished
+ */
+int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor,
+ unsigned int timeout)
+{
+ return wait_event_timeout(anchor->wait, list_empty(&anchor->urb_list),
+ msecs_to_jiffies(timeout));
+}
+EXPORT_SYMBOL_GPL(usb_wait_anchor_empty_timeout);
+
EXPORT_SYMBOL(usb_init_urb);
EXPORT_SYMBOL(usb_alloc_urb);
EXPORT_SYMBOL(usb_free_urb);
@@ -485,4 +585,3 @@ EXPORT_SYMBOL(usb_get_urb);
EXPORT_SYMBOL(usb_submit_urb);
EXPORT_SYMBOL(usb_unlink_urb);
EXPORT_SYMBOL(usb_kill_urb);
-
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 4a6299bd0047..0fee5c66fd64 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -253,6 +253,7 @@ usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
dev->dev.bus = &usb_bus_type;
dev->dev.type = &usb_device_type;
dev->dev.dma_mask = bus->controller->dma_mask;
+ set_dev_node(&dev->dev, dev_to_node(bus->controller));
dev->state = USB_STATE_ATTACHED;
INIT_LIST_HEAD(&dev->ep0.urb_list);
@@ -578,11 +579,12 @@ int __usb_get_extra_descriptor(char *buffer, unsigned size,
* address (through the pointer provided).
*
* These buffers are used with URB_NO_xxx_DMA_MAP set in urb->transfer_flags
- * to avoid behaviors like using "DMA bounce buffers", or tying down I/O
- * mapping hardware for long idle periods. The implementation varies between
+ * to avoid behaviors like using "DMA bounce buffers", or thrashing IOMMU
+ * hardware during URB completion/resubmit. The implementation varies between
* platforms, depending on details of how DMA will work to this device.
- * Using these buffers also helps prevent cacheline sharing problems on
- * architectures where CPU caches are not DMA-coherent.
+ * Using these buffers also eliminates cacheline sharing problems on
+ * architectures where CPU caches are not DMA-coherent. On systems without
+ * bus-snooping caches, these buffers are uncached.
*
* When the buffer is no longer used, free it with usb_buffer_free().
*/
@@ -607,7 +609,7 @@ void *usb_buffer_alloc(
*
* This reclaims an I/O buffer, letting it be reused. The memory must have
* been allocated using usb_buffer_alloc(), and the parameters must match
- * those provided in that allocation request.
+ * those provided in that allocation request.
*/
void usb_buffer_free(
struct usb_device *dev,
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index bf2eb0dae2ec..ad5fa0338f49 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -52,8 +52,16 @@ static inline void usb_pm_unlock(struct usb_device *udev)
#else
-#define usb_port_suspend(dev) 0
-#define usb_port_resume(dev) 0
+static inline int usb_port_suspend(struct usb_device *udev)
+{
+ return 0;
+}
+
+static inline int usb_port_resume(struct usb_device *udev)
+{
+ return 0;
+}
+
static inline void usb_pm_lock(struct usb_device *udev) {}
static inline void usb_pm_unlock(struct usb_device *udev) {}
@@ -100,11 +108,13 @@ static inline int is_usb_device_driver(struct device_driver *drv)
static inline void mark_active(struct usb_interface *f)
{
f->is_active = 1;
+ f->dev.power.power_state.event = PM_EVENT_ON;
}
static inline void mark_quiesced(struct usb_interface *f)
{
f->is_active = 0;
+ f->dev.power.power_state.event = PM_EVENT_SUSPEND;
}
static inline int is_active(const struct usb_interface *f)