forked from mirrors/linux
		
	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 <kai.heng.feng@canonical.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									ca5a2e9a77
								
							
						
					
					
						commit
						027bd6cafd
					
				
					 4 changed files with 231 additions and 5 deletions
				
			
		| 
						 | 
				
			
			@ -4368,6 +4368,62 @@
 | 
			
		|||
 | 
			
		||||
	usbcore.nousb	[USB] Disable the USB subsystem
 | 
			
		||||
 | 
			
		||||
	usbcore.quirks=
 | 
			
		||||
			[USB] A list of quirk entries to augment the built-in
 | 
			
		||||
			usb core quirk list. List entries are separated by
 | 
			
		||||
			commas. Each entry has the form
 | 
			
		||||
			VendorID:ProductID:Flags. The IDs are 4-digit hex
 | 
			
		||||
			numbers and Flags is a set of letters. Each letter
 | 
			
		||||
			will change the built-in quirk; setting it if it is
 | 
			
		||||
			clear and clearing it if it is set. The letters have
 | 
			
		||||
			the following meanings:
 | 
			
		||||
				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] The interval which mice are to be polled at.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,7 +459,7 @@ static int usb_amd_resume_quirk(struct usb_device *udev)
 | 
			
		|||
	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)
 | 
			
		||||
{
 | 
			
		||||
	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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1259,6 +1259,7 @@ static void __exit usb_exit(void)
 | 
			
		|||
	if (usb_disabled())
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	usb_release_quirk_list();
 | 
			
		||||
	usb_deregister_device_driver(&usb_generic_driver);
 | 
			
		||||
	usb_major_cleanup();
 | 
			
		||||
	usb_deregister(&usbfs_driver);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ extern void usb_deauthorize_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_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_get_device_descriptor(struct usb_device *dev,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue