|
@@ -6,11 +6,149 @@
|
|
|
* Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de>
|
|
|
*/
|
|
|
|
|
|
+#include <linux/moduleparam.h>
|
|
|
#include <linux/usb.h>
|
|
|
#include <linux/usb/quirks.h>
|
|
|
#include <linux/usb/hcd.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.
|
|
|
* 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
|
|
@@ -321,8 +459,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static u32 __usb_detect_quirks(struct usb_device *udev,
|
|
|
- const struct usb_device_id *id)
|
|
|
+static u32 usb_detect_static_quirks(struct usb_device *udev,
|
|
|
+ const struct usb_device_id *id)
|
|
|
{
|
|
|
u32 quirks = 0;
|
|
|
|
|
@@ -340,21 +478,43 @@ static u32 __usb_detect_quirks(struct usb_device *udev,
|
|
|
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.
|
|
|
*/
|
|
|
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
|
|
|
* Yangtze chipset, so set them as RESET_RESUME flag.
|
|
|
*/
|
|
|
if (usb_amd_resume_quirk(udev))
|
|
|
- udev->quirks |= __usb_detect_quirks(udev,
|
|
|
+ udev->quirks |= usb_detect_static_quirks(udev,
|
|
|
usb_amd_resume_quirk_list);
|
|
|
|
|
|
+ udev->quirks ^= usb_detect_dynamic_quirks(udev);
|
|
|
+
|
|
|
if (udev->quirks)
|
|
|
dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
|
|
|
udev->quirks);
|
|
@@ -373,7 +533,7 @@ void usb_detect_interface_quirks(struct usb_device *udev)
|
|
|
{
|
|
|
u32 quirks;
|
|
|
|
|
|
- quirks = __usb_detect_quirks(udev, usb_interface_quirk_list);
|
|
|
+ quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list);
|
|
|
if (quirks == 0)
|
|
|
return;
|
|
|
|
|
@@ -381,3 +541,11 @@ void usb_detect_interface_quirks(struct usb_device *udev)
|
|
|
quirks);
|
|
|
udev->quirks |= quirks;
|
|
|
}
|
|
|
+
|
|
|
+void usb_release_quirk_list(void)
|
|
|
+{
|
|
|
+ mutex_lock(&quirk_mutex);
|
|
|
+ kfree(quirk_list);
|
|
|
+ quirk_list = NULL;
|
|
|
+ mutex_unlock(&quirk_mutex);
|
|
|
+}
|