aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/core/generic.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/generic.c')
-rw-r--r--drivers/usb/core/generic.c104
1 files changed, 72 insertions, 32 deletions
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index ebb20ff7ac5..358ca8dd784 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -18,6 +18,7 @@
*/
#include <linux/usb.h>
+#include <linux/usb/hcd.h>
#include "usb.h"
static inline const char *plural(int n)
@@ -25,13 +26,30 @@ static inline const char *plural(int n)
return (n == 1 ? "" : "s");
}
-static int choose_configuration(struct usb_device *udev)
+static int is_rndis(struct usb_interface_descriptor *desc)
+{
+ return desc->bInterfaceClass == USB_CLASS_COMM
+ && desc->bInterfaceSubClass == 2
+ && desc->bInterfaceProtocol == 0xff;
+}
+
+static int is_activesync(struct usb_interface_descriptor *desc)
+{
+ return desc->bInterfaceClass == USB_CLASS_MISC
+ && desc->bInterfaceSubClass == 1
+ && desc->bInterfaceProtocol == 1;
+}
+
+int usb_choose_configuration(struct usb_device *udev)
{
int i;
int num_configs;
int insufficient_power = 0;
struct usb_host_config *c, *best;
+ if (usb_device_is_owned(udev))
+ return 0;
+
best = NULL;
c = udev->config;
num_configs = udev->descriptor.bNumConfigurations;
@@ -82,19 +100,19 @@ static int choose_configuration(struct usb_device *udev)
*/
/* Rule out configs that draw too much bus current */
- if (c->desc.bMaxPower * 2 > udev->bus_mA) {
+ if (usb_get_max_power(udev, c) > udev->bus_mA) {
insufficient_power++;
continue;
}
- /* If the first config's first interface is COMM/2/0xff
- * (MSFT RNDIS), rule it out unless Linux has host-side
- * RNDIS support. */
- if (i == 0 && desc
- && desc->bInterfaceClass == USB_CLASS_COMM
- && desc->bInterfaceSubClass == 2
- && desc->bInterfaceProtocol == 0xff) {
-#ifndef CONFIG_USB_NET_RNDIS_HOST
+ /* When the first config's first interface is one of Microsoft's
+ * pet nonstandard Ethernet-over-USB protocols, ignore it unless
+ * this kernel has enabled the necessary host side driver.
+ * But: Don't ignore it if it's the only config.
+ */
+ if (i == 0 && num_configs > 1 && desc &&
+ (is_rndis(desc) || is_activesync(desc))) {
+#if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
continue;
#else
best = c;
@@ -107,7 +125,7 @@ static int choose_configuration(struct usb_device *udev)
* than a vendor-specific driver. */
else if (udev->descriptor.bDeviceClass !=
USB_CLASS_VENDOR_SPEC &&
- (!desc || desc->bInterfaceClass !=
+ (desc && desc->bInterfaceClass !=
USB_CLASS_VENDOR_SPEC)) {
best = c;
break;
@@ -126,7 +144,7 @@ static int choose_configuration(struct usb_device *udev)
if (best) {
i = best->desc.bConfigurationValue;
- dev_info(&udev->dev,
+ dev_dbg(&udev->dev,
"configuration #%d chosen from %d choice%s\n",
i, num_configs, plural(num_configs));
} else {
@@ -137,28 +155,29 @@ static int choose_configuration(struct usb_device *udev)
}
return i;
}
+EXPORT_SYMBOL_GPL(usb_choose_configuration);
static int generic_probe(struct usb_device *udev)
{
int err, c;
- /* put device-specific files into sysfs */
- usb_create_sysfs_dev_files(udev);
-
/* Choose and set the configuration. This registers the interfaces
* with the driver core and lets interface drivers bind to them.
*/
- c = choose_configuration(udev);
- if (c >= 0) {
- err = usb_set_configuration(udev, c);
- if (err) {
- dev_err(&udev->dev, "can't set config #%d, error %d\n",
+ if (udev->authorized == 0)
+ dev_err(&udev->dev, "Device is not authorized for usage\n");
+ else {
+ c = usb_choose_configuration(udev);
+ if (c >= 0) {
+ err = usb_set_configuration(udev, c);
+ if (err && err != -ENODEV) {
+ dev_err(&udev->dev, "can't set config #%d, error %d\n",
c, err);
- /* This need not be fatal. The user can try to
- * set other configurations. */
+ /* This need not be fatal. The user can try to
+ * set other configurations. */
+ }
}
}
-
/* USB device state == configured ... usable */
usb_notify_add_device(udev);
@@ -172,25 +191,46 @@ static void generic_disconnect(struct usb_device *udev)
/* if this is only an unbind, not a physical disconnect, then
* unconfigure the device */
if (udev->actconfig)
- usb_set_configuration(udev, 0);
-
- usb_remove_sysfs_dev_files(udev);
+ usb_set_configuration(udev, -1);
}
#ifdef CONFIG_PM
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, msg);
+
+ /* Non-root devices don't need to do anything for FREEZE or PRETHAW */
+ else if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW)
+ rc = 0;
+ else
+ rc = usb_port_suspend(udev, msg);
+
+ return rc;
}
-static int generic_resume(struct usb_device *udev)
+static int generic_resume(struct usb_device *udev, pm_message_t msg)
{
- 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, msg);
+ else
+ rc = usb_port_resume(udev, msg);
+ return rc;
}
#endif /* CONFIG_PM */