linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 6/6] usbhid: add configfs USB HID quirk configuration
@ 2007-04-11 19:03 Paul Walmsley
  0 siblings, 0 replies; only message in thread
From: Paul Walmsley @ 2007-04-11 19:03 UTC (permalink / raw)
  To: linux-input

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/hid/usbhid/Kconfig            |   20 +
  drivers/hid/usbhid/Makefile           |    2
  drivers/hid/usbhid/hid-core.c         |    9
  drivers/hid/usbhid/hid-quirks-cfgfs.c |  484 ++++++++++++++++++++++++++++++++++
  drivers/hid/usbhid/hid-quirks.c       |   33 ++
  include/linux/hid-quirks.h            |    5
  6 files changed, 552 insertions(+), 1 deletion(-)

Index: hid/drivers/hid/usbhid/Kconfig
===================================================================
--- hid.orig/drivers/hid/usbhid/Kconfig
+++ hid/drivers/hid/usbhid/Kconfig
@@ -96,6 +96,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: hid/drivers/hid/usbhid/Makefile
===================================================================
--- hid.orig/drivers/hid/usbhid/Makefile
+++ hid/drivers/hid/usbhid/Makefile
@@ -3,7 +3,7 @@
  #

  # Multipart objects.
-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: hid/drivers/hid/usbhid/hid-core.c
===================================================================
--- hid.orig/drivers/hid/usbhid/hid-core.c
+++ hid/drivers/hid/usbhid/hid-core.c
@@ -51,6 +51,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.
   */
@@ -1127,6 +1130,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;
@@ -1144,6 +1150,8 @@ usb_register_fail:
  hiddev_init_fail:
  	usbhid_quirks_exit();
  usbhid_quirks_init_fail:
+	usbhid_cfgfs_exit();
+usbhid_cfgfs_init_fail:
  	return retval;
  }

@@ -1152,6 +1160,7 @@ static void __exit hid_exit(void)
  	usb_deregister(&hid_driver);
  	hiddev_exit();
  	usbhid_quirks_exit();
+	usbhid_cfgfs_exit();
  }

  module_init(hid_init);
Index: hid/include/linux/hid-quirks.h
===================================================================
--- hid.orig/include/linux/hid-quirks.h
+++ hid/include/linux/hid-quirks.h
@@ -8,6 +8,10 @@

  /*
   * HID device quirks.
+ *
+ * Any changes made to this list should also be applied to
+ * drivers/hid/usbhid/hid-quirks-cfgfs.c:hid_quirk_types[]
+ *
   */

  #define HID_QUIRK_INVERT			0x00000001
@@ -37,5 +41,6 @@
  u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct);

  int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct, const u32 quirks);
+int usbhid_remove_dquirk(const u16 idVendor, const u16 idProduct);

  #endif /* __HID_QUIRKS_H */
Index: hid/drivers/hid/usbhid/hid-quirks.c
===================================================================
--- hid.orig/drivers/hid/usbhid/hid-quirks.c
+++ hid/drivers/hid/usbhid/hid-quirks.c
@@ -652,6 +652,39 @@ int usbhid_modify_dquirk(const u16 idVen


  /**
+ * usbhid_remove_dquirk: 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_dquirk(const u16 idVendor, const u16 idProduct)
+{
+	struct quirks_list_struct *q, *temp;
+	int ret = -ENOENT;
+
+	WARN_ON(idVendor == 0);
+
+	down_write(&dquirks_rwsem);
+
+        list_for_each_entry_safe(q, temp, &dquirks_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(&dquirks_rwsem);
+
+	return ret;
+}
+
+
+/**
   * usbhid_remove_all_dquirks: remove all runtime HID quirks from memory
   *
   * Description:
Index: hid/drivers/hid/usbhid/hid-quirks-cfgfs.c
===================================================================
--- /dev/null
+++ hid/drivers/hid/usbhid/hid-quirks-cfgfs.c
@@ -0,0 +1,484 @@
+/*
+ *  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_type {
+	u32 quirk;
+	struct configfs_attribute ca;
+} hid_quirk_types[] = {
+	{
+		.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_DESCRIPTOR,
+		.ca ={
+			.ca_name = "logitech_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 = HID_QUIRK_RESET_LEDS,
+		.ca = {
+			.ca_name = "reset_leds",
+			.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_types[n].quirk; n++)
+		if (strcmp(hid_quirk_types[n].ca.ca_name, name) == 0)
+			break;
+
+	return (hid_quirk_types[n].quirk) ? hid_quirk_types[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_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_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_dquirk(idVendor, idProduct, quirks) != 0) {
+		dbg("Could not modify dquirks 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_dquirk(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_types[]
+	 */
+
+	ca = kzalloc(sizeof(struct configfs_attribute *) *
+		     ARRAY_SIZE(hid_quirk_types), GFP_KERNEL);
+	if (!ca) {
+		dbg("Cannot allocate cfgfs attribute array");
+		return -ENOMEM;
+	}
+
+	for (; hid_quirk_types[n].quirk; n++)
+		ca[n] = &hid_quirk_types[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 */
+

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2007-04-11 19:03 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-04-11 19:03 [PATCH 6/6] usbhid: add configfs USB HID quirk configuration Paul Walmsley

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).