From mboxrd@z Thu Jan 1 00:00:00 1970 From: Paul Walmsley Subject: [PATCH 7/7] usbhid: add configfs USB HID quirk configuration Date: Wed, 11 Apr 2007 00:50:21 -0600 (MDT) Message-ID: Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed Return-path: Sender: owner-linux-input@atrey.karlin.mff.cuni.cz List-Help: List-Owner: List-Post: List-Unsubscribe: To: linux-input@atrey.karlin.mff.cuni.cz List-Id: linux-input@vger.kernel.org From: Paul Walmsley Add support for runtime USB HID quirk configuration via ConfigFS, after the usbhid module has been loaded. Also included is support for dynamic quirk removal. Signed-off-by: Paul Walmsley --- drivers/usb/input/Kconfig | 20 + drivers/usb/input/Makefile | 2 drivers/usb/input/hid-core.c | 9 drivers/usb/input/hid-quirks-cfgfs.c | 476 +++++++++++++++++++++++++++++++++++ drivers/usb/input/hid-quirks.c | 33 ++ include/linux/hid-quirks.h | 5 6 files changed, 544 insertions(+), 1 deletion(-) Index: dev/drivers/usb/input/Kconfig =================================================================== --- dev.orig/drivers/usb/input/Kconfig +++ dev/drivers/usb/input/Kconfig @@ -99,6 +99,26 @@ config ZEROPLUS_FF Say Y here if you have a Zeroplus based game controller and want to enable force feedback for it. +config USB_HID_CONFIGFS + bool "Enable runtime configuration of USB HID quirks via ConfigFS" + default n + depends on USB_HID + select CONFIGFS_FS + help + Say Y here if you want to be able to add or modify USB HID quirks + at runtime. When ConfigFS is mounted, the directory + "usbhid/quirks_runtime" will appear. Making a directory + here in the form "xxxx:yyyy" (where xxxx is the 16-bit USB + vendor ID, and yyyy is the 16-bit USB product ID) will + expose filenames underneath that correspond to different quirks. + The files will contain a 1 if the quirk is enabled; 0 if disabled. + Writing the corresponding digit to a file will either enable + or disable that particular quirk. A dynamically-created device + directory may be removed via rmdir; this will remove the + dynamic quirk created for that device. + + If unsure, say N. + config USB_HIDDEV bool "/dev/hiddev raw HID device support" depends on USB_HID Index: dev/drivers/usb/input/Makefile =================================================================== --- dev.orig/drivers/usb/input/Makefile +++ dev/drivers/usb/input/Makefile @@ -4,7 +4,7 @@ # Multipart objects. wacom-objs := wacom_wac.o wacom_sys.o -usbhid-objs := hid-core.o hid-quirks.o +usbhid-objs := hid-core.o hid-quirks.o hid-quirks-cfgfs.o # Optional parts of multipart objects. Index: dev/drivers/usb/input/hid-core.c =================================================================== --- dev.orig/drivers/usb/input/hid-core.c +++ dev/drivers/usb/input/hid-core.c @@ -50,6 +50,9 @@ static char *hid_types[] = {"Device", "P int usbhid_quirks_init(char **quirks_param); void usbhid_quirks_exit(void); +int usbhid_cfgfs_init(void); +void usbhid_cfgfs_exit(void); + /* * Module parameters. */ @@ -1047,6 +1050,9 @@ static struct usb_driver hid_driver = { static int __init hid_init(void) { int retval; + retval = usbhid_cfgfs_init(); + if (retval) + goto usbhid_cfgfs_init_fail; retval = usbhid_quirks_init(quirks_param); if (retval) goto usbhid_quirks_init_fail; @@ -1064,6 +1070,8 @@ usb_register_fail: hiddev_init_fail: usbhid_quirks_exit(); usbhid_quirks_init_fail: + usbhid_cfgfs_exit(); +usbhid_cfgfs_init_fail: return retval; } @@ -1072,6 +1080,7 @@ static void __exit hid_exit(void) usb_deregister(&hid_driver); hiddev_exit(); usbhid_quirks_exit(); + usbhid_cfgfs_exit(); } module_init(hid_init); Index: dev/include/linux/hid-quirks.h =================================================================== --- dev.orig/include/linux/hid-quirks.h +++ dev/include/linux/hid-quirks.h @@ -8,6 +8,10 @@ /* * HID device quirks. + * + * Any changes made to this list should also be applied to + * drivers/usb/input/hid-quirks-cfgfs.c:hid_quirk_names[] + * */ #define HID_QUIRK_INVERT 0x00000001 @@ -36,5 +40,6 @@ u32 usbhid_lookup_any_quirk(const u16 idVendor, const u16 idProduct); int usbhid_modify_equirk(const u16 idVendor, const u16 idProduct, const u32 quirks); +int usbhid_remove_equirk(const u16 idVendor, const u16 idProduct); #endif /* __HID_QUIRKS_H */ Index: dev/drivers/usb/input/hid-quirks.c =================================================================== --- dev.orig/drivers/usb/input/hid-quirks.c +++ dev/drivers/usb/input/hid-quirks.c @@ -637,6 +637,39 @@ int usbhid_modify_equirk(const u16 idVen /** + * usbhid_remove_equirk: remove a runtime HID quirk from memory. + * @idVendor: the 16-bit USB vendor ID, in native byteorder + * @idProduct: the 16-bit USB product ID, in native byteorder + * + * Returns: 0 OK, -ENOENT if the entry could not be found. + */ +int usbhid_remove_equirk(const u16 idVendor, const u16 idProduct) +{ + struct quirks_list_struct *q, *temp; + int ret = -ENOENT; + + WARN_ON(idVendor == 0); + + down_write(&equirks_rwsem); + + list_for_each_entry_safe(q, temp, &equirks_list, node) { + if (q->hid_bl_item.idVendor == idVendor && + q->hid_bl_item.idProduct == idProduct) { + + list_del(&q->node); + kfree(q); + ret = 0; + + } + } + + up_write(&equirks_rwsem); + + return ret; +} + + +/** * usbhid_remove_all_equirks: remove all runtime HID quirks from memory * * Description: Index: dev/drivers/usb/input/hid-quirks-cfgfs.c =================================================================== --- /dev/null +++ dev/drivers/usb/input/hid-quirks-cfgfs.c @@ -0,0 +1,476 @@ +/* + * USB HID dynamic quirks ConfigFS support for Linux + * + * Copyright (c) 2007 Paul Walmsley + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +#include /* for u32, u16 */ +#include /* for kmalloc/kfree */ +#include /* for ARRAY_SIZE */ +#include /* for NAME_MAX */ +#include + +#include +#include +#include +#include "usbhid.h" + +#ifdef CONFIG_USB_HID_CONFIGFS + +/* + * ConfigFS attribute filenames for each HID quirk. Any changes + * made here should also be applied to include/linux/hid-quirks.h. + */ + +static struct hid_quirk_name { + u32 quirk; + struct configfs_attribute ca; +} hid_quirk_names[] = { + { + .quirk = HID_QUIRK_INVERT, + .ca = { + .ca_name = "invert", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_NOTOUCH, + .ca = { + .ca_name = "notouch", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_IGNORE, + .ca = { + .ca_name = "ignore", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_NOGET, + .ca = { + .ca_name = "noget", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_HIDDEV, + .ca = { + .ca_name = "hiddev", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_BADPAD, + .ca = { + .ca_name = "badpad", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_MULTI_INPUT, + .ca = { + .ca_name = "multi_input", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_2WHEEL_MOUSE_HACK_7, + .ca = { + .ca_name = "2wheel_mouse_hack_7", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_2WHEEL_MOUSE_HACK_5, + .ca = { + .ca_name = "2wheel_mouse_hack_5", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_2WHEEL_MOUSE_HACK_ON, + .ca = { + .ca_name = "2wheel_mouse_hack_on", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_MIGHTYMOUSE, + .ca = { + .ca_name = "mightymouse", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_CYMOTION, + .ca = { + .ca_name = "cymotion", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_POWERBOOK_HAS_FN, + .ca = { + .ca_name = "powerbook_has_fn", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_POWERBOOK_FN_ON, + .ca = { + .ca_name = "powerbook_fn_on", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_INVERT_HWHEEL, + .ca = { + .ca_name = "invert_hwheel", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_POWERBOOK_ISO_KEYBOARD, + .ca = { + .ca_name = "powerbook_iso_keyboard", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_BAD_RELATIVE_KEYS, + .ca = { + .ca_name = "bad_relative_keys", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_SKIP_OUTPUT_REPORTS, + .ca = { + .ca_name = "skip_output_reports", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_IGNORE_MOUSE, + .ca = { + .ca_name = "ignore_mouse", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_SONY_PS3_CONTROLLER, + .ca = { + .ca_name = "sony_ps3_controller", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_LOGITECH_S510_DESCRIPTOR, + .ca ={ + .ca_name = "logitech_s510_descriptor", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = HID_QUIRK_DUPLICATE_USAGES, + .ca = { + .ca_name = "duplicate_usages", + .ca_mode = S_IRUGO | S_IWUSR, + .ca_owner = THIS_MODULE, + }, + }, + { + .quirk = 0, + } +}; + + +static int hid_parse_dev_fname(const char *name, + u16 *idVendor, + u16 *idProduct) +{ + char cmp_name[NAME_MAX]; + + WARN_ON(sizeof(unsigned short) != 2); + + WARN_ON(idVendor == NULL); + WARN_ON(idProduct == NULL); + + if (strlen(name) != strlen("xxxx:yyyy")) { + dbg("Invalid name - cannot parse device directory name"); + return -EINVAL; + } + + if (sscanf(name, "%hx:%hx", idVendor, idProduct) != 2) { + dbg("Invalid name - cannot parse device directory name"); + return -EINVAL; + } + + snprintf(cmp_name, NAME_MAX, "%04hx:%04hx", *idVendor, *idProduct); + + if (strcmp(cmp_name, name) != 0) { + dbg("Invalid name - cannot parse device directory name"); + return -EINVAL; + } + + return 0; +} + + +static u32 find_quirk_bits_by_name(const char *name) +{ + int n; + + for (n = 0; hid_quirk_names[n].quirk; n++) + if (strcmp(hid_quirk_names[n].ca.ca_name, name) == 0) + break; + + return (hid_quirk_names[n].quirk) ? hid_quirk_names[n].quirk : 0; +} + + +/* "/usbhid/quirks_runtime/" configfs code and data */ + + +struct device_cfgfs_group { + struct config_group group; + u16 idVendor; + u16 idProduct; +}; + +static struct device_cfgfs_group *to_device_cfgfs_group(struct config_item *item) +{ + return item ? container_of(to_config_group(item), struct device_cfgfs_group, group) : NULL; +} + +static ssize_t device_cfgfs_show_attr(struct config_item *pitem, + struct configfs_attribute *attr, + char *page) +{ + u32 quirk, quirk_bits; + u16 idVendor, idProduct; + + idVendor = to_device_cfgfs_group(pitem)->idVendor; + idProduct = to_device_cfgfs_group(pitem)->idProduct; + BUG_ON(idVendor == 0); + + quirk_bits = find_quirk_bits_by_name(attr->ca_name); + BUG_ON(quirk_bits == 0); + + quirk = usbhid_lookup_any_quirk(idVendor, idProduct) & quirk_bits; + + page[0] = (quirk == 0) ? '0' : '1'; + + return 1; +} + + +static ssize_t device_cfgfs_store_attr(struct config_item *pitem, + struct configfs_attribute *attr, + const char *page, size_t len) +{ + u32 quirks, quirk_bits; + u16 idVendor, idProduct; + + idVendor = to_device_cfgfs_group(pitem)->idVendor; + idProduct = to_device_cfgfs_group(pitem)->idProduct; + BUG_ON(idVendor == 0); + + quirk_bits = find_quirk_bits_by_name(attr->ca_name); + BUG_ON(quirk_bits == 0); + + quirks = usbhid_lookup_any_quirk(idVendor, idProduct); + + if (page[0] == '0') { + quirks &= ~quirk_bits; + } else if (page[0] == '1') { + quirks |= quirk_bits; + } else { + dbg("Invalid quirks format"); + return -EINVAL; + } + + if (usbhid_modify_equirk(idVendor, idProduct, quirks) != 0) { + dbg("Could not modify equirks list"); + return -EINVAL; + } + + return len; +} + + +static void device_cfgfs_release(struct config_item *item) +{ + struct device_cfgfs_group *dg; + + dg = to_device_cfgfs_group(item); + + WARN_ON(dg->idVendor == 0); + + usbhid_remove_equirk(dg->idVendor, dg->idProduct); + + kfree(dg); +} + + +static struct configfs_item_operations device_cfgfs_iops = { + .show_attribute = &device_cfgfs_show_attr, + .store_attribute = &device_cfgfs_store_attr, + .release = &device_cfgfs_release +}; + +static struct config_item_type device_cfgfs_itype = { + .ct_item_ops = &device_cfgfs_iops, + .ct_owner = THIS_MODULE +}; + + +/* "/usbhid/quirks_runtime" configfs code and data */ + +/* Called upon device quirk add via mkdir() */ +static struct config_group *qrt_cfgfs_make_group(struct config_group *pgroup, + const char *name) +{ + struct device_cfgfs_group *dg; + u16 idVendor, idProduct; + + /* The directory name created here must match + * a particular pattern: hex two-byte vendor ID, colon, hex + * two-byte product ID. */ + if (hid_parse_dev_fname(name, &idVendor, &idProduct) != 0) + return NULL; + + dg = kzalloc(sizeof(struct device_cfgfs_group), GFP_KERNEL); + if (!dg) { + dbg("Cannot allocate struct device_cfgfs_group"); + return NULL; + } + + config_group_init_type_name(&dg->group, name, &device_cfgfs_itype); + + dg->idVendor = idVendor; + dg->idProduct = idProduct; + + return &dg->group; +} + + +static struct configfs_group_operations qrt_cfgfs_gops = { + .make_group = &qrt_cfgfs_make_group +}; + +static struct config_item_type qrt_cfgfs_type = { + .ct_group_ops = &qrt_cfgfs_gops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group qrt_cfgfs_group = { + .cg_item = { + .ci_namebuf = "quirks_runtime", + .ci_type = &qrt_cfgfs_type + } +}; + +/* "/usbhid" cfgfs subsystem */ + +static struct config_group *hid_cfgfs_default_groups[] = { + &qrt_cfgfs_group, + NULL +}; + +static struct config_item_type hid_cfgfs_itype = { + .ct_owner = THIS_MODULE +}; + +static struct configfs_subsystem hid_cfgfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "usbhid", + .ci_type = &hid_cfgfs_itype + }, + .default_groups = hid_cfgfs_default_groups + }, +}; + + +int usbhid_cfgfs_init(void) +{ + struct configfs_attribute **ca; + int err = 0; + int n = 0; + + /* + * Dynamically allocate ConfigFS attributes from + * hid_quirk_names[] + */ + + ca = kzalloc(sizeof(struct configfs_attribute *) * + ARRAY_SIZE(hid_quirk_names), GFP_KERNEL); + if (!ca) { + dbg("Cannot allocate cfgfs attribute array"); + return -ENOMEM; + } + + for (; hid_quirk_names[n].quirk; n++) + ca[n] = &hid_quirk_names[n].ca; + + device_cfgfs_itype.ct_attrs = ca; + + /* standard configfs init follows */ + + config_group_init(&qrt_cfgfs_group); + config_group_init(&hid_cfgfs_subsys.su_group); + init_MUTEX(&hid_cfgfs_subsys.su_sem); + err = configfs_register_subsystem(&hid_cfgfs_subsys); + if (err) { + printk(KERN_ERR "Error %d while registering cfgfs subsystem\n", + err); + kfree(ca); + } + + return err; +} + +void usbhid_cfgfs_exit(void) +{ + configfs_unregister_subsystem(&hid_cfgfs_subsys); + kfree(device_cfgfs_itype.ct_attrs); +} + +#else + +int usbhid_cfgfs_init(void) { return 0; } +void usbhid_cfgfs_exit(void) {} + +#endif /* CONFIG_USB_HID_CONFIGFS */ +