Commit b27560e4 authored by Kai-Heng Feng's avatar Kai-Heng Feng Committed by Greg Kroah-Hartman

usb: core: Add "quirks" parameter for usbcore

Trying quirks in usbcore needs to rebuild the driver or the entire
kernel if it's builtin. It can save a lot of time if usbcore has similar
ability like "usbhid.quirks=" and "usb-storage.quirks=".

Rename the original quirk detection function to "static" as we introduce
this new "dynamic" function.

Now users can use "usbcore.quirks=" as short term workaround before the
next kernel release. Also, the quirk parameter can XOR the builtin
quirks for debugging purpose.

This is inspired by usbhid and usb-storage.
Signed-off-by: default avatarKai-Heng Feng <kai.heng.feng@canonical.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent cf6e06cd
...@@ -4368,6 +4368,61 @@ ...@@ -4368,6 +4368,61 @@
usbcore.nousb [USB] Disable the USB subsystem usbcore.nousb [USB] Disable the USB subsystem
usbcore.quirks=
[USB] A list of quirks entries to supplement or
override the built-in usb core quirk list. List
entries are separated by commas. Each entry has
the form VID:PID:Flags where VID and PID are Vendor
and Product ID values (4-digit hex numbers) and
Flags is a set of characters, each corresponding
to a common usb core quirk flag as follows:
a = USB_QUIRK_STRING_FETCH_255 (string
descriptors must not be fetched using
a 255-byte read);
b = USB_QUIRK_RESET_RESUME (device can't resume
correctly so reset it instead);
c = USB_QUIRK_NO_SET_INTF (device can't handle
Set-Interface requests);
d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't
handle its Configuration or Interface
strings);
e = USB_QUIRK_RESET (device can't be reset
(e.g morph devices), don't use reset);
f = USB_QUIRK_HONOR_BNUMINTERFACES (device has
more interface descriptions than the
bNumInterfaces count, and can't handle
talking to these interfaces);
g = USB_QUIRK_DELAY_INIT (device needs a pause
during initialization, after we read
the device descriptor);
h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For
high speed and super speed interrupt
endpoints, the USB 2.0 and USB 3.0 spec
require the interval in microframes (1
microframe = 125 microseconds) to be
calculated as interval = 2 ^
(bInterval-1).
Devices with this quirk report their
bInterval as the result of this
calculation instead of the exponent
variable used in the calculation);
i = USB_QUIRK_DEVICE_QUALIFIER (device can't
handle device_qualifier descriptor
requests);
j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device
generates spurious wakeup, ignore
remote wakeup capability);
k = USB_QUIRK_NO_LPM (device can't handle Link
Power Management);
l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL
(Device reports its bInterval as linear
frames instead of the USB 2.0
calculation);
m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs
to be disconnected before suspend to
prevent spurious wakeup)
Example: quirks=0781:5580:bk,0a5c:5834:gij
usbhid.mousepoll= usbhid.mousepoll=
[USBHID] The interval which mice are to be polled at. [USBHID] The interval which mice are to be polled at.
......
...@@ -11,6 +11,143 @@ ...@@ -11,6 +11,143 @@
#include <linux/usb/hcd.h> #include <linux/usb/hcd.h>
#include "usb.h" #include "usb.h"
struct quirk_entry {
u16 vid;
u16 pid;
u32 flags;
};
static DEFINE_MUTEX(quirk_mutex);
static struct quirk_entry *quirk_list;
static unsigned int quirk_count;
static char quirks_param[128];
static int quirks_param_set(const char *val, const struct kernel_param *kp)
{
char *p, *field;
u16 vid, pid;
u32 flags;
size_t i;
mutex_lock(&quirk_mutex);
if (!val || !*val) {
quirk_count = 0;
kfree(quirk_list);
quirk_list = NULL;
goto unlock;
}
for (quirk_count = 1, i = 0; val[i]; i++)
if (val[i] == ',')
quirk_count++;
if (quirk_list) {
kfree(quirk_list);
quirk_list = NULL;
}
quirk_list = kcalloc(quirk_count, sizeof(struct quirk_entry),
GFP_KERNEL);
if (!quirk_list) {
mutex_unlock(&quirk_mutex);
return -ENOMEM;
}
for (i = 0, p = (char *)val; p && *p;) {
/* Each entry consists of VID:PID:flags */
field = strsep(&p, ":");
if (!field)
break;
if (kstrtou16(field, 16, &vid))
break;
field = strsep(&p, ":");
if (!field)
break;
if (kstrtou16(field, 16, &pid))
break;
field = strsep(&p, ",");
if (!field || !*field)
break;
/* Collect the flags */
for (flags = 0; *field; field++) {
switch (*field) {
case 'a':
flags |= USB_QUIRK_STRING_FETCH_255;
break;
case 'b':
flags |= USB_QUIRK_RESET_RESUME;
break;
case 'c':
flags |= USB_QUIRK_NO_SET_INTF;
break;
case 'd':
flags |= USB_QUIRK_CONFIG_INTF_STRINGS;
break;
case 'e':
flags |= USB_QUIRK_RESET;
break;
case 'f':
flags |= USB_QUIRK_HONOR_BNUMINTERFACES;
break;
case 'g':
flags |= USB_QUIRK_DELAY_INIT;
break;
case 'h':
flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL;
break;
case 'i':
flags |= USB_QUIRK_DEVICE_QUALIFIER;
break;
case 'j':
flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP;
break;
case 'k':
flags |= USB_QUIRK_NO_LPM;
break;
case 'l':
flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL;
break;
case 'm':
flags |= USB_QUIRK_DISCONNECT_SUSPEND;
break;
/* Ignore unrecognized flag characters */
}
}
quirk_list[i++] = (struct quirk_entry)
{ .vid = vid, .pid = pid, .flags = flags };
}
if (i < quirk_count)
quirk_count = i;
unlock:
mutex_unlock(&quirk_mutex);
return param_set_copystring(val, kp);
}
static const struct kernel_param_ops quirks_param_ops = {
.set = quirks_param_set,
.get = param_get_string,
};
static struct kparam_string quirks_param_string = {
.maxlen = sizeof(quirks_param),
.string = quirks_param,
};
module_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644);
MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks");
/* Lists of quirky USB devices, split in device quirks and interface quirks. /* Lists of quirky USB devices, split in device quirks and interface quirks.
* Device quirks are applied at the very beginning of the enumeration process, * Device quirks are applied at the very beginning of the enumeration process,
* right after reading the device descriptor. They can thus only match on device * right after reading the device descriptor. They can thus only match on device
...@@ -320,8 +457,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev) ...@@ -320,8 +457,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev)
return 0; return 0;
} }
static u32 __usb_detect_quirks(struct usb_device *udev, static u32 usb_detect_static_quirks(struct usb_device *udev,
const struct usb_device_id *id) const struct usb_device_id *id)
{ {
u32 quirks = 0; u32 quirks = 0;
...@@ -339,21 +476,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev, ...@@ -339,21 +476,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev,
return quirks; return quirks;
} }
static u32 usb_detect_dynamic_quirks(struct usb_device *udev)
{
u16 vid = le16_to_cpu(udev->descriptor.idVendor);
u16 pid = le16_to_cpu(udev->descriptor.idProduct);
int i, flags = 0;
mutex_lock(&quirk_mutex);
for (i = 0; i < quirk_count; i++) {
if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) {
flags = quirk_list[i].flags;
break;
}
}
mutex_unlock(&quirk_mutex);
return flags;
}
/* /*
* Detect any quirks the device has, and do any housekeeping for it if needed. * Detect any quirks the device has, and do any housekeeping for it if needed.
*/ */
void usb_detect_quirks(struct usb_device *udev) void usb_detect_quirks(struct usb_device *udev)
{ {
udev->quirks = __usb_detect_quirks(udev, usb_quirk_list); udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list);
/* /*
* Pixart-based mice would trigger remote wakeup issue on AMD * Pixart-based mice would trigger remote wakeup issue on AMD
* Yangtze chipset, so set them as RESET_RESUME flag. * Yangtze chipset, so set them as RESET_RESUME flag.
*/ */
if (usb_amd_resume_quirk(udev)) if (usb_amd_resume_quirk(udev))
udev->quirks |= __usb_detect_quirks(udev, udev->quirks |= usb_detect_static_quirks(udev,
usb_amd_resume_quirk_list); usb_amd_resume_quirk_list);
udev->quirks ^= usb_detect_dynamic_quirks(udev);
if (udev->quirks) if (udev->quirks)
dev_dbg(&udev->dev, "USB quirks for this device: %x\n", dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
udev->quirks); udev->quirks);
...@@ -372,7 +531,7 @@ void usb_detect_interface_quirks(struct usb_device *udev) ...@@ -372,7 +531,7 @@ void usb_detect_interface_quirks(struct usb_device *udev)
{ {
u32 quirks; u32 quirks;
quirks = __usb_detect_quirks(udev, usb_interface_quirk_list); quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list);
if (quirks == 0) if (quirks == 0)
return; return;
...@@ -380,3 +539,11 @@ void usb_detect_interface_quirks(struct usb_device *udev) ...@@ -380,3 +539,11 @@ void usb_detect_interface_quirks(struct usb_device *udev)
quirks); quirks);
udev->quirks |= quirks; udev->quirks |= quirks;
} }
void usb_release_quirk_list(void)
{
mutex_lock(&quirk_mutex);
kfree(quirk_list);
quirk_list = NULL;
mutex_unlock(&quirk_mutex);
}
...@@ -1259,6 +1259,7 @@ static void __exit usb_exit(void) ...@@ -1259,6 +1259,7 @@ static void __exit usb_exit(void)
if (usb_disabled()) if (usb_disabled())
return; return;
usb_release_quirk_list();
usb_deregister_device_driver(&usb_generic_driver); usb_deregister_device_driver(&usb_generic_driver);
usb_major_cleanup(); usb_major_cleanup();
usb_deregister(&usbfs_driver); usb_deregister(&usbfs_driver);
......
...@@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *); ...@@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *);
extern void usb_authorize_interface(struct usb_interface *); extern void usb_authorize_interface(struct usb_interface *);
extern void usb_detect_quirks(struct usb_device *udev); extern void usb_detect_quirks(struct usb_device *udev);
extern void usb_detect_interface_quirks(struct usb_device *udev); extern void usb_detect_interface_quirks(struct usb_device *udev);
extern void usb_release_quirk_list(void);
extern int usb_remove_device(struct usb_device *udev); extern int usb_remove_device(struct usb_device *udev);
extern int usb_get_device_descriptor(struct usb_device *dev, extern int usb_get_device_descriptor(struct usb_device *dev,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment