From 7ceec1f1d26f966c0816b86a1aab1e0b3b208757 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 26 Jan 2007 14:26:21 +0100 Subject: USB: add a blacklist for devices that can't handle some things we throw at them. This adds a blacklist to the USB core to handle some autosuspend and string issues that devices have. Originally written by Oliver, but hacked up a lot by Greg. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 drivers/usb/core/quirks.c (limited to 'drivers/usb/core/quirks.c') diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c new file mode 100644 index 000000000000..ea0e48e9f611 --- /dev/null +++ b/drivers/usb/core/quirks.c @@ -0,0 +1,75 @@ +/* + * USB device quirk handling logic and table + * + * Copyright (c) 2007 Oliver Neukum + * Copyright (c) 2007 Greg Kroah-Hartman + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + * + */ + +#include +#include +#include "usb.h" + +/* List of quirky USB devices. Please keep this list ordered by: + * 1) Vendor ID + * 2) Product ID + * 3) Class ID + * + * as we want specific devices to be overridden first, and only after that, any + * class specific quirks. + * + * Right now the logic aborts if it finds a valid device in the table, we might + * want to change that in the future if it turns out that a whole class of + * devices is broken... + */ +static const struct usb_device_id usb_quirk_list[] = { + /* HP 5300/5370C scanner */ + { USB_DEVICE(0x03f0, 0x0701), .driver_info = USB_QUIRK_STRING_FETCH_255 }, + + /* Elsa MicroLink 56k (V.250) */ + { USB_DEVICE(0x05cc, 0x2267), .driver_info = USB_QUIRK_NO_AUTOSUSPEND }, + + { } /* terminating entry must be last */ +}; + +static void usb_autosuspend_quirk(struct usb_device *udev) +{ + /* unbalanced resume to prevent autosuspends */ + usb_autoresume_device(udev); +} + +static const struct usb_device_id *find_id(struct usb_device *udev) +{ + const struct usb_device_id *id = usb_quirk_list; + + for (; id->idVendor || id->bDeviceClass || id->bInterfaceClass || + id->driver_info; id++) { + if (usb_match_device(udev, id)) + return id; + } + return NULL; +} + +/* + * Detect any quirks the device has, and do any housekeeping for it if needed. + */ +void usb_detect_quirks(struct usb_device *udev) +{ + const struct usb_device_id *id = usb_quirk_list; + + id = find_id(udev); + if (id) + udev->quirks = (u32)(id->driver_info); + if (udev->quirks) + dev_dbg(&udev->dev, "USB quirks for this device: %x\n", + udev->quirks); + + /* do any special quirk handling here if needed */ + if (udev->quirks & USB_QUIRK_NO_AUTOSUSPEND) + usb_autosuspend_quirk(udev); +} -- cgit From b5e795f8df42936590ba9c606edc715fe3593284 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 20 Feb 2007 15:00:53 -0500 Subject: USB: make autosuspend delay a module parameter This patch (as859) makes the default USB autosuspend delay a module parameter of usbcore. By setting the delay value at boot time, users will be able to prevent the system from autosuspending devices which for some reason can't handle it. The patch also stores the autosuspend delay as a per-device value. A later patch will allow the user to change the value, tailoring the delay for each individual device. A delay value of 0 will prevent autosuspend. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/kernel-parameters.txt | 7 +++++++ drivers/usb/core/driver.c | 16 ++++++++++------ drivers/usb/core/quirks.c | 6 ++++-- drivers/usb/core/usb.c | 12 ++++++++++++ drivers/usb/core/usb.h | 2 -- include/linux/usb.h | 3 +++ 6 files changed, 36 insertions(+), 10 deletions(-) (limited to 'drivers/usb/core/quirks.c') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index c479d30eeaa3..03eb5ed503f7 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1758,6 +1758,13 @@ and is between 256 and 4096 characters. It is defined in the file Note that genuine overcurrent events won't be reported either. + usbcore.autosuspend= + [USB] The autosuspend time delay (in seconds) used + for newly-detected USB devices (default 2). This + is the time required before an idle device will be + autosuspended. Devices for which the delay is set + to 0 won't be autosuspended at all. + usbhid.mousepoll= [USBHID] The interval which mice are to be polled at. diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f9196a0a9412..a420d72a0254 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -963,12 +963,16 @@ static int autosuspend_check(struct usb_device *udev) int i; struct usb_interface *intf; - /* For autosuspend, fail fast if anything is in use. - * Also fail if any interfaces require remote wakeup but it - * isn't available. */ + /* For autosuspend, fail fast if anything is in use or autosuspend + * is disabled. Also fail if any interfaces require remote wakeup + * but it isn't available. + */ udev->do_remote_wakeup = device_may_wakeup(&udev->dev); if (udev->pm_usage_cnt > 0) return -EBUSY; + if (!udev->autosuspend_delay) + return -EPERM; + if (udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { intf = udev->actconfig->interface[i]; @@ -991,7 +995,7 @@ static int autosuspend_check(struct usb_device *udev) #define autosuspend_check(udev) 0 -#endif +#endif /* CONFIG_USB_SUSPEND */ /** * usb_suspend_both - suspend a USB device and its interfaces @@ -1186,7 +1190,7 @@ static int usb_autopm_do_device(struct usb_device *udev, int inc_usage_cnt) udev->pm_usage_cnt -= inc_usage_cnt; } else if (inc_usage_cnt <= 0 && autosuspend_check(udev) == 0) queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend, - USB_AUTOSUSPEND_DELAY); + udev->autosuspend_delay); usb_pm_unlock(udev); return status; } @@ -1270,7 +1274,7 @@ static int usb_autopm_do_interface(struct usb_interface *intf, intf->pm_usage_cnt -= inc_usage_cnt; } else if (inc_usage_cnt <= 0 && autosuspend_check(udev) == 0) queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend, - USB_AUTOSUSPEND_DELAY); + udev->autosuspend_delay); } usb_pm_unlock(udev); return status; diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index ea0e48e9f611..0e5c646cb4f6 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -39,8 +39,10 @@ static const struct usb_device_id usb_quirk_list[] = { static void usb_autosuspend_quirk(struct usb_device *udev) { - /* unbalanced resume to prevent autosuspends */ - usb_autoresume_device(udev); +#ifdef CONFIG_USB_SUSPEND + /* disable autosuspend, but allow the user to re-enable it via sysfs */ + udev->autosuspend_delay = 0; +#endif } static const struct usb_device_id *find_id(struct usb_device *udev) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 3db721cd557a..54b42ce311c1 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -22,6 +22,7 @@ */ #include +#include #include #include #include @@ -50,6 +51,16 @@ static int nousb; /* Disable USB when built into kernel image */ struct workqueue_struct *ksuspend_usb_wq; /* For autosuspend */ +#ifdef CONFIG_USB_SUSPEND +static int usb_autosuspend_delay = 2; /* Default delay value, + * in seconds */ +module_param_named(autosuspend, usb_autosuspend_delay, uint, 0644); +MODULE_PARM_DESC(autosuspend, "default autosuspend delay"); + +#else +#define usb_autosuspend_delay 0 +#endif + /** * usb_ifnum_to_if - get the interface object with a given interface number @@ -306,6 +317,7 @@ usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1) #ifdef CONFIG_PM mutex_init(&dev->pm_mutex); INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work); + dev->autosuspend_delay = usb_autosuspend_delay * HZ; #endif return dev; } diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 045cbd111887..b0a35f45b099 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -65,8 +65,6 @@ static inline void usb_pm_unlock(struct usb_device *udev) {} #ifdef CONFIG_USB_SUSPEND -#define USB_AUTOSUSPEND_DELAY (HZ*2) - extern void usb_autosuspend_device(struct usb_device *udev); extern int usb_autoresume_device(struct usb_device *udev); diff --git a/include/linux/usb.h b/include/linux/usb.h index 37e522eba47f..87dc75a6cee1 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -389,10 +389,13 @@ struct usb_device { int pm_usage_cnt; /* usage counter for autosuspend */ u32 quirks; /* quirks of the whole device */ + #ifdef CONFIG_PM struct delayed_work autosuspend; /* for delayed autosuspends */ struct mutex pm_mutex; /* protects PM operations */ + unsigned autosuspend_delay; /* in jiffies */ + unsigned auto_pm:1; /* autosuspend/resume in progress */ unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ #endif -- cgit From 8a61499bc769f3e12c7f866f7283728308fff6ad Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 20 Mar 2007 19:32:51 +0100 Subject: USB: another entry for the quirk list this scanner disconnects upon suspend. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb/core/quirks.c') diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 0e5c646cb4f6..f08ec85a6d64 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -30,7 +30,8 @@ static const struct usb_device_id usb_quirk_list[] = { /* HP 5300/5370C scanner */ { USB_DEVICE(0x03f0, 0x0701), .driver_info = USB_QUIRK_STRING_FETCH_255 }, - + /* Seiko Epson Corp - Perfection 1670 */ + { USB_DEVICE(0x04b8, 0x011f), .driver_info = USB_QUIRK_NO_AUTOSUSPEND }, /* Elsa MicroLink 56k (V.250) */ { USB_DEVICE(0x05cc, 0x2267), .driver_info = USB_QUIRK_NO_AUTOSUSPEND }, -- cgit From 2add5229d77a3de08015feef437653e02372162f Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 20 Mar 2007 14:59:39 -0400 Subject: USB: add power/level sysfs attribute This patch (as874) adds another piece to the user-visible part of the USB autosuspend interface. The new power/level sysfs attribute allows users to force the device on (with autosuspend off), force the device to sleep (with autoresume off), or return to normal automatic operation. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 26 +++++++++++ drivers/usb/core/driver.c | 15 ++++-- drivers/usb/core/quirks.c | 2 +- drivers/usb/core/sysfs.c | 81 +++++++++++++++++++++++++++++++-- include/linux/usb.h | 2 + 5 files changed, 118 insertions(+), 8 deletions(-) (limited to 'drivers/usb/core/quirks.c') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 00a84326325f..f9937add033d 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -13,3 +13,29 @@ Description: The autosuspend delay for newly-created devices is set to the value of the usbcore.autosuspend module parameter. + +What: /sys/bus/usb/devices/.../power/level +Date: March 2007 +KernelVersion: 2.6.21 +Contact: Alan Stern +Description: + Each USB device directory will contain a file named + power/level. This file holds a power-level setting for + the device, one of "on", "auto", or "suspend". + + "on" means that the device is not allowed to autosuspend, + although normal suspends for system sleep will still + be honored. "auto" means the device will autosuspend + and autoresume in the usual manner, according to the + capabilities of its driver. "suspend" means the device + is forced into a suspended state and it will not autoresume + in response to I/O requests. However remote-wakeup requests + from the device may still be enabled (the remote-wakeup + setting is controlled separately by the power/wakeup + attribute). + + During normal use, devices should be left in the "auto" + level. The other levels are meant for administrative uses. + If you want to suspend a device immediately but leave it + free to wake up in response to I/O requests, you should + write "0" to power/autosuspend. diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 884179f1e163..9b6a60fafddb 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -872,8 +872,10 @@ static int usb_resume_device(struct usb_device *udev) done: // dev_dbg(&udev->dev, "%s: status %d\n", __FUNCTION__, status); - if (status == 0) + if (status == 0) { + udev->autoresume_disabled = 0; udev->dev.power.power_state.event = PM_EVENT_ON; + } return status; } @@ -970,7 +972,7 @@ static int autosuspend_check(struct usb_device *udev) udev->do_remote_wakeup = device_may_wakeup(&udev->dev); if (udev->pm_usage_cnt > 0) return -EBUSY; - if (udev->autosuspend_delay < 0) + if (udev->autosuspend_delay < 0 || udev->autosuspend_disabled) return -EPERM; if (udev->actconfig) { @@ -1116,6 +1118,8 @@ static int usb_resume_both(struct usb_device *udev) struct usb_interface *intf; struct usb_device *parent = udev->parent; + if (udev->auto_pm && udev->autoresume_disabled) + return -EPERM; cancel_delayed_work(&udev->autosuspend); if (udev->state == USB_STATE_NOTATTACHED) return -ENODEV; @@ -1486,9 +1490,14 @@ static int usb_suspend(struct device *dev, pm_message_t message) static int usb_resume(struct device *dev) { + struct usb_device *udev; + if (!is_usb_device(dev)) /* Ignore PM for interfaces */ return 0; - return usb_external_resume_device(to_usb_device(dev)); + udev = to_usb_device(dev); + if (udev->autoresume_disabled) + return -EPERM; + return usb_external_resume_device(udev); } #else diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index f08ec85a6d64..739f520908aa 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -42,7 +42,7 @@ static void usb_autosuspend_quirk(struct usb_device *udev) { #ifdef CONFIG_USB_SUSPEND /* disable autosuspend, but allow the user to re-enable it via sysfs */ - udev->autosuspend_delay = 0; + udev->autosuspend_disabled = 1; #endif } diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 731001f7d2c1..2ea47a38aefa 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -11,6 +11,7 @@ #include +#include #include #include "usb.h" @@ -184,9 +185,8 @@ set_autosuspend(struct device *dev, struct device_attribute *attr, if (value >= 0) usb_try_autosuspend_device(udev); else { - usb_lock_device(udev); - usb_external_resume_device(udev); - usb_unlock_device(udev); + if (usb_autoresume_device(udev) == 0) + usb_autosuspend_device(udev); } return count; } @@ -194,21 +194,94 @@ set_autosuspend(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(autosuspend, S_IRUGO | S_IWUSR, show_autosuspend, set_autosuspend); +static const char on_string[] = "on"; +static const char auto_string[] = "auto"; +static const char suspend_string[] = "suspend"; + +static ssize_t +show_level(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p = auto_string; + + if (udev->state == USB_STATE_SUSPENDED) { + if (udev->autoresume_disabled) + p = suspend_string; + } else { + if (udev->autosuspend_disabled) + p = on_string; + } + return sprintf(buf, "%s\n", p); +} + +static ssize_t +set_level(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int len = count; + char *cp; + int rc = 0; + + cp = memchr(buf, '\n', count); + if (cp) + len = cp - buf; + + usb_lock_device(udev); + + /* Setting the flags without calling usb_pm_lock is a subject to + * races, but who cares... + */ + if (len == sizeof on_string - 1 && + strncmp(buf, on_string, len) == 0) { + udev->autosuspend_disabled = 1; + udev->autoresume_disabled = 0; + rc = usb_external_resume_device(udev); + + } else if (len == sizeof auto_string - 1 && + strncmp(buf, auto_string, len) == 0) { + udev->autosuspend_disabled = 0; + udev->autoresume_disabled = 0; + rc = usb_external_resume_device(udev); + + } else if (len == sizeof suspend_string - 1 && + strncmp(buf, suspend_string, len) == 0) { + udev->autosuspend_disabled = 0; + udev->autoresume_disabled = 1; + rc = usb_external_suspend_device(udev, PMSG_SUSPEND); + + } else + rc = -EINVAL; + + usb_unlock_device(udev); + return (rc < 0 ? rc : count); +} + +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; - if (is_usb_device(dev)) + if (is_usb_device(dev)) { rc = sysfs_add_file_to_group(&dev->kobj, &dev_attr_autosuspend.attr, power_group); + if (rc == 0) + rc = sysfs_add_file_to_group(&dev->kobj, + &dev_attr_level.attr, + power_group); + } return rc; } static void remove_power_attributes(struct device *dev) { + sysfs_remove_file_from_group(&dev->kobj, + &dev_attr_level.attr, + power_group); sysfs_remove_file_from_group(&dev->kobj, &dev_attr_autosuspend.attr, power_group); diff --git a/include/linux/usb.h b/include/linux/usb.h index cc24d089faa0..5e8e144afbae 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -398,6 +398,8 @@ struct usb_device { unsigned auto_pm:1; /* autosuspend/resume in progress */ unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ + unsigned autosuspend_disabled:1; /* autosuspend and autoresume */ + unsigned autoresume_disabled:1; /* disabled by the user */ #endif }; #define to_usb_device(d) container_of(d, struct usb_device, dev) -- cgit