forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: Kai-Heng Feng <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
- Loading branch information
Showing
4 changed files
with
231 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,149 @@ | |
* Copyright (c) 2007 Greg Kroah-Hartman <[email protected]> | ||
*/ | ||
|
||
#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,11 +533,19 @@ 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; | ||
|
||
dev_dbg(&udev->dev, "USB interface quirks for this device: %x\n", | ||
quirks); | ||
udev->quirks |= quirks; | ||
} | ||
|
||
void usb_release_quirk_list(void) | ||
{ | ||
mutex_lock(&quirk_mutex); | ||
kfree(quirk_list); | ||
quirk_list = NULL; | ||
mutex_unlock(&quirk_mutex); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters