From: Paul Walmsley <paul@booyaka.com>
To: linux-input@atrey.karlin.mff.cuni.cz
Subject: [PATCH 7/7] usbhid: add configfs USB HID quirk configuration
Date: Wed, 11 Apr 2007 00:50:21 -0600 (MDT) [thread overview]
Message-ID: <Pine.LNX.4.64.0704110037520.28726@utopia.booyaka.com> (raw)
From: Paul Walmsley <paul@booyaka.com>
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 <paul@booyaka.com>
---
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 <asm/types.h> /* for u32, u16 */
+#include <linux/slab.h> /* for kmalloc/kfree */
+#include <linux/kernel.h> /* for ARRAY_SIZE */
+#include <linux/limits.h> /* for NAME_MAX */
+#include <linux/configfs.h>
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+#include <linux/hid-quirks.h>
+#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/<device>" 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 */
+
reply other threads:[~2007-04-11 6:50 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Pine.LNX.4.64.0704110037520.28726@utopia.booyaka.com \
--to=paul@booyaka.com \
--cc=linux-input@atrey.karlin.mff.cuni.cz \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).