From fcc91b86d4afd11a96334d55e438dcfffba83016 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Sun, 16 Nov 2008 00:16:29 +0100 Subject: adding usbhid-dkms 0.10.1 to GIT Signed-off-by: Henrik Rydberg --- HACKING | 27 + Makefile | 38 + debian/README.Debian | 11 + debian/changelog | 11 + debian/compat | 1 + debian/control | 13 + debian/copyright | 30 + debian/dirs | 1 + debian/postinst | 71 ++ debian/postrm | 27 + debian/prerm | 28 + debian/rules | 53 + scripts/patch-name-version.sh | 9 + usr/src/dkms_main_tree/dkms_dbversion | 1 + usr/src/dkms_source_tree/KERNEL_SOURCE_VERSION | 1 + usr/src/dkms_source_tree/Makefile | 38 + usr/src/dkms_source_tree/dkms.conf | 9 + usr/src/dkms_source_tree/hid-core.c | 1145 +++++++++++++++++ usr/src/dkms_source_tree/hid-ff.c | 95 ++ usr/src/dkms_source_tree/hid-lg2ff.c | 114 ++ usr/src/dkms_source_tree/hid-lgff.c | 151 +++ usr/src/dkms_source_tree/hid-pidff.c | 1332 ++++++++++++++++++++ usr/src/dkms_source_tree/hid-plff.c | 139 ++ usr/src/dkms_source_tree/hid-quirks.c | 1224 ++++++++++++++++++ usr/src/dkms_source_tree/hid-tmff.c | 225 ++++ usr/src/dkms_source_tree/hid-zpff.c | 107 ++ usr/src/dkms_source_tree/hiddev.c | 893 +++++++++++++ .../10-ignore-trackpad-on-unibody-macbooks.patch | 24 + usr/src/dkms_source_tree/usbhid.h | 88 ++ usr/src/dkms_source_tree/usbkbd.c | 365 ++++++ usr/src/dkms_source_tree/usbmouse.c | 253 ++++ 31 files changed, 6524 insertions(+) create mode 100644 HACKING create mode 100644 Makefile create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100644 debian/prerm create mode 100755 debian/rules create mode 100755 scripts/patch-name-version.sh create mode 100644 usr/src/dkms_main_tree/dkms_dbversion create mode 100644 usr/src/dkms_source_tree/KERNEL_SOURCE_VERSION create mode 100644 usr/src/dkms_source_tree/Makefile create mode 100644 usr/src/dkms_source_tree/dkms.conf create mode 100644 usr/src/dkms_source_tree/hid-core.c create mode 100644 usr/src/dkms_source_tree/hid-ff.c create mode 100644 usr/src/dkms_source_tree/hid-lg2ff.c create mode 100644 usr/src/dkms_source_tree/hid-lgff.c create mode 100644 usr/src/dkms_source_tree/hid-pidff.c create mode 100644 usr/src/dkms_source_tree/hid-plff.c create mode 100644 usr/src/dkms_source_tree/hid-quirks.c create mode 100644 usr/src/dkms_source_tree/hid-tmff.c create mode 100644 usr/src/dkms_source_tree/hid-zpff.c create mode 100644 usr/src/dkms_source_tree/hiddev.c create mode 100644 usr/src/dkms_source_tree/patches/10-ignore-trackpad-on-unibody-macbooks.patch create mode 100644 usr/src/dkms_source_tree/usbhid.h create mode 100644 usr/src/dkms_source_tree/usbkbd.c create mode 100644 usr/src/dkms_source_tree/usbmouse.c diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..4a27fda --- /dev/null +++ b/HACKING @@ -0,0 +1,27 @@ +Modifying and building the DKMS package + +1. use bzr to sync source with launchpad + +2. make your changes + +3. modify the VERSION variable in Makefile, then do + + make bump + +4. add a new entry to debian/changelog, matching VERSION + +5. build binary package for testing with + + dpkg-buildpackage + +6. clean the checkout with + + make distclean + +7. build source package for upload with + + debuild -S -sa + +8. upload packacke to ppa with (see ~/.dput.cf) + + dput your-mactel-ppa-dput-entry name-and-version_source.changes diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..11db0ef --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +NAME = usbhid +VERSION = 0.10.2 +SRC = usr/src +DKMSSRC = $(SRC)/dkms_source_tree + +TARBALL = $(NAME)-$(VERSION).dkms.tar.gz +PATCH = ./scripts/patch-name-version.sh $(NAME) $(VERSION) + +KERNSRC = $(HOME)/code/kernel/intrepid + +all: $(SRC)/$(TARBALL) + +$(SRC)/$(TARBALL): $(DKMSSRC)/hid-quirks.c + (cd $(SRC); tar cvfz $(TARBALL) dkms*) + +clean: + rm -f $(SRC)/$(TARBALL) + +distclean: clean + rm -rf *-stamp debian/$(NAME)-dkms debian/*.log debian/files + +install: $(SRC)/$(TARBALL) + install -d "$(DESTDIR)/$(SRC)" + (cd $(SRC); install -m 644 $(NAME)-$(VERSION).dkms.tar.gz "$(DESTDIR)/$(SRC)") + +bump: + $(PATCH) debian/postinst + $(PATCH) debian/postrm + $(PATCH) debian/prerm + $(PATCH) debian/rules + $(PATCH) debian/postinst + $(PATCH) $(DKMSSRC)/dkms.conf + chmod 0755 debian/rules + +fetch: + cp $(KERNSRC)/drivers/hid/usbhid/*.[hc] $(DKMSSRC) + (cd $(DKMSSRC); cat patches/*.patch | patch -p4) + uname -r > $(DKMSSRC)/KERNEL_SOURCE_VERSION diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..4933f48 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,11 @@ +usbhid DKMS module for Debian + +This package adds supplementary quirks mappings. + +* Install the debian package (uses dkms) + +* Reboot + +Enjoy! + +Henrik Rydberg diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..45e9ae0 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,11 @@ +usbhid-dkms (0.10.2) unstable; urgency=low + + * Upgrade to kernel Ubuntu-2.6.27-8.17 + + -- Henrik Rydberg Tue, 11 Nov 2008 02:55:02 +0100 + +usbhid-dkms (0.10.1) unstable; urgency=low + + * Ignore trackpad on new unibody Macbooks + + -- Henrik Rydberg Fri, 31 Oct 2008 22:20:49 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..7644cdc --- /dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: usbhid-dkms +Section: misc +Priority: optional +Maintainer: Henrik Rydberg +Build-Depends: debhelper (>= 5) +Standards-Version: 3.7.2 +Homepage: https://launchpad.net/~rydberg + +Package: usbhid-dkms +Architecture: all +Depends: dkms (>= 1.95), bash (>> 1.99) +Description: Supplementary usbhid support + This package makes usbhid ignore the trackpad on the new unibody Macbooks. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e7e1901 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,30 @@ +The usbhid-dkms package was created using DKMS + +Source URL: https://launchpad.net/~rydberg + +Upstream Author: Intrepid kernel 2.6.27 + +Copyright: + Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) + + See the hid-quirks.c kernel source for a full list of credits. + +License: + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Packaging: + Copyright (C) 2008 by Henrik Rydberg + released under GPL 2 diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..b601f22 --- /dev/null +++ b/debian/dirs @@ -0,0 +1 @@ +usr/src diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..3da20fa --- /dev/null +++ b/debian/postinst @@ -0,0 +1,71 @@ +#!/bin/sh + +set -e + +NAME=usbhid +VERSION=0.10.2 + +case "$1" in + configure) + # Determine current arch / kernel + c_arch=`uname -m` + c_kern=`uname -r` + + if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then + echo "Removing old $NAME-$VERSION DKMS files..." + dkms remove -m $NAME -v $VERSION --all + fi + + echo "Loading new $NAME-$VERSION DKMS files..." + #dkms add -m $NAME -v $VERSION + dkms ldtarball --archive "/usr/src/$NAME-$VERSION.dkms.tar.gz" + rm -f "/usr/src/$NAME-$VERSION.dkms.tar.gz" + + echo "Installing prebuilt kernel module binaries (if any)" + set +e + IFS=' +' + for kern in `dkms status -m $NAME -v $VERSION -a $c_arch | grep ": built" | awk {'print $3'} | sed 's/,$//'`; do + echo "Trying kernel: $kern" + dkms install --force -m $NAME -v $VERSION -k $kern -a $c_arch + done + unset IFS + set -e + + # If none installed, install. + ##if [ `dkms status -m $NAME -v $VERSION -k $c_kern -a $c_arch | grep -c ": installed"` -eq 0 ]; then + dkms_status=`dkms status -m $NAME -v $VERSION -k $c_kern -a $c_arch` + if [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then + if [ `echo $c_kern | grep -c "BOOT"` -eq 0 ] && [ -e "/lib/modules/$c_kern/build/include" ]; then + # Only build if we need to. + if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then + echo "Building module..." + dkms build -m $NAME -v $VERSION + fi + echo "Installing module..." + dkms install -m $NAME -v $VERSION + elif [ `echo $c_kern | grep -c "BOOT"` -gt 0 ]; then + echo -e "" + echo -e "Module build for the currently running kernel was skipped since you" + echo -e "are running a BOOT variant of the kernel." + else + echo -e "" + echo -e "Module build for the currently running kernel was skipped since the" + echo -e "kernel source for this kernel does not seem to be installed." + fi + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 + diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..556824e --- /dev/null +++ b/debian/postrm @@ -0,0 +1,27 @@ +#!/bin/sh + +NAME=usbhid +VERSION=0.10.2 + +set -e + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + moddir="/lib/modules/`uname -r`/kernel/" + depmod + if [ -d "/usr/src/$NAME-$VERSION" ]; then + echo "Removing old module source..." + rm -rf "/usr/src/$NAME-$VERSION" + fi + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 + diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 0000000..74b6234 --- /dev/null +++ b/debian/prerm @@ -0,0 +1,28 @@ +#!/bin/sh + +NAME=usbhid +VERSION=0.10.2 + +set -e + +case "$1" in + remove|upgrade|deconfigure) + if [ "`dkms status -m $NAME`" ]; then + dkms remove -m $NAME -v $VERSION --all + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 + + diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..76a2a3b --- /dev/null +++ b/debian/rules @@ -0,0 +1,53 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +NAME=usbhid +VERSION=0.10.2 + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + $(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + -$(MAKE) clean + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + $(MAKE) DESTDIR=$(CURDIR)/debian/$(NAME)-dkms NAME=$(NAME) VERSION=$(VERSION) install + +binary-arch: build install + +binary-indep: build install + dh_testdir + dh_testroot + dh_link + dh_strip + dh_compress + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/scripts/patch-name-version.sh b/scripts/patch-name-version.sh new file mode 100755 index 0000000..d1a7796 --- /dev/null +++ b/scripts/patch-name-version.sh @@ -0,0 +1,9 @@ +#!/bin/sh +#1: name +#2: version +#3: file +cat $3 \ +| sed "s/^NAME=.*/NAME=$1/" \ +| sed "s/^VERSION=.*/VERSION=$2/" \ +> /tmp/$1.$$ +mv /tmp/$1.$$ $3 diff --git a/usr/src/dkms_main_tree/dkms_dbversion b/usr/src/dkms_main_tree/dkms_dbversion new file mode 100644 index 0000000..227cea2 --- /dev/null +++ b/usr/src/dkms_main_tree/dkms_dbversion @@ -0,0 +1 @@ +2.0.0 diff --git a/usr/src/dkms_source_tree/KERNEL_SOURCE_VERSION b/usr/src/dkms_source_tree/KERNEL_SOURCE_VERSION new file mode 100644 index 0000000..9cd2822 --- /dev/null +++ b/usr/src/dkms_source_tree/KERNEL_SOURCE_VERSION @@ -0,0 +1 @@ +2.6.27-7-generic diff --git a/usr/src/dkms_source_tree/Makefile b/usr/src/dkms_source_tree/Makefile new file mode 100644 index 0000000..00a7b70 --- /dev/null +++ b/usr/src/dkms_source_tree/Makefile @@ -0,0 +1,38 @@ +# +# Makefile for the USB input drivers +# + +# Multipart objects. +usbhid-objs := hid-core.o hid-quirks.o + +# Optional parts of multipart objects. + +ifeq ($(CONFIG_USB_HIDDEV),y) + usbhid-objs += hiddev.o +endif +ifeq ($(CONFIG_HID_PID),y) + usbhid-objs += hid-pidff.o +endif +ifeq ($(CONFIG_LOGITECH_FF),y) + usbhid-objs += hid-lgff.o +endif +ifeq ($(CONFIG_LOGIRUMBLEPAD2_FF),y) + usbhid-objs += hid-lg2ff.o +endif +ifeq ($(CONFIG_PANTHERLORD_FF),y) + usbhid-objs += hid-plff.o +endif +ifeq ($(CONFIG_THRUSTMASTER_FF),y) + usbhid-objs += hid-tmff.o +endif +ifeq ($(CONFIG_ZEROPLUS_FF),y) + usbhid-objs += hid-zpff.o +endif +ifeq ($(CONFIG_HID_FF),y) + usbhid-objs += hid-ff.o +endif + +obj-$(CONFIG_USB_HID) += usbhid.o +obj-$(CONFIG_USB_KBD) += usbkbd.o +obj-$(CONFIG_USB_MOUSE) += usbmouse.o + diff --git a/usr/src/dkms_source_tree/dkms.conf b/usr/src/dkms_source_tree/dkms.conf new file mode 100644 index 0000000..34d0f25 --- /dev/null +++ b/usr/src/dkms_source_tree/dkms.conf @@ -0,0 +1,9 @@ +NAME=usbhid +VERSION=0.10.2 +PACKAGE_NAME="$NAME" +PACKAGE_VERSION="$VERSION" +MAKE[0]="make -C ${kernel_source_dir} + SUBDIRS=${dkms_tree}/${NAME}/${VERSION}/build modules" +BUILT_MODULE_NAME[0]="usbhid" +DEST_MODULE_LOCATION[0]="/kernel/drivers/hid/usbhid" +REMAKE_INITRD=y diff --git a/usr/src/dkms_source_tree/hid-core.c b/usr/src/dkms_source_tree/hid-core.c new file mode 100644 index 0000000..27fe4d8 --- /dev/null +++ b/usr/src/dkms_source_tree/hid-core.c @@ -0,0 +1,1145 @@ +/* + * USB HID support for Linux + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2006-2007 Jiri Kosina + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include "usbhid.h" + +/* + * Version Information + */ + +#define DRIVER_VERSION "v2.6" +#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik, Jiri Kosina" +#define DRIVER_DESC "USB HID core driver" +#define DRIVER_LICENSE "GPL" + +static char *hid_types[] = {"Device", "Pointer", "Mouse", "Device", "Joystick", + "Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller"}; +/* + * Module parameters. + */ + +static unsigned int hid_mousepoll_interval; +module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644); +MODULE_PARM_DESC(mousepoll, "Polling interval of mice"); + +/* Quirks specified at module load time */ +static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL }; +module_param_array_named(quirks, quirks_param, charp, NULL, 0444); +MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying " + " quirks=vendorID:productID:quirks" + " where vendorID, productID, and quirks are all in" + " 0x-prefixed hex"); +static char *rdesc_quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL }; +module_param_array_named(rdesc_quirks, rdesc_quirks_param, charp, NULL, 0444); +MODULE_PARM_DESC(rdesc_quirks, "Add/modify report descriptor quirks by specifying " + " rdesc_quirks=vendorID:productID:rdesc_quirks" + " where vendorID, productID, and rdesc_quirks are all in" + " 0x-prefixed hex"); +/* + * Input submission and I/O error handler. + */ + +static void hid_io_error(struct hid_device *hid); + +/* Start up the input URB */ +static int hid_start_in(struct hid_device *hid) +{ + unsigned long flags; + int rc = 0; + struct usbhid_device *usbhid = hid->driver_data; + + spin_lock_irqsave(&usbhid->inlock, flags); + if (hid->open > 0 && !test_bit(HID_SUSPENDED, &usbhid->iofl) && + !test_bit(HID_DISCONNECTED, &usbhid->iofl) && + !test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) { + rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC); + if (rc != 0) + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + } + spin_unlock_irqrestore(&usbhid->inlock, flags); + return rc; +} + +/* I/O retry timer routine */ +static void hid_retry_timeout(unsigned long _hid) +{ + struct hid_device *hid = (struct hid_device *) _hid; + struct usbhid_device *usbhid = hid->driver_data; + + dev_dbg(&usbhid->intf->dev, "retrying intr urb\n"); + if (hid_start_in(hid)) + hid_io_error(hid); +} + +/* Workqueue routine to reset the device or clear a halt */ +static void hid_reset(struct work_struct *work) +{ + struct usbhid_device *usbhid = + container_of(work, struct usbhid_device, reset_work); + struct hid_device *hid = usbhid->hid; + int rc_lock, rc = 0; + + if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) { + dev_dbg(&usbhid->intf->dev, "clear halt\n"); + rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe); + clear_bit(HID_CLEAR_HALT, &usbhid->iofl); + hid_start_in(hid); + } + + else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) { + dev_dbg(&usbhid->intf->dev, "resetting device\n"); + rc = rc_lock = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf); + if (rc_lock >= 0) { + rc = usb_reset_device(hid_to_usb_dev(hid)); + if (rc_lock) + usb_unlock_device(hid_to_usb_dev(hid)); + } + clear_bit(HID_RESET_PENDING, &usbhid->iofl); + } + + switch (rc) { + case 0: + if (!test_bit(HID_IN_RUNNING, &usbhid->iofl)) + hid_io_error(hid); + break; + default: + err_hid("can't reset device, %s-%s/input%d, status %d", + hid_to_usb_dev(hid)->bus->bus_name, + hid_to_usb_dev(hid)->devpath, + usbhid->ifnum, rc); + /* FALLTHROUGH */ + case -EHOSTUNREACH: + case -ENODEV: + case -EINTR: + break; + } +} + +/* Main I/O error handler */ +static void hid_io_error(struct hid_device *hid) +{ + unsigned long flags; + struct usbhid_device *usbhid = hid->driver_data; + + spin_lock_irqsave(&usbhid->inlock, flags); + + /* Stop when disconnected */ + if (test_bit(HID_DISCONNECTED, &usbhid->iofl)) + goto done; + + /* If it has been a while since the last error, we'll assume + * this a brand new error and reset the retry timeout. */ + if (time_after(jiffies, usbhid->stop_retry + HZ/2)) + usbhid->retry_delay = 0; + + /* When an error occurs, retry at increasing intervals */ + if (usbhid->retry_delay == 0) { + usbhid->retry_delay = 13; /* Then 26, 52, 104, 104, ... */ + usbhid->stop_retry = jiffies + msecs_to_jiffies(1000); + } else if (usbhid->retry_delay < 100) + usbhid->retry_delay *= 2; + + if (time_after(jiffies, usbhid->stop_retry)) { + + /* Retries failed, so do a port reset */ + if (!test_and_set_bit(HID_RESET_PENDING, &usbhid->iofl)) { + schedule_work(&usbhid->reset_work); + goto done; + } + } + + mod_timer(&usbhid->io_retry, + jiffies + msecs_to_jiffies(usbhid->retry_delay)); +done: + spin_unlock_irqrestore(&usbhid->inlock, flags); +} + +/* + * Input interrupt completion handler. + */ + +static void hid_irq_in(struct urb *urb) +{ + struct hid_device *hid = urb->context; + struct usbhid_device *usbhid = hid->driver_data; + int status; + + switch (urb->status) { + case 0: /* success */ + usbhid->retry_delay = 0; + hid_input_report(urb->context, HID_INPUT_REPORT, + urb->transfer_buffer, + urb->actual_length, 1); + break; + case -EPIPE: /* stall */ + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + set_bit(HID_CLEAR_HALT, &usbhid->iofl); + schedule_work(&usbhid->reset_work); + return; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: /* unplug */ + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + return; + case -EILSEQ: /* protocol error or unplug */ + case -EPROTO: /* protocol error or unplug */ + case -ETIME: /* protocol error or unplug */ + case -ETIMEDOUT: /* Should never happen, but... */ + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + hid_io_error(hid); + return; + default: /* error */ + warn("input irq status %d received", urb->status); + } + + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + clear_bit(HID_IN_RUNNING, &usbhid->iofl); + if (status != -EPERM) { + err_hid("can't resubmit intr, %s-%s/input%d, status %d", + hid_to_usb_dev(hid)->bus->bus_name, + hid_to_usb_dev(hid)->devpath, + usbhid->ifnum, status); + hid_io_error(hid); + } + } +} + +static int hid_submit_out(struct hid_device *hid) +{ + struct hid_report *report; + struct usbhid_device *usbhid = hid->driver_data; + + report = usbhid->out[usbhid->outtail]; + + hid_output_report(report, usbhid->outbuf); + usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); + usbhid->urbout->dev = hid_to_usb_dev(hid); + + dbg_hid("submitting out urb\n"); + + if (usb_submit_urb(usbhid->urbout, GFP_ATOMIC)) { + err_hid("usb_submit_urb(out) failed"); + return -1; + } + + return 0; +} + +static int hid_submit_ctrl(struct hid_device *hid) +{ + struct hid_report *report; + unsigned char dir; + int len; + struct usbhid_device *usbhid = hid->driver_data; + + report = usbhid->ctrl[usbhid->ctrltail].report; + dir = usbhid->ctrl[usbhid->ctrltail].dir; + + len = ((report->size - 1) >> 3) + 1 + (report->id > 0); + if (dir == USB_DIR_OUT) { + hid_output_report(report, usbhid->ctrlbuf); + usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); + usbhid->urbctrl->transfer_buffer_length = len; + } else { + int maxpacket, padlen; + + usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0); + maxpacket = usb_maxpacket(hid_to_usb_dev(hid), usbhid->urbctrl->pipe, 0); + if (maxpacket > 0) { + padlen = DIV_ROUND_UP(len, maxpacket); + padlen *= maxpacket; + if (padlen > usbhid->bufsize) + padlen = usbhid->bufsize; + } else + padlen = 0; + usbhid->urbctrl->transfer_buffer_length = padlen; + } + usbhid->urbctrl->dev = hid_to_usb_dev(hid); + + usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir; + usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT; + usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id); + usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum); + usbhid->cr->wLength = cpu_to_le16(len); + + dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n", + usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : "Get_Report", + usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength); + + if (usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC)) { + err_hid("usb_submit_urb(ctrl) failed"); + return -1; + } + + return 0; +} + +/* + * Output interrupt completion handler. + */ + +static void hid_irq_out(struct urb *urb) +{ + struct hid_device *hid = urb->context; + struct usbhid_device *usbhid = hid->driver_data; + unsigned long flags; + int unplug = 0; + + switch (urb->status) { + case 0: /* success */ + break; + case -ESHUTDOWN: /* unplug */ + unplug = 1; + case -EILSEQ: /* protocol error or unplug */ + case -EPROTO: /* protocol error or unplug */ + case -ECONNRESET: /* unlink */ + case -ENOENT: + break; + default: /* error */ + warn("output irq status %d received", urb->status); + } + + spin_lock_irqsave(&usbhid->outlock, flags); + + if (unplug) + usbhid->outtail = usbhid->outhead; + else + usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1); + + if (usbhid->outhead != usbhid->outtail) { + if (hid_submit_out(hid)) { + clear_bit(HID_OUT_RUNNING, &usbhid->iofl); + wake_up(&usbhid->wait); + } + spin_unlock_irqrestore(&usbhid->outlock, flags); + return; + } + + clear_bit(HID_OUT_RUNNING, &usbhid->iofl); + spin_unlock_irqrestore(&usbhid->outlock, flags); + wake_up(&usbhid->wait); +} + +/* + * Control pipe completion handler. + */ + +static void hid_ctrl(struct urb *urb) +{ + struct hid_device *hid = urb->context; + struct usbhid_device *usbhid = hid->driver_data; + unsigned long flags; + int unplug = 0; + + spin_lock_irqsave(&usbhid->ctrllock, flags); + + switch (urb->status) { + case 0: /* success */ + if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN) + hid_input_report(urb->context, usbhid->ctrl[usbhid->ctrltail].report->type, + urb->transfer_buffer, urb->actual_length, 0); + break; + case -ESHUTDOWN: /* unplug */ + unplug = 1; + case -EILSEQ: /* protocol error or unplug */ + case -EPROTO: /* protocol error or unplug */ + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -EPIPE: /* report not available */ + break; + default: /* error */ + warn("ctrl urb status %d received", urb->status); + } + + if (unplug) + usbhid->ctrltail = usbhid->ctrlhead; + else + usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1); + + if (usbhid->ctrlhead != usbhid->ctrltail) { + if (hid_submit_ctrl(hid)) { + clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); + wake_up(&usbhid->wait); + } + spin_unlock_irqrestore(&usbhid->ctrllock, flags); + return; + } + + clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); + spin_unlock_irqrestore(&usbhid->ctrllock, flags); + wake_up(&usbhid->wait); +} + +void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir) +{ + int head; + unsigned long flags; + struct usbhid_device *usbhid = hid->driver_data; + + if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) + return; + + if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) { + + spin_lock_irqsave(&usbhid->outlock, flags); + + if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) { + spin_unlock_irqrestore(&usbhid->outlock, flags); + warn("output queue full"); + return; + } + + usbhid->out[usbhid->outhead] = report; + usbhid->outhead = head; + + if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) + if (hid_submit_out(hid)) + clear_bit(HID_OUT_RUNNING, &usbhid->iofl); + + spin_unlock_irqrestore(&usbhid->outlock, flags); + return; + } + + spin_lock_irqsave(&usbhid->ctrllock, flags); + + if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) { + spin_unlock_irqrestore(&usbhid->ctrllock, flags); + warn("control queue full"); + return; + } + + usbhid->ctrl[usbhid->ctrlhead].report = report; + usbhid->ctrl[usbhid->ctrlhead].dir = dir; + usbhid->ctrlhead = head; + + if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + if (hid_submit_ctrl(hid)) + clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); + + spin_unlock_irqrestore(&usbhid->ctrllock, flags); +} + +static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct hid_field *field; + int offset; + + if (type == EV_FF) + return input_ff_event(dev, type, code, value); + + if (type != EV_LED) + return -1; + + if ((offset = hidinput_find_field(hid, type, code, &field)) == -1) { + warn("event field not found"); + return -1; + } + + hid_set_field(field, offset, value); + usbhid_submit_report(hid, field->report, USB_DIR_OUT); + + return 0; +} + +int usbhid_wait_io(struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + if (!wait_event_timeout(usbhid->wait, + (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl) && + !test_bit(HID_OUT_RUNNING, &usbhid->iofl)), + 10*HZ)) { + dbg_hid("timeout waiting for ctrl or out queue to clear\n"); + return -1; + } + + return 0; +} + +static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + HID_REQ_SET_IDLE, USB_TYPE_CLASS | USB_RECIP_INTERFACE, (idle << 8) | report, + ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static int hid_get_class_descriptor(struct usb_device *dev, int ifnum, + unsigned char type, void *buf, int size) +{ + int result, retries = 4; + + memset(buf, 0, size); + + do { + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_RECIP_INTERFACE | USB_DIR_IN, + (type << 8), ifnum, buf, size, USB_CTRL_GET_TIMEOUT); + retries--; + } while (result < size && retries); + return result; +} + +int usbhid_open(struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + int res; + + if (!hid->open++) { + res = usb_autopm_get_interface(usbhid->intf); + if (res < 0) { + hid->open--; + return -EIO; + } + } + if (hid_start_in(hid)) + hid_io_error(hid); + return 0; +} + +void usbhid_close(struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + if (!--hid->open) { + usb_kill_urb(usbhid->urbin); + usb_autopm_put_interface(usbhid->intf); + } +} + +/* + * Initialize all reports + */ + +void usbhid_init_reports(struct hid_device *hid) +{ + struct hid_report *report; + struct usbhid_device *usbhid = hid->driver_data; + int err, ret; + + list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list) + usbhid_submit_report(hid, report, USB_DIR_IN); + + list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list) + usbhid_submit_report(hid, report, USB_DIR_IN); + + err = 0; + ret = usbhid_wait_io(hid); + while (ret) { + err |= ret; + if (test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + usb_kill_urb(usbhid->urbctrl); + if (test_bit(HID_OUT_RUNNING, &usbhid->iofl)) + usb_kill_urb(usbhid->urbout); + ret = usbhid_wait_io(hid); + } + + if (err) + warn("timeout initializing reports"); +} + +/* + * Reset LEDs which BIOS might have left on. For now, just NumLock (0x01). + */ +static int hid_find_field_early(struct hid_device *hid, unsigned int page, + unsigned int hid_code, struct hid_field **pfield) +{ + struct hid_report *report; + struct hid_field *field; + struct hid_usage *usage; + int i, j; + + list_for_each_entry(report, &hid->report_enum[HID_OUTPUT_REPORT].report_list, list) { + for (i = 0; i < report->maxfield; i++) { + field = report->field[i]; + for (j = 0; j < field->maxusage; j++) { + usage = &field->usage[j]; + if ((usage->hid & HID_USAGE_PAGE) == page && + (usage->hid & 0xFFFF) == hid_code) { + *pfield = field; + return j; + } + } + } + } + return -1; +} + +static void usbhid_set_leds(struct hid_device *hid) +{ + struct hid_field *field; + int offset; + + if ((offset = hid_find_field_early(hid, HID_UP_LED, 0x01, &field)) != -1) { + hid_set_field(field, offset, 0); + usbhid_submit_report(hid, field->report, USB_DIR_OUT); + } +} + +/* + * Traverse the supplied list of reports and find the longest + */ +static void hid_find_max_report(struct hid_device *hid, unsigned int type, + unsigned int *max) +{ + struct hid_report *report; + unsigned int size; + + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size = ((report->size - 1) >> 3) + 1; + if (type == HID_INPUT_REPORT && hid->report_enum[type].numbered) + size++; + if (*max < size) + *max = size; + } +} + +static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + if (!(usbhid->inbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->inbuf_dma))) + return -1; + if (!(usbhid->outbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->outbuf_dma))) + return -1; + if (!(usbhid->cr = usb_buffer_alloc(dev, sizeof(*(usbhid->cr)), GFP_ATOMIC, &usbhid->cr_dma))) + return -1; + if (!(usbhid->ctrlbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->ctrlbuf_dma))) + return -1; + + return 0; +} + +static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t count) +{ + struct usbhid_device *usbhid = hid->driver_data; + struct usb_device *dev = hid_to_usb_dev(hid); + struct usb_interface *intf = usbhid->intf; + struct usb_host_interface *interface = intf->cur_altsetting; + int ret; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ((HID_OUTPUT_REPORT + 1) << 8) | *buf, + interface->desc.bInterfaceNumber, buf + 1, count - 1, + USB_CTRL_SET_TIMEOUT); + + /* count also the report id */ + if (ret > 0) + ret++; + + return ret; +} + +static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid) +{ + struct usbhid_device *usbhid = hid->driver_data; + + usb_buffer_free(dev, usbhid->bufsize, usbhid->inbuf, usbhid->inbuf_dma); + usb_buffer_free(dev, usbhid->bufsize, usbhid->outbuf, usbhid->outbuf_dma); + usb_buffer_free(dev, sizeof(*(usbhid->cr)), usbhid->cr, usbhid->cr_dma); + usb_buffer_free(dev, usbhid->bufsize, usbhid->ctrlbuf, usbhid->ctrlbuf_dma); +} + +/* + * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller + * to "operational". Without this, the ps3 controller will not report any + * events. + */ +static void hid_fixup_sony_ps3_controller(struct usb_device *dev, int ifnum) +{ + int result; + char *buf = kmalloc(18, GFP_KERNEL); + + if (!buf) + return; + + result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (3 << 8) | 0xf2, ifnum, buf, 17, + USB_CTRL_GET_TIMEOUT); + + if (result < 0) + err_hid("%s failed: %d\n", __func__, result); + + kfree(buf); +} + +static struct hid_device *usb_hid_configure(struct usb_interface *intf) +{ + struct usb_host_interface *interface = intf->cur_altsetting; + struct usb_device *dev = interface_to_usbdev (intf); + struct hid_descriptor *hdesc; + struct hid_device *hid; + u32 quirks = 0; + unsigned int insize = 0, rsize = 0; + char *rdesc; + int n, len; + struct usbhid_device *usbhid; + + quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + /* Many keyboards and mice don't like to be polled for reports, + * so we will always set the HID_QUIRK_NOGET flag for them. */ + if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) { + if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD || + interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE) + quirks |= HID_QUIRK_NOGET; + } + + if (quirks & HID_QUIRK_IGNORE) + return NULL; + + if ((quirks & HID_QUIRK_IGNORE_MOUSE) && + (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)) + return NULL; + + + if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) && + (!interface->desc.bNumEndpoints || + usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) { + dbg_hid("class descriptor not present\n"); + return NULL; + } + + for (n = 0; n < hdesc->bNumDescriptors; n++) + if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT) + rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dbg_hid("weird size of report descriptor (%u)\n", rsize); + return NULL; + } + + if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) { + dbg_hid("couldn't allocate rdesc memory\n"); + return NULL; + } + + hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0); + + if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) { + dbg_hid("reading report descriptor failed\n"); + kfree(rdesc); + return NULL; + } + + usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct), rdesc, + rsize, rdesc_quirks_param); + + dbg_hid("report descriptor (size %u, read %d) = ", rsize, n); + for (n = 0; n < rsize; n++) + dbg_hid_line(" %02x", (unsigned char) rdesc[n]); + dbg_hid_line("\n"); + + if (!(hid = hid_parse_report(rdesc, n))) { + dbg_hid("parsing report descriptor failed\n"); + kfree(rdesc); + return NULL; + } + + kfree(rdesc); + hid->quirks = quirks; + + if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL))) + goto fail_no_usbhid; + + hid->driver_data = usbhid; + usbhid->hid = hid; + + usbhid->bufsize = HID_MIN_BUFFER_SIZE; + hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize); + hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize); + hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize); + + if (usbhid->bufsize > HID_MAX_BUFFER_SIZE) + usbhid->bufsize = HID_MAX_BUFFER_SIZE; + + hid_find_max_report(hid, HID_INPUT_REPORT, &insize); + + if (insize > HID_MAX_BUFFER_SIZE) + insize = HID_MAX_BUFFER_SIZE; + + if (hid_alloc_buffers(dev, hid)) { + hid_free_buffers(dev, hid); + goto fail; + } + + hid->name[0] = 0; + + if (dev->manufacturer) + strlcpy(hid->name, dev->manufacturer, sizeof(hid->name)); + + if (dev->product) { + if (dev->manufacturer) + strlcat(hid->name, " ", sizeof(hid->name)); + strlcat(hid->name, dev->product, sizeof(hid->name)); + } + + if (!strlen(hid->name)) + snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + for (n = 0; n < interface->desc.bNumEndpoints; n++) { + + struct usb_endpoint_descriptor *endpoint; + int pipe; + int interval; + + endpoint = &interface->endpoint[n].desc; + if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */ + continue; + + interval = endpoint->bInterval; + + /* Some vendors give fullspeed interval on highspeed devides */ + if (quirks & HID_QUIRK_FULLSPEED_INTERVAL && + dev->speed == USB_SPEED_HIGH) { + interval = fls(endpoint->bInterval*8); + printk(KERN_INFO "%s: Fixing fullspeed to highspeed interval: %d -> %d\n", + hid->name, endpoint->bInterval, interval); + } + + /* Change the polling interval of mice. */ + if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0) + interval = hid_mousepoll_interval; + + if (usb_endpoint_dir_in(endpoint)) { + if (usbhid->urbin) + continue; + if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL))) + goto fail; + pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); + usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize, + hid_irq_in, hid, interval); + usbhid->urbin->transfer_dma = usbhid->inbuf_dma; + usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } else { + if (usbhid->urbout) + continue; + if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL))) + goto fail; + pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress); + usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0, + hid_irq_out, hid, interval); + usbhid->urbout->transfer_dma = usbhid->outbuf_dma; + usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + } + + if (!usbhid->urbin) { + err_hid("couldn't find an input interrupt endpoint"); + goto fail; + } + + init_waitqueue_head(&usbhid->wait); + INIT_WORK(&usbhid->reset_work, hid_reset); + setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid); + + spin_lock_init(&usbhid->inlock); + spin_lock_init(&usbhid->outlock); + spin_lock_init(&usbhid->ctrllock); + + hid->version = le16_to_cpu(hdesc->bcdHID); + hid->country = hdesc->bCountryCode; + hid->dev = &intf->dev; + usbhid->intf = intf; + usbhid->ifnum = interface->desc.bInterfaceNumber; + + hid->bus = BUS_USB; + hid->vendor = le16_to_cpu(dev->descriptor.idVendor); + hid->product = le16_to_cpu(dev->descriptor.idProduct); + + usb_make_path(dev, hid->phys, sizeof(hid->phys)); + strlcat(hid->phys, "/input", sizeof(hid->phys)); + len = strlen(hid->phys); + if (len < sizeof(hid->phys) - 1) + snprintf(hid->phys + len, sizeof(hid->phys) - len, + "%d", intf->altsetting[0].desc.bInterfaceNumber); + + if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) + hid->uniq[0] = 0; + + usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL); + if (!usbhid->urbctrl) + goto fail; + + usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr, + usbhid->ctrlbuf, 1, hid_ctrl, hid); + usbhid->urbctrl->setup_dma = usbhid->cr_dma; + usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma; + usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP); + hid->hidinput_input_event = usb_hidinput_input_event; + hid->hid_open = usbhid_open; + hid->hid_close = usbhid_close; +#ifdef CONFIG_USB_HIDDEV + hid->hiddev_hid_event = hiddev_hid_event; + hid->hiddev_report_event = hiddev_report_event; +#endif + hid->hid_output_raw_report = usbhid_output_raw_report; + return hid; + +fail: + usb_free_urb(usbhid->urbin); + usb_free_urb(usbhid->urbout); + usb_free_urb(usbhid->urbctrl); + hid_free_buffers(dev, hid); + kfree(usbhid); +fail_no_usbhid: + hid_free_device(hid); + + return NULL; +} + +static void hid_disconnect(struct usb_interface *intf) +{ + struct hid_device *hid = usb_get_intfdata (intf); + struct usbhid_device *usbhid; + + if (!hid) + return; + + usbhid = hid->driver_data; + + spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ + usb_set_intfdata(intf, NULL); + set_bit(HID_DISCONNECTED, &usbhid->iofl); + spin_unlock_irq(&usbhid->inlock); + usb_kill_urb(usbhid->urbin); + usb_kill_urb(usbhid->urbout); + usb_kill_urb(usbhid->urbctrl); + + del_timer_sync(&usbhid->io_retry); + cancel_work_sync(&usbhid->reset_work); + + if (hid->claimed & HID_CLAIMED_INPUT) + hidinput_disconnect(hid); + if (hid->claimed & HID_CLAIMED_HIDDEV) + hiddev_disconnect(hid); + if (hid->claimed & HID_CLAIMED_HIDRAW) + hidraw_disconnect(hid); + + usb_free_urb(usbhid->urbin); + usb_free_urb(usbhid->urbctrl); + usb_free_urb(usbhid->urbout); + + hid_free_buffers(hid_to_usb_dev(hid), hid); + kfree(usbhid); + hid_free_device(hid); +} + +static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct hid_device *hid; + char path[64]; + int i; + char *c; + + dbg_hid("HID probe called for ifnum %d\n", + intf->altsetting->desc.bInterfaceNumber); + + if (!(hid = usb_hid_configure(intf))) + return -ENODEV; + + usbhid_init_reports(hid); + hid_dump_device(hid); + if (hid->quirks & HID_QUIRK_RESET_LEDS) + usbhid_set_leds(hid); + + if (!hidinput_connect(hid)) + hid->claimed |= HID_CLAIMED_INPUT; + if (!hiddev_connect(hid)) + hid->claimed |= HID_CLAIMED_HIDDEV; + if (!hidraw_connect(hid)) + hid->claimed |= HID_CLAIMED_HIDRAW; + + usb_set_intfdata(intf, hid); + + if (!hid->claimed) { + printk ("HID device claimed by neither input, hiddev nor hidraw\n"); + hid_disconnect(intf); + return -ENODEV; + } + + if ((hid->claimed & HID_CLAIMED_INPUT)) + hid_ff_init(hid); + + if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER) + hid_fixup_sony_ps3_controller(interface_to_usbdev(intf), + intf->cur_altsetting->desc.bInterfaceNumber); + + printk(KERN_INFO); + + if (hid->claimed & HID_CLAIMED_INPUT) + printk("input"); + if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) || + hid->claimed & HID_CLAIMED_HIDRAW)) + printk(","); + if (hid->claimed & HID_CLAIMED_HIDDEV) + printk("hiddev%d", hid->minor); + if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) && + (hid->claimed & HID_CLAIMED_HIDRAW)) + printk(","); + if (hid->claimed & HID_CLAIMED_HIDRAW) + printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor); + + c = "Device"; + for (i = 0; i < hid->maxcollection; i++) { + if (hid->collection[i].type == HID_COLLECTION_APPLICATION && + (hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK && + (hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) { + c = hid_types[hid->collection[i].usage & 0xffff]; + break; + } + } + + usb_make_path(interface_to_usbdev(intf), path, 63); + + printk(": USB HID v%x.%02x %s [%s] on %s\n", + hid->version >> 8, hid->version & 0xff, c, hid->name, path); + + return 0; +} + +static int hid_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct hid_device *hid = usb_get_intfdata (intf); + struct usbhid_device *usbhid = hid->driver_data; + + spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ + set_bit(HID_SUSPENDED, &usbhid->iofl); + spin_unlock_irq(&usbhid->inlock); + del_timer(&usbhid->io_retry); + usb_kill_urb(usbhid->urbin); + dev_dbg(&intf->dev, "suspend\n"); + return 0; +} + +static int hid_resume(struct usb_interface *intf) +{ + struct hid_device *hid = usb_get_intfdata (intf); + struct usbhid_device *usbhid = hid->driver_data; + int status; + + clear_bit(HID_SUSPENDED, &usbhid->iofl); + usbhid->retry_delay = 0; + status = hid_start_in(hid); + dev_dbg(&intf->dev, "resume status %d\n", status); + return status; +} + +/* Treat USB reset pretty much the same as suspend/resume */ +static int hid_pre_reset(struct usb_interface *intf) +{ + /* FIXME: What if the interface is already suspended? */ + hid_suspend(intf, PMSG_ON); + return 0; +} + +/* Same routine used for post_reset and reset_resume */ +static int hid_post_reset(struct usb_interface *intf) +{ + struct usb_device *dev = interface_to_usbdev (intf); + + hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0); + /* FIXME: Any more reinitialization needed? */ + + return hid_resume(intf); +} + +static struct usb_device_id hid_usb_ids [] = { + { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, + .bInterfaceClass = USB_INTERFACE_CLASS_HID }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, hid_usb_ids); + +static struct usb_driver hid_driver = { + .name = "usbhid", + .probe = hid_probe, + .disconnect = hid_disconnect, + .suspend = hid_suspend, + .resume = hid_resume, + .reset_resume = hid_post_reset, + .pre_reset = hid_pre_reset, + .post_reset = hid_post_reset, + .id_table = hid_usb_ids, + .supports_autosuspend = 1, +}; + +static int __init hid_init(void) +{ + int retval; + retval = usbhid_quirks_init(quirks_param); + if (retval) + goto usbhid_quirks_init_fail; + retval = hiddev_init(); + if (retval) + goto hiddev_init_fail; + retval = usb_register(&hid_driver); + if (retval) + goto usb_register_fail; + info(DRIVER_VERSION ":" DRIVER_DESC); + + return 0; +usb_register_fail: + hiddev_exit(); +hiddev_init_fail: + usbhid_quirks_exit(); +usbhid_quirks_init_fail: + return retval; +} + +static void __exit hid_exit(void) +{ + usb_deregister(&hid_driver); + hiddev_exit(); + usbhid_quirks_exit(); +} + +module_init(hid_init); +module_exit(hid_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); diff --git a/usr/src/dkms_source_tree/hid-ff.c b/usr/src/dkms_source_tree/hid-ff.c new file mode 100644 index 0000000..1d0dac5 --- /dev/null +++ b/usr/src/dkms_source_tree/hid-ff.c @@ -0,0 +1,95 @@ +/* + * Force feedback support for hid devices. + * Not all hid devices use the same protocol. For example, some use PID, + * other use their own proprietary procotol. + * + * Copyright (c) 2002-2004 Johann Deneux + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so by + * e-mail - mail your message to + */ + +#include + +#undef DEBUG +#include + +#include +#include "usbhid.h" + +/* + * This table contains pointers to initializers. To add support for new + * devices, you need to add the USB vendor and product ids here. + */ +struct hid_ff_initializer { + u16 idVendor; + u16 idProduct; + int (*init)(struct hid_device*); +}; + +/* + * We try pidff when no other driver is found because PID is the + * standards compliant way of implementing force feedback in HID. + * pidff_init() will quickly abort if the device doesn't appear to + * be a PID device + */ +static struct hid_ff_initializer inits[] = { +#ifdef CONFIG_LOGITECH_FF + { 0x46d, 0xc211, hid_lgff_init }, /* Logitech Cordless rumble pad */ + { 0x46d, 0xc219, hid_lgff_init }, /* Logitech Cordless rumble pad 2 */ + { 0x46d, 0xc283, hid_lgff_init }, /* Logitech Wingman Force 3d */ + { 0x46d, 0xc286, hid_lgff_init }, /* Logitech Force 3D Pro Joystick */ + { 0x46d, 0xc294, hid_lgff_init }, /* Logitech Formula Force EX */ + { 0x46d, 0xc295, hid_lgff_init }, /* Logitech MOMO force wheel */ + { 0x46d, 0xca03, hid_lgff_init }, /* Logitech MOMO force wheel */ +#endif +#ifdef CONFIG_LOGIRUMBLEPAD2_FF + { 0x46d, 0xc218, hid_lg2ff_init }, /* Logitech Rumblepad 2 */ +#endif +#ifdef CONFIG_PANTHERLORD_FF + { 0x810, 0x0001, hid_plff_init }, /* "Twin USB Joystick" */ + { 0xe8f, 0x0003, hid_plff_init }, /* "GreenAsia Inc. USB Joystick " */ +#endif +#ifdef CONFIG_THRUSTMASTER_FF + { 0x44f, 0xb300, hid_tmff_init }, + { 0x44f, 0xb304, hid_tmff_init }, + { 0x44f, 0xb651, hid_tmff_init }, /* FGT Rumble Force Wheel */ + { 0x44f, 0xb654, hid_tmff_init }, /* FGT Force Feedback Wheel */ +#endif +#ifdef CONFIG_ZEROPLUS_FF + { 0xc12, 0x0005, hid_zpff_init }, + { 0xc12, 0x0030, hid_zpff_init }, +#endif + { 0, 0, hid_pidff_init} /* Matches anything */ +}; + +int hid_ff_init(struct hid_device* hid) +{ + struct hid_ff_initializer *init; + int vendor = le16_to_cpu(hid_to_usb_dev(hid)->descriptor.idVendor); + int product = le16_to_cpu(hid_to_usb_dev(hid)->descriptor.idProduct); + + for (init = inits; init->idVendor; init++) + if (init->idVendor == vendor && init->idProduct == product) + break; + + return init->init(hid); +} +EXPORT_SYMBOL_GPL(hid_ff_init); + diff --git a/usr/src/dkms_source_tree/hid-lg2ff.c b/usr/src/dkms_source_tree/hid-lg2ff.c new file mode 100644 index 0000000..d469bd0 --- /dev/null +++ b/usr/src/dkms_source_tree/hid-lg2ff.c @@ -0,0 +1,114 @@ +/* + * Force feedback support for Logitech Rumblepad 2 + * + * Copyright (c) 2008 Anssi Hannula + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include "usbhid.h" + +struct lg2ff_device { + struct hid_report *report; +}; + +static int play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct lg2ff_device *lg2ff = data; + int weak, strong; + + strong = effect->u.rumble.strong_magnitude; + weak = effect->u.rumble.weak_magnitude; + + if (weak || strong) { + weak = weak * 0xff / 0xffff; + strong = strong * 0xff / 0xffff; + + lg2ff->report->field[0]->value[0] = 0x51; + lg2ff->report->field[0]->value[2] = weak; + lg2ff->report->field[0]->value[4] = strong; + } else { + lg2ff->report->field[0]->value[0] = 0xf3; + lg2ff->report->field[0]->value[2] = 0x00; + lg2ff->report->field[0]->value[4] = 0x00; + } + + usbhid_submit_report(hid, lg2ff->report, USB_DIR_OUT); + return 0; +} + +int hid_lg2ff_init(struct hid_device *hid) +{ + struct lg2ff_device *lg2ff; + struct hid_report *report; + struct hid_input *hidinput = list_entry(hid->inputs.next, + struct hid_input, list); + struct list_head *report_list = + &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + int error; + + if (list_empty(report_list)) { + printk(KERN_ERR "hid-lg2ff: no output report found\n"); + return -ENODEV; + } + + report = list_entry(report_list->next, struct hid_report, list); + + if (report->maxfield < 1) { + printk(KERN_ERR "hid-lg2ff: output report is empty\n"); + return -ENODEV; + } + if (report->field[0]->report_count < 7) { + printk(KERN_ERR "hid-lg2ff: not enough values in the field\n"); + return -ENODEV; + } + + lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL); + if (!lg2ff) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, lg2ff, play_effect); + if (error) { + kfree(lg2ff); + return error; + } + + lg2ff->report = report; + report->field[0]->value[0] = 0xf3; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = 0x00; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x00; + report->field[0]->value[6] = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + + printk(KERN_INFO "Force feedback for Logitech Rumblepad 2 by " + "Anssi Hannula \n"); + + return 0; +} diff --git a/usr/src/dkms_source_tree/hid-lgff.c b/usr/src/dkms_source_tree/hid-lgff.c new file mode 100644 index 0000000..4b7ab6a --- /dev/null +++ b/usr/src/dkms_source_tree/hid-lgff.c @@ -0,0 +1,151 @@ +/* + * Force feedback support for hid-compliant for some of the devices from + * Logitech, namely: + * - WingMan Cordless RumblePad + * - WingMan Force 3D + * + * Copyright (c) 2002-2004 Johann Deneux + * Copyright (c) 2006 Anssi Hannula + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so by + * e-mail - mail your message to + */ + +#include +#include +#include +#include "usbhid.h" + +struct dev_type { + u16 idVendor; + u16 idProduct; + const signed short *ff; +}; + +static const signed short ff_rumble[] = { + FF_RUMBLE, + -1 +}; + +static const signed short ff_joystick[] = { + FF_CONSTANT, + -1 +}; + +static const struct dev_type devices[] = { + { 0x046d, 0xc211, ff_rumble }, + { 0x046d, 0xc219, ff_rumble }, + { 0x046d, 0xc283, ff_joystick }, + { 0x046d, 0xc286, ff_joystick }, + { 0x046d, 0xc294, ff_joystick }, + { 0x046d, 0xc295, ff_joystick }, + { 0x046d, 0xca03, ff_joystick }, +}; + +static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + int x, y; + unsigned int left, right; + +#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff + + switch (effect->type) { + case FF_CONSTANT: + x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */ + y = effect->u.ramp.end_level + 0x7f; + CLAMP(x); + CLAMP(y); + report->field[0]->value[0] = 0x51; + report->field[0]->value[1] = 0x08; + report->field[0]->value[2] = x; + report->field[0]->value[3] = y; + dbg_hid("(x, y)=(%04x, %04x)\n", x, y); + usbhid_submit_report(hid, report, USB_DIR_OUT); + break; + + case FF_RUMBLE: + right = effect->u.rumble.strong_magnitude; + left = effect->u.rumble.weak_magnitude; + right = right * 0xff / 0xffff; + left = left * 0xff / 0xffff; + CLAMP(left); + CLAMP(right); + report->field[0]->value[0] = 0x42; + report->field[0]->value[1] = 0x00; + report->field[0]->value[2] = left; + report->field[0]->value[3] = right; + dbg_hid("(left, right)=(%04x, %04x)\n", left, right); + usbhid_submit_report(hid, report, USB_DIR_OUT); + break; + } + return 0; +} + +int hid_lgff_init(struct hid_device* hid) +{ + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + struct hid_report *report; + struct hid_field *field; + const signed short *ff_bits = ff_joystick; + int error; + int i; + + /* Find the report to use */ + if (list_empty(report_list)) { + err_hid("No output report found"); + return -1; + } + + /* Check that the report looks ok */ + report = list_entry(report_list->next, struct hid_report, list); + if (!report) { + err_hid("NULL output report"); + return -1; + } + + field = report->field[0]; + if (!field) { + err_hid("NULL field"); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(devices); i++) { + if (dev->id.vendor == devices[i].idVendor && + dev->id.product == devices[i].idProduct) { + ff_bits = devices[i].ff; + break; + } + } + + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], dev->ffbit); + + error = input_ff_create_memless(dev, NULL, hid_lgff_play); + if (error) + return error; + + printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux \n"); + + return 0; +} diff --git a/usr/src/dkms_source_tree/hid-pidff.c b/usr/src/dkms_source_tree/hid-pidff.c new file mode 100644 index 0000000..0113261 --- /dev/null +++ b/usr/src/dkms_source_tree/hid-pidff.c @@ -0,0 +1,1332 @@ +/* + * Force feedback driver for USB HID PID compliant devices + * + * Copyright (c) 2005, 2006 Anssi Hannula + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("hid-pidff: " format "\n" , ## arg) + +#include +#include + +#include + +#include "usbhid.h" + +#define PID_EFFECTS_MAX 64 + +/* Report usage table used to put reports into an array */ + +#define PID_SET_EFFECT 0 +#define PID_EFFECT_OPERATION 1 +#define PID_DEVICE_GAIN 2 +#define PID_POOL 3 +#define PID_BLOCK_LOAD 4 +#define PID_BLOCK_FREE 5 +#define PID_DEVICE_CONTROL 6 +#define PID_CREATE_NEW_EFFECT 7 + +#define PID_REQUIRED_REPORTS 7 + +#define PID_SET_ENVELOPE 8 +#define PID_SET_CONDITION 9 +#define PID_SET_PERIODIC 10 +#define PID_SET_CONSTANT 11 +#define PID_SET_RAMP 12 +static const u8 pidff_reports[] = { + 0x21, 0x77, 0x7d, 0x7f, 0x89, 0x90, 0x96, 0xab, + 0x5a, 0x5f, 0x6e, 0x73, 0x74 +}; + +/* device_control is really 0x95, but 0x96 specified as it is the usage of +the only field in that report */ + +/* Value usage tables used to put fields and values into arrays */ + +#define PID_EFFECT_BLOCK_INDEX 0 + +#define PID_DURATION 1 +#define PID_GAIN 2 +#define PID_TRIGGER_BUTTON 3 +#define PID_TRIGGER_REPEAT_INT 4 +#define PID_DIRECTION_ENABLE 5 +#define PID_START_DELAY 6 +static const u8 pidff_set_effect[] = { + 0x22, 0x50, 0x52, 0x53, 0x54, 0x56, 0xa7 +}; + +#define PID_ATTACK_LEVEL 1 +#define PID_ATTACK_TIME 2 +#define PID_FADE_LEVEL 3 +#define PID_FADE_TIME 4 +static const u8 pidff_set_envelope[] = { 0x22, 0x5b, 0x5c, 0x5d, 0x5e }; + +#define PID_PARAM_BLOCK_OFFSET 1 +#define PID_CP_OFFSET 2 +#define PID_POS_COEFFICIENT 3 +#define PID_NEG_COEFFICIENT 4 +#define PID_POS_SATURATION 5 +#define PID_NEG_SATURATION 6 +#define PID_DEAD_BAND 7 +static const u8 pidff_set_condition[] = { + 0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65 +}; + +#define PID_MAGNITUDE 1 +#define PID_OFFSET 2 +#define PID_PHASE 3 +#define PID_PERIOD 4 +static const u8 pidff_set_periodic[] = { 0x22, 0x70, 0x6f, 0x71, 0x72 }; +static const u8 pidff_set_constant[] = { 0x22, 0x70 }; + +#define PID_RAMP_START 1 +#define PID_RAMP_END 2 +static const u8 pidff_set_ramp[] = { 0x22, 0x75, 0x76 }; + +#define PID_RAM_POOL_AVAILABLE 1 +static const u8 pidff_block_load[] = { 0x22, 0xac }; + +#define PID_LOOP_COUNT 1 +static const u8 pidff_effect_operation[] = { 0x22, 0x7c }; + +static const u8 pidff_block_free[] = { 0x22 }; + +#define PID_DEVICE_GAIN_FIELD 0 +static const u8 pidff_device_gain[] = { 0x7e }; + +#define PID_RAM_POOL_SIZE 0 +#define PID_SIMULTANEOUS_MAX 1 +#define PID_DEVICE_MANAGED_POOL 2 +static const u8 pidff_pool[] = { 0x80, 0x83, 0xa9 }; + +/* Special field key tables used to put special field keys into arrays */ + +#define PID_ENABLE_ACTUATORS 0 +#define PID_RESET 1 +static const u8 pidff_device_control[] = { 0x97, 0x9a }; + +#define PID_CONSTANT 0 +#define PID_RAMP 1 +#define PID_SQUARE 2 +#define PID_SINE 3 +#define PID_TRIANGLE 4 +#define PID_SAW_UP 5 +#define PID_SAW_DOWN 6 +#define PID_SPRING 7 +#define PID_DAMPER 8 +#define PID_INERTIA 9 +#define PID_FRICTION 10 +static const u8 pidff_effect_types[] = { + 0x26, 0x27, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x40, 0x41, 0x42, 0x43 +}; + +#define PID_BLOCK_LOAD_SUCCESS 0 +#define PID_BLOCK_LOAD_FULL 1 +static const u8 pidff_block_load_status[] = { 0x8c, 0x8d }; + +#define PID_EFFECT_START 0 +#define PID_EFFECT_STOP 1 +static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b }; + +struct pidff_usage { + struct hid_field *field; + s32 *value; +}; + +struct pidff_device { + struct hid_device *hid; + + struct hid_report *reports[sizeof(pidff_reports)]; + + struct pidff_usage set_effect[sizeof(pidff_set_effect)]; + struct pidff_usage set_envelope[sizeof(pidff_set_envelope)]; + struct pidff_usage set_condition[sizeof(pidff_set_condition)]; + struct pidff_usage set_periodic[sizeof(pidff_set_periodic)]; + struct pidff_usage set_constant[sizeof(pidff_set_constant)]; + struct pidff_usage set_ramp[sizeof(pidff_set_ramp)]; + + struct pidff_usage device_gain[sizeof(pidff_device_gain)]; + struct pidff_usage block_load[sizeof(pidff_block_load)]; + struct pidff_usage pool[sizeof(pidff_pool)]; + struct pidff_usage effect_operation[sizeof(pidff_effect_operation)]; + struct pidff_usage block_free[sizeof(pidff_block_free)]; + + /* Special field is a field that is not composed of + usage<->value pairs that pidff_usage values are */ + + /* Special field in create_new_effect */ + struct hid_field *create_new_effect_type; + + /* Special fields in set_effect */ + struct hid_field *set_effect_type; + struct hid_field *effect_direction; + + /* Special field in device_control */ + struct hid_field *device_control; + + /* Special field in block_load */ + struct hid_field *block_load_status; + + /* Special field in effect_operation */ + struct hid_field *effect_operation_status; + + int control_id[sizeof(pidff_device_control)]; + int type_id[sizeof(pidff_effect_types)]; + int status_id[sizeof(pidff_block_load_status)]; + int operation_id[sizeof(pidff_effect_operation_status)]; + + int pid_id[PID_EFFECTS_MAX]; +}; + +/* + * Scale an unsigned value with range 0..max for the given field + */ +static int pidff_rescale(int i, int max, struct hid_field *field) +{ + return i * (field->logical_maximum - field->logical_minimum) / max + + field->logical_minimum; +} + +/* + * Scale a signed value in range -0x8000..0x7fff for the given field + */ +static int pidff_rescale_signed(int i, struct hid_field *field) +{ + return i == 0 ? 0 : i > + 0 ? i * field->logical_maximum / 0x7fff : i * + field->logical_minimum / -0x8000; +} + +static void pidff_set(struct pidff_usage *usage, u16 value) +{ + usage->value[0] = pidff_rescale(value, 0xffff, usage->field); + debug("calculated from %d to %d", value, usage->value[0]); +} + +static void pidff_set_signed(struct pidff_usage *usage, s16 value) +{ + if (usage->field->logical_minimum < 0) + usage->value[0] = pidff_rescale_signed(value, usage->field); + else { + if (value < 0) + usage->value[0] = + pidff_rescale(-value, 0x8000, usage->field); + else + usage->value[0] = + pidff_rescale(value, 0x7fff, usage->field); + } + debug("calculated from %d to %d", value, usage->value[0]); +} + +/* + * Send envelope report to the device + */ +static void pidff_set_envelope_report(struct pidff_device *pidff, + struct ff_envelope *envelope) +{ + pidff->set_envelope[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + + pidff->set_envelope[PID_ATTACK_LEVEL].value[0] = + pidff_rescale(envelope->attack_level > + 0x7fff ? 0x7fff : envelope->attack_level, 0x7fff, + pidff->set_envelope[PID_ATTACK_LEVEL].field); + pidff->set_envelope[PID_FADE_LEVEL].value[0] = + pidff_rescale(envelope->fade_level > + 0x7fff ? 0x7fff : envelope->fade_level, 0x7fff, + pidff->set_envelope[PID_FADE_LEVEL].field); + + pidff->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length; + pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length; + + debug("attack %u => %d", envelope->attack_level, + pidff->set_envelope[PID_ATTACK_LEVEL].value[0]); + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_ENVELOPE], + USB_DIR_OUT); +} + +/* + * Test if the new envelope differs from old one + */ +static int pidff_needs_set_envelope(struct ff_envelope *envelope, + struct ff_envelope *old) +{ + return envelope->attack_level != old->attack_level || + envelope->fade_level != old->fade_level || + envelope->attack_length != old->attack_length || + envelope->fade_length != old->fade_length; +} + +/* + * Send constant force report to the device + */ +static void pidff_set_constant_force_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_constant[PID_MAGNITUDE], + effect->u.constant.level); + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONSTANT], + USB_DIR_OUT); +} + +/* + * Test if the constant parameters have changed between effects + */ +static int pidff_needs_set_constant(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->u.constant.level != old->u.constant.level; +} + +/* + * Send set effect report to the device + */ +static void pidff_set_effect_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff->set_effect_type->value[0] = + pidff->create_new_effect_type->value[0]; + pidff->set_effect[PID_DURATION].value[0] = effect->replay.length; + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button; + pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = + effect->trigger.interval; + pidff->set_effect[PID_GAIN].value[0] = + pidff->set_effect[PID_GAIN].field->logical_maximum; + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; + pidff->effect_direction->value[0] = + pidff_rescale(effect->direction, 0xffff, + pidff->effect_direction); + pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT], + USB_DIR_OUT); +} + +/* + * Test if the values used in set_effect have changed + */ +static int pidff_needs_set_effect(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->replay.length != old->replay.length || + effect->trigger.interval != old->trigger.interval || + effect->trigger.button != old->trigger.button || + effect->direction != old->direction || + effect->replay.delay != old->replay.delay; +} + +/* + * Send periodic effect report to the device + */ +static void pidff_set_periodic_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_periodic[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_periodic[PID_MAGNITUDE], + effect->u.periodic.magnitude); + pidff_set_signed(&pidff->set_periodic[PID_OFFSET], + effect->u.periodic.offset); + pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase); + pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_PERIODIC], + USB_DIR_OUT); + +} + +/* + * Test if periodic effect parameters have changed + */ +static int pidff_needs_set_periodic(struct ff_effect *effect, + struct ff_effect *old) +{ + return effect->u.periodic.magnitude != old->u.periodic.magnitude || + effect->u.periodic.offset != old->u.periodic.offset || + effect->u.periodic.phase != old->u.periodic.phase || + effect->u.periodic.period != old->u.periodic.period; +} + +/* + * Send condition effect reports to the device + */ +static void pidff_set_condition_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + int i; + + pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + + for (i = 0; i < 2; i++) { + pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i; + pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET], + effect->u.condition[i].center); + pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT], + effect->u.condition[i].right_coeff); + pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT], + effect->u.condition[i].left_coeff); + pidff_set(&pidff->set_condition[PID_POS_SATURATION], + effect->u.condition[i].right_saturation); + pidff_set(&pidff->set_condition[PID_NEG_SATURATION], + effect->u.condition[i].left_saturation); + pidff_set(&pidff->set_condition[PID_DEAD_BAND], + effect->u.condition[i].deadband); + usbhid_wait_io(pidff->hid); + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION], + USB_DIR_OUT); + } +} + +/* + * Test if condition effect parameters have changed + */ +static int pidff_needs_set_condition(struct ff_effect *effect, + struct ff_effect *old) +{ + int i; + int ret = 0; + + for (i = 0; i < 2; i++) { + struct ff_condition_effect *cond = &effect->u.condition[i]; + struct ff_condition_effect *old_cond = &old->u.condition[i]; + + ret |= cond->center != old_cond->center || + cond->right_coeff != old_cond->right_coeff || + cond->left_coeff != old_cond->left_coeff || + cond->right_saturation != old_cond->right_saturation || + cond->left_saturation != old_cond->left_saturation || + cond->deadband != old_cond->deadband; + } + + return ret; +} + +/* + * Send ramp force report to the device + */ +static void pidff_set_ramp_force_report(struct pidff_device *pidff, + struct ff_effect *effect) +{ + pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff_set_signed(&pidff->set_ramp[PID_RAMP_START], + effect->u.ramp.start_level); + pidff_set_signed(&pidff->set_ramp[PID_RAMP_END], + effect->u.ramp.end_level); + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_RAMP], + USB_DIR_OUT); +} + +/* + * Test if ramp force parameters have changed + */ +static int pidff_needs_set_ramp(struct ff_effect *effect, struct ff_effect *old) +{ + return effect->u.ramp.start_level != old->u.ramp.start_level || + effect->u.ramp.end_level != old->u.ramp.end_level; +} + +/* + * Send a request for effect upload to the device + * + * Returns 0 if device reported success, -ENOSPC if the device reported memory + * is full. Upon unknown response the function will retry for 60 times, if + * still unsuccessful -EIO is returned. + */ +static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) +{ + int j; + + pidff->create_new_effect_type->value[0] = efnum; + usbhid_submit_report(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], + USB_DIR_OUT); + debug("create_new_effect sent, type: %d", efnum); + + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; + pidff->block_load_status->value[0] = 0; + usbhid_wait_io(pidff->hid); + + for (j = 0; j < 60; j++) { + debug("pid_block_load requested"); + usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_LOAD], + USB_DIR_IN); + usbhid_wait_io(pidff->hid); + if (pidff->block_load_status->value[0] == + pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) { + debug("device reported free memory: %d bytes", + pidff->block_load[PID_RAM_POOL_AVAILABLE].value ? + pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1); + return 0; + } + if (pidff->block_load_status->value[0] == + pidff->status_id[PID_BLOCK_LOAD_FULL]) { + debug("not enough memory free: %d bytes", + pidff->block_load[PID_RAM_POOL_AVAILABLE].value ? + pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1); + return -ENOSPC; + } + } + printk(KERN_ERR "hid-pidff: pid_block_load failed 60 times\n"); + return -EIO; +} + +/* + * Play the effect with PID id n times + */ +static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) +{ + pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + + if (n == 0) { + pidff->effect_operation_status->value[0] = + pidff->operation_id[PID_EFFECT_STOP]; + } else { + pidff->effect_operation_status->value[0] = + pidff->operation_id[PID_EFFECT_START]; + pidff->effect_operation[PID_LOOP_COUNT].value[0] = n; + } + + usbhid_wait_io(pidff->hid); + usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], + USB_DIR_OUT); +} + +/** + * Play the effect with effect id @effect_id for @value times + */ +static int pidff_playback(struct input_dev *dev, int effect_id, int value) +{ + struct pidff_device *pidff = dev->ff->private; + + pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); + + return 0; +} + +/* + * Erase effect with PID id + */ +static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) +{ + pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_FREE], + USB_DIR_OUT); +} + +/* + * Stop and erase effect with effect_id + */ +static int pidff_erase_effect(struct input_dev *dev, int effect_id) +{ + struct pidff_device *pidff = dev->ff->private; + int pid_id = pidff->pid_id[effect_id]; + + debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]); + pidff_playback_pid(pidff, pid_id, 0); + pidff_erase_pid(pidff, pid_id); + + return 0; +} + +/* + * Effect upload handler + */ +static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect, + struct ff_effect *old) +{ + struct pidff_device *pidff = dev->ff->private; + int type_id; + int error; + + switch (effect->type) { + case FF_CONSTANT: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_CONSTANT]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_constant(effect, old)) + pidff_set_constant_force_report(pidff, effect); + if (!old || + pidff_needs_set_envelope(&effect->u.constant.envelope, + &old->u.constant.envelope)) + pidff_set_envelope_report(pidff, + &effect->u.constant.envelope); + break; + + case FF_PERIODIC: + if (!old) { + switch (effect->u.periodic.waveform) { + case FF_SQUARE: + type_id = PID_SQUARE; + break; + case FF_TRIANGLE: + type_id = PID_TRIANGLE; + break; + case FF_SINE: + type_id = PID_SINE; + break; + case FF_SAW_UP: + type_id = PID_SAW_UP; + break; + case FF_SAW_DOWN: + type_id = PID_SAW_DOWN; + break; + default: + printk(KERN_ERR + "hid-pidff: invalid waveform\n"); + return -EINVAL; + } + + error = pidff_request_effect_upload(pidff, + pidff->type_id[type_id]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_periodic(effect, old)) + pidff_set_periodic_report(pidff, effect); + if (!old || + pidff_needs_set_envelope(&effect->u.periodic.envelope, + &old->u.periodic.envelope)) + pidff_set_envelope_report(pidff, + &effect->u.periodic.envelope); + break; + + case FF_RAMP: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_RAMP]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_ramp(effect, old)) + pidff_set_ramp_force_report(pidff, effect); + if (!old || + pidff_needs_set_envelope(&effect->u.ramp.envelope, + &old->u.ramp.envelope)) + pidff_set_envelope_report(pidff, + &effect->u.ramp.envelope); + break; + + case FF_SPRING: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_SPRING]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_condition(effect, old)) + pidff_set_condition_report(pidff, effect); + break; + + case FF_FRICTION: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_FRICTION]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_condition(effect, old)) + pidff_set_condition_report(pidff, effect); + break; + + case FF_DAMPER: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_DAMPER]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_condition(effect, old)) + pidff_set_condition_report(pidff, effect); + break; + + case FF_INERTIA: + if (!old) { + error = pidff_request_effect_upload(pidff, + pidff->type_id[PID_INERTIA]); + if (error) + return error; + } + if (!old || pidff_needs_set_effect(effect, old)) + pidff_set_effect_report(pidff, effect); + if (!old || pidff_needs_set_condition(effect, old)) + pidff_set_condition_report(pidff, effect); + break; + + default: + printk(KERN_ERR "hid-pidff: invalid type\n"); + return -EINVAL; + } + + if (!old) + pidff->pid_id[effect->id] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + + debug("uploaded"); + + return 0; +} + +/* + * set_gain() handler + */ +static void pidff_set_gain(struct input_dev *dev, u16 gain) +{ + struct pidff_device *pidff = dev->ff->private; + + pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain); + usbhid_submit_report(pidff->hid, pidff->reports[PID_DEVICE_GAIN], + USB_DIR_OUT); +} + +static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude) +{ + struct hid_field *field = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field; + + if (!magnitude) { + pidff_playback_pid(pidff, field->logical_minimum, 0); + return; + } + + pidff_playback_pid(pidff, field->logical_minimum, 1); + + pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum; + pidff->set_effect_type->value[0] = pidff->type_id[PID_SPRING]; + pidff->set_effect[PID_DURATION].value[0] = 0; + pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = 0; + pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0; + pidff_set(&pidff->set_effect[PID_GAIN], magnitude); + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; + pidff->set_effect[PID_START_DELAY].value[0] = 0; + + usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT], + USB_DIR_OUT); +} + +/* + * pidff_set_autocenter() handler + */ +static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct pidff_device *pidff = dev->ff->private; + + pidff_autocenter(pidff, magnitude); +} + +/* + * Find fields from a report and fill a pidff_usage + */ +static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, + struct hid_report *report, int count, int strict) +{ + int i, j, k, found; + + for (k = 0; k < count; k++) { + found = 0; + for (i = 0; i < report->maxfield; i++) { + if (report->field[i]->maxusage != + report->field[i]->report_count) { + debug("maxusage and report_count do not match, " + "skipping"); + continue; + } + for (j = 0; j < report->field[i]->maxusage; j++) { + if (report->field[i]->usage[j].hid == + (HID_UP_PID | table[k])) { + debug("found %d at %d->%d", k, i, j); + usage[k].field = report->field[i]; + usage[k].value = + &report->field[i]->value[j]; + found = 1; + break; + } + } + if (found) + break; + } + if (!found && strict) { + debug("failed to locate %d", k); + return -1; + } + } + return 0; +} + +/* + * Return index into pidff_reports for the given usage + */ +static int pidff_check_usage(int usage) +{ + int i; + + for (i = 0; i < sizeof(pidff_reports); i++) + if (usage == (HID_UP_PID | pidff_reports[i])) + return i; + + return -1; +} + +/* + * Find the reports and fill pidff->reports[] + * report_type specifies either OUTPUT or FEATURE reports + */ +static void pidff_find_reports(struct hid_device *hid, int report_type, + struct pidff_device *pidff) +{ + struct hid_report *report; + int i, ret; + + list_for_each_entry(report, + &hid->report_enum[report_type].report_list, list) { + if (report->maxfield < 1) + continue; + ret = pidff_check_usage(report->field[0]->logical); + if (ret != -1) { + debug("found usage 0x%02x from field->logical", + pidff_reports[ret]); + pidff->reports[ret] = report; + continue; + } + + /* + * Sometimes logical collections are stacked to indicate + * different usages for the report and the field, in which + * case we want the usage of the parent. However, Linux HID + * implementation hides this fact, so we have to dig it up + * ourselves + */ + i = report->field[0]->usage[0].collection_index; + if (i <= 0 || + hid->collection[i - 1].type != HID_COLLECTION_LOGICAL) + continue; + ret = pidff_check_usage(hid->collection[i - 1].usage); + if (ret != -1 && !pidff->reports[ret]) { + debug("found usage 0x%02x from collection array", + pidff_reports[ret]); + pidff->reports[ret] = report; + } + } +} + +/* + * Test if the required reports have been found + */ +static int pidff_reports_ok(struct pidff_device *pidff) +{ + int i; + + for (i = 0; i <= PID_REQUIRED_REPORTS; i++) { + if (!pidff->reports[i]) { + debug("%d missing", i); + return 0; + } + } + + return 1; +} + +/* + * Find a field with a specific usage within a report + */ +static struct hid_field *pidff_find_special_field(struct hid_report *report, + int usage, int enforce_min) +{ + int i; + + for (i = 0; i < report->maxfield; i++) { + if (report->field[i]->logical == (HID_UP_PID | usage) && + report->field[i]->report_count > 0) { + if (!enforce_min || + report->field[i]->logical_minimum == 1) + return report->field[i]; + else { + printk(KERN_ERR "hid-pidff: logical_minimum " + "is not 1 as it should be\n"); + return NULL; + } + } + } + return NULL; +} + +/* + * Fill a pidff->*_id struct table + */ +static int pidff_find_special_keys(int *keys, struct hid_field *fld, + const u8 *usagetable, int count) +{ + + int i, j; + int found = 0; + + for (i = 0; i < count; i++) { + for (j = 0; j < fld->maxusage; j++) { + if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) { + keys[i] = j + 1; + found++; + break; + } + } + } + return found; +} + +#define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \ + pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ + sizeof(pidff_ ## name)) + +/* + * Find and check the special fields + */ +static int pidff_find_special_fields(struct pidff_device *pidff) +{ + debug("finding special fields"); + + pidff->create_new_effect_type = + pidff_find_special_field(pidff->reports[PID_CREATE_NEW_EFFECT], + 0x25, 1); + pidff->set_effect_type = + pidff_find_special_field(pidff->reports[PID_SET_EFFECT], + 0x25, 1); + pidff->effect_direction = + pidff_find_special_field(pidff->reports[PID_SET_EFFECT], + 0x57, 0); + pidff->device_control = + pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL], + 0x96, 1); + pidff->block_load_status = + pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD], + 0x8b, 1); + pidff->effect_operation_status = + pidff_find_special_field(pidff->reports[PID_EFFECT_OPERATION], + 0x78, 1); + + debug("search done"); + + if (!pidff->create_new_effect_type || !pidff->set_effect_type) { + printk(KERN_ERR "hid-pidff: effect lists not found\n"); + return -1; + } + + if (!pidff->effect_direction) { + printk(KERN_ERR "hid-pidff: direction field not found\n"); + return -1; + } + + if (!pidff->device_control) { + printk(KERN_ERR "hid-pidff: device control field not found\n"); + return -1; + } + + if (!pidff->block_load_status) { + printk(KERN_ERR + "hid-pidff: block load status field not found\n"); + return -1; + } + + if (!pidff->effect_operation_status) { + printk(KERN_ERR + "hid-pidff: effect operation field not found\n"); + return -1; + } + + pidff_find_special_keys(pidff->control_id, pidff->device_control, + pidff_device_control, + sizeof(pidff_device_control)); + + PIDFF_FIND_SPECIAL_KEYS(control_id, device_control, device_control); + + if (!PIDFF_FIND_SPECIAL_KEYS(type_id, create_new_effect_type, + effect_types)) { + printk(KERN_ERR "hid-pidff: no effect types found\n"); + return -1; + } + + if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status, + block_load_status) != + sizeof(pidff_block_load_status)) { + printk(KERN_ERR + "hidpidff: block load status identifiers not found\n"); + return -1; + } + + if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status, + effect_operation_status) != + sizeof(pidff_effect_operation_status)) { + printk(KERN_ERR + "hidpidff: effect operation identifiers not found\n"); + return -1; + } + + return 0; +} + +/** + * Find the implemented effect types + */ +static int pidff_find_effects(struct pidff_device *pidff, + struct input_dev *dev) +{ + int i; + + for (i = 0; i < sizeof(pidff_effect_types); i++) { + int pidff_type = pidff->type_id[i]; + if (pidff->set_effect_type->usage[pidff_type].hid != + pidff->create_new_effect_type->usage[pidff_type].hid) { + printk(KERN_ERR "hid-pidff: " + "effect type number %d is invalid\n", i); + return -1; + } + } + + if (pidff->type_id[PID_CONSTANT]) + set_bit(FF_CONSTANT, dev->ffbit); + if (pidff->type_id[PID_RAMP]) + set_bit(FF_RAMP, dev->ffbit); + if (pidff->type_id[PID_SQUARE]) { + set_bit(FF_SQUARE, dev->ffbit); + set_bit(FF_PERIODIC, dev->ffbit); + } + if (pidff->type_id[PID_SINE]) { + set_bit(FF_SINE, dev->ffbit); + set_bit(FF_PERIODIC, dev->ffbit); + } + if (pidff->type_id[PID_TRIANGLE]) { + set_bit(FF_TRIANGLE, dev->ffbit); + set_bit(FF_PERIODIC, dev->ffbit); + } + if (pidff->type_id[PID_SAW_UP]) { + set_bit(FF_SAW_UP, dev->ffbit); + set_bit(FF_PERIODIC, dev->ffbit); + } + if (pidff->type_id[PID_SAW_DOWN]) { + set_bit(FF_SAW_DOWN, dev->ffbit); + set_bit(FF_PERIODIC, dev->ffbit); + } + if (pidff->type_id[PID_SPRING]) + set_bit(FF_SPRING, dev->ffbit); + if (pidff->type_id[PID_DAMPER]) + set_bit(FF_DAMPER, dev->ffbit); + if (pidff->type_id[PID_INERTIA]) + set_bit(FF_INERTIA, dev->ffbit); + if (pidff->type_id[PID_FRICTION]) + set_bit(FF_FRICTION, dev->ffbit); + + return 0; + +} + +#define PIDFF_FIND_FIELDS(name, report, strict) \ + pidff_find_fields(pidff->name, pidff_ ## name, \ + pidff->reports[report], \ + sizeof(pidff_ ## name), strict) + +/* + * Fill and check the pidff_usages + */ +static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) +{ + int envelope_ok = 0; + + if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) { + printk(KERN_ERR + "hid-pidff: unknown set_effect report layout\n"); + return -ENODEV; + } + + PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0); + if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) { + printk(KERN_ERR + "hid-pidff: unknown pid_block_load report layout\n"); + return -ENODEV; + } + + if (PIDFF_FIND_FIELDS(effect_operation, PID_EFFECT_OPERATION, 1)) { + printk(KERN_ERR + "hid-pidff: unknown effect_operation report layout\n"); + return -ENODEV; + } + + if (PIDFF_FIND_FIELDS(block_free, PID_BLOCK_FREE, 1)) { + printk(KERN_ERR + "hid-pidff: unknown pid_block_free report layout\n"); + return -ENODEV; + } + + if (!PIDFF_FIND_FIELDS(set_envelope, PID_SET_ENVELOPE, 1)) + envelope_ok = 1; + + if (pidff_find_special_fields(pidff) || pidff_find_effects(pidff, dev)) + return -ENODEV; + + if (!envelope_ok) { + if (test_and_clear_bit(FF_CONSTANT, dev->ffbit)) + printk(KERN_WARNING "hid-pidff: " + "has constant effect but no envelope\n"); + if (test_and_clear_bit(FF_RAMP, dev->ffbit)) + printk(KERN_WARNING "hid-pidff: " + "has ramp effect but no envelope\n"); + + if (test_and_clear_bit(FF_PERIODIC, dev->ffbit)) + printk(KERN_WARNING "hid-pidff: " + "has periodic effect but no envelope\n"); + } + + if (test_bit(FF_CONSTANT, dev->ffbit) && + PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) { + printk(KERN_WARNING + "hid-pidff: unknown constant effect layout\n"); + clear_bit(FF_CONSTANT, dev->ffbit); + } + + if (test_bit(FF_RAMP, dev->ffbit) && + PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) { + printk(KERN_WARNING "hid-pidff: unknown ramp effect layout\n"); + clear_bit(FF_RAMP, dev->ffbit); + } + + if ((test_bit(FF_SPRING, dev->ffbit) || + test_bit(FF_DAMPER, dev->ffbit) || + test_bit(FF_FRICTION, dev->ffbit) || + test_bit(FF_INERTIA, dev->ffbit)) && + PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) { + printk(KERN_WARNING + "hid-pidff: unknown condition effect layout\n"); + clear_bit(FF_SPRING, dev->ffbit); + clear_bit(FF_DAMPER, dev->ffbit); + clear_bit(FF_FRICTION, dev->ffbit); + clear_bit(FF_INERTIA, dev->ffbit); + } + + if (test_bit(FF_PERIODIC, dev->ffbit) && + PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) { + printk(KERN_WARNING + "hid-pidff: unknown periodic effect layout\n"); + clear_bit(FF_PERIODIC, dev->ffbit); + } + + PIDFF_FIND_FIELDS(pool, PID_POOL, 0); + + if (!PIDFF_FIND_FIELDS(device_gain, PID_DEVICE_GAIN, 1)) + set_bit(FF_GAIN, dev->ffbit); + + return 0; +} + +/* + * Reset the device + */ +static void pidff_reset(struct pidff_device *pidff) +{ + struct hid_device *hid = pidff->hid; + int i = 0; + + pidff->device_control->value[0] = pidff->control_id[PID_RESET]; + /* We reset twice as sometimes hid_wait_io isn't waiting long enough */ + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + + pidff->device_control->value[0] = + pidff->control_id[PID_ENABLE_ACTUATORS]; + usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT); + usbhid_wait_io(hid); + + /* pool report is sometimes messed up, refetch it */ + usbhid_submit_report(hid, pidff->reports[PID_POOL], USB_DIR_IN); + usbhid_wait_io(hid); + + if (pidff->pool[PID_SIMULTANEOUS_MAX].value) { + int sim_effects = pidff->pool[PID_SIMULTANEOUS_MAX].value[0]; + while (sim_effects < 2) { + if (i++ > 20) { + printk(KERN_WARNING "hid-pidff: device reports " + "%d simultaneous effects\n", + sim_effects); + break; + } + debug("pid_pool requested again"); + usbhid_submit_report(hid, pidff->reports[PID_POOL], + USB_DIR_IN); + usbhid_wait_io(hid); + } + } +} + +/* + * Test if autocenter modification is using the supported method + */ +static int pidff_check_autocenter(struct pidff_device *pidff, + struct input_dev *dev) +{ + int error; + + /* + * Let's find out if autocenter modification is supported + * Specification doesn't specify anything, so we request an + * effect upload and cancel it immediately. If the approved + * effect id was one above the minimum, then we assume the first + * effect id is a built-in spring type effect used for autocenter + */ + + error = pidff_request_effect_upload(pidff, 1); + if (error) { + printk(KERN_ERR "hid-pidff: upload request failed\n"); + return error; + } + + if (pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] == + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + 1) { + pidff_autocenter(pidff, 0xffff); + set_bit(FF_AUTOCENTER, dev->ffbit); + } else { + printk(KERN_NOTICE "hid-pidff: " + "device has unknown autocenter control method\n"); + } + + pidff_erase_pid(pidff, + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]); + + return 0; + +} + +/* + * Check if the device is PID and initialize it + */ +int hid_pidff_init(struct hid_device *hid) +{ + struct pidff_device *pidff; + struct hid_input *hidinput = list_entry(hid->inputs.next, + struct hid_input, list); + struct input_dev *dev = hidinput->input; + struct ff_device *ff; + int max_effects; + int error; + + debug("starting pid init"); + + if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) { + debug("not a PID device, no output report"); + return -ENODEV; + } + + pidff = kzalloc(sizeof(*pidff), GFP_KERNEL); + if (!pidff) + return -ENOMEM; + + pidff->hid = hid; + + pidff_find_reports(hid, HID_OUTPUT_REPORT, pidff); + pidff_find_reports(hid, HID_FEATURE_REPORT, pidff); + + if (!pidff_reports_ok(pidff)) { + debug("reports not ok, aborting"); + error = -ENODEV; + goto fail; + } + + error = pidff_init_fields(pidff, dev); + if (error) + goto fail; + + pidff_reset(pidff); + + if (test_bit(FF_GAIN, dev->ffbit)) { + pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], 0xffff); + usbhid_submit_report(pidff->hid, pidff->reports[PID_DEVICE_GAIN], + USB_DIR_OUT); + } + + error = pidff_check_autocenter(pidff, dev); + if (error) + goto fail; + + max_effects = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_maximum - + pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + + 1; + debug("max effects is %d", max_effects); + + if (max_effects > PID_EFFECTS_MAX) + max_effects = PID_EFFECTS_MAX; + + if (pidff->pool[PID_SIMULTANEOUS_MAX].value) + debug("max simultaneous effects is %d", + pidff->pool[PID_SIMULTANEOUS_MAX].value[0]); + + if (pidff->pool[PID_RAM_POOL_SIZE].value) + debug("device memory size is %d bytes", + pidff->pool[PID_RAM_POOL_SIZE].value[0]); + + if (pidff->pool[PID_DEVICE_MANAGED_POOL].value && + pidff->pool[PID_DEVICE_MANAGED_POOL].value[0] == 0) { + printk(KERN_NOTICE "hid-pidff: " + "device does not support device managed pool\n"); + goto fail; + } + + error = input_ff_create(dev, max_effects); + if (error) + goto fail; + + ff = dev->ff; + ff->private = pidff; + ff->upload = pidff_upload_effect; + ff->erase = pidff_erase_effect; + ff->set_gain = pidff_set_gain; + ff->set_autocenter = pidff_set_autocenter; + ff->playback = pidff_playback; + + printk(KERN_INFO "Force feedback for USB HID PID devices by " + "Anssi Hannula \n"); + + return 0; + + fail: + kfree(pidff); + return error; +} diff --git a/usr/src/dkms_source_tree/hid-plff.c b/usr/src/dkms_source_tree/hid-plff.c new file mode 100644 index 0000000..9eb83cf --- /dev/null +++ b/usr/src/dkms_source_tree/hid-plff.c @@ -0,0 +1,139 @@ +/* + * Force feedback support for PantherLord/GreenAsia based devices + * + * The devices are distributed under various names and the same USB device ID + * can be used in both adapters and actual game controllers. + * + * 0810:0001 "Twin USB Joystick" + * - tested with PantherLord USB/PS2 2in1 Adapter + * - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT) + * + * 0e8f:0003 "GreenAsia Inc. USB Joystick " + * - tested with Köng Gaming gamepad + * + * Copyright (c) 2007 Anssi Hannula + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* #define DEBUG */ + +#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg) + +#include +#include +#include +#include "usbhid.h" + +struct plff_device { + struct hid_report *report; +}; + +static int hid_plff_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct plff_device *plff = data; + int left, right; + + left = effect->u.rumble.strong_magnitude; + right = effect->u.rumble.weak_magnitude; + debug("called with 0x%04x 0x%04x", left, right); + + left = left * 0x7f / 0xffff; + right = right * 0x7f / 0xffff; + + plff->report->field[0]->value[2] = left; + plff->report->field[0]->value[3] = right; + debug("running with 0x%02x 0x%02x", left, right); + usbhid_submit_report(hid, plff->report, USB_DIR_OUT); + + return 0; +} + +int hid_plff_init(struct hid_device *hid) +{ + struct plff_device *plff; + struct hid_report *report; + struct hid_input *hidinput; + struct list_head *report_list = + &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct list_head *report_ptr = report_list; + struct input_dev *dev; + int error; + + /* The device contains one output report per physical device, all + containing 1 field, which contains 4 ff00.0002 usages and 4 16bit + absolute values. + + The input reports also contain a field which contains + 8 ff00.0001 usages and 8 boolean values. Their meaning is + currently unknown. */ + + if (list_empty(report_list)) { + printk(KERN_ERR "hid-plff: no output reports found\n"); + return -ENODEV; + } + + list_for_each_entry(hidinput, &hid->inputs, list) { + + report_ptr = report_ptr->next; + + if (report_ptr == report_list) { + printk(KERN_ERR "hid-plff: required output report is missing\n"); + return -ENODEV; + } + + report = list_entry(report_ptr, struct hid_report, list); + if (report->maxfield < 1) { + printk(KERN_ERR "hid-plff: no fields in the report\n"); + return -ENODEV; + } + + if (report->field[0]->report_count < 4) { + printk(KERN_ERR "hid-plff: not enough values in the field\n"); + return -ENODEV; + } + + plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL); + if (!plff) + return -ENOMEM; + + dev = hidinput->input; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, plff, hid_plff_play); + if (error) { + kfree(plff); + return error; + } + + plff->report = report; + plff->report->field[0]->value[0] = 0x00; + plff->report->field[0]->value[1] = 0x00; + plff->report->field[0]->value[2] = 0x00; + plff->report->field[0]->value[3] = 0x00; + usbhid_submit_report(hid, plff->report, USB_DIR_OUT); + } + + printk(KERN_INFO "hid-plff: Force feedback for PantherLord/GreenAsia " + "devices by Anssi Hannula \n"); + + return 0; +} diff --git a/usr/src/dkms_source_tree/hid-quirks.c b/usr/src/dkms_source_tree/hid-quirks.c new file mode 100644 index 0000000..870247d --- /dev/null +++ b/usr/src/dkms_source_tree/hid-quirks.c @@ -0,0 +1,1224 @@ +/* + * USB HID quirks support for Linux + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2006-2007 Jiri Kosina + * 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 + +#define USB_VENDOR_ID_A4TECH 0x09da +#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006 +#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a + +#define USB_VENDOR_ID_AASHIMA 0x06d6 +#define USB_DEVICE_ID_AASHIMA_GAMEPAD 0x0025 +#define USB_DEVICE_ID_AASHIMA_PREDATOR 0x0026 + +#define USB_VENDOR_ID_ACECAD 0x0460 +#define USB_DEVICE_ID_ACECAD_FLAIR 0x0004 +#define USB_DEVICE_ID_ACECAD_302 0x0008 + +#define USB_VENDOR_ID_ADS_TECH 0x06e1 +#define USB_DEVICE_ID_ADS_TECH_RADIO_SI470X 0xa155 + +#define USB_VENDOR_ID_AFATECH 0x15a4 +#define USB_DEVICE_ID_AFATECH_AF9016 0x9016 + +#define USB_VENDOR_ID_AIPTEK 0x08ca +#define USB_DEVICE_ID_AIPTEK_01 0x0001 +#define USB_DEVICE_ID_AIPTEK_10 0x0010 +#define USB_DEVICE_ID_AIPTEK_20 0x0020 +#define USB_DEVICE_ID_AIPTEK_21 0x0021 +#define USB_DEVICE_ID_AIPTEK_22 0x0022 +#define USB_DEVICE_ID_AIPTEK_23 0x0023 +#define USB_DEVICE_ID_AIPTEK_24 0x0024 + +#define USB_VENDOR_ID_AIRCABLE 0x16CA +#define USB_DEVICE_ID_AIRCABLE1 0x1502 + +#define USB_VENDOR_ID_ALCOR 0x058f +#define USB_DEVICE_ID_ALCOR_USBRS232 0x9720 + +#define USB_VENDOR_ID_ALPS 0x0433 +#define USB_DEVICE_ID_IBM_GAMEPAD 0x1101 + +#define USB_VENDOR_ID_APPLE 0x05ac +#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 +#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e +#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f +#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214 +#define USB_DEVICE_ID_APPLE_GEYSER_ISO 0x0215 +#define USB_DEVICE_ID_APPLE_GEYSER_JIS 0x0216 +#define USB_DEVICE_ID_APPLE_GEYSER3_ANSI 0x0217 +#define USB_DEVICE_ID_APPLE_GEYSER3_ISO 0x0218 +#define USB_DEVICE_ID_APPLE_GEYSER3_JIS 0x0219 +#define USB_DEVICE_ID_APPLE_GEYSER4_ANSI 0x021a +#define USB_DEVICE_ID_APPLE_GEYSER4_ISO 0x021b +#define USB_DEVICE_ID_APPLE_GEYSER4_JIS 0x021c +#define USB_DEVICE_ID_APPLE_ALU_ANSI 0x0220 +#define USB_DEVICE_ID_APPLE_ALU_ISO 0x0221 +#define USB_DEVICE_ID_APPLE_ALU_JIS 0x0222 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI 0x0223 +#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO 0x0224 +#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS 0x0225 +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI 0x0229 +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO 0x022a +#define USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS 0x022b +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI 0x022c +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO 0x022d +#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS 0x022e +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231 +#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI 0x0236 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO 0x0237 +#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS 0x0238 +#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a +#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b +#define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242 + +#define USB_VENDOR_ID_ASUS 0x0b05 +#define USB_DEVICE_ID_ASUS_LCM 0x1726 + +#define USB_VENDOR_ID_ATEN 0x0557 +#define USB_DEVICE_ID_ATEN_UC100KM 0x2004 +#define USB_DEVICE_ID_ATEN_CS124U 0x2202 +#define USB_DEVICE_ID_ATEN_2PORTKVM 0x2204 +#define USB_DEVICE_ID_ATEN_4PORTKVM 0x2205 +#define USB_DEVICE_ID_ATEN_4PORTKVMC 0x2208 + +#define USB_VENDOR_ID_BELKIN 0x050d +#define USB_DEVICE_ID_FLIP_KVM 0x3201 + +#define USB_VENDOR_ID_BERKSHIRE 0x0c98 +#define USB_DEVICE_ID_BERKSHIRE_PCWD 0x1140 + +#define USB_VENDOR_ID_CHERRY 0x046a +#define USB_DEVICE_ID_CHERRY_CYMOTION 0x0023 + +#define USB_VENDOR_ID_CHIC 0x05fe +#define USB_DEVICE_ID_CHIC_GAMEPAD 0x0014 + +#define USB_VENDOR_ID_CIDC 0x1677 + +#define USB_VENDOR_ID_CMEDIA 0x0d8c +#define USB_DEVICE_ID_CM109 0x000e + +#define USB_VENDOR_ID_CODEMERCS 0x07c0 +#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500 +#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff + +#define USB_VENDOR_ID_CYGNAL 0x10c4 +#define USB_DEVICE_ID_CYGNAL_RADIO_SI470X 0x818a + +#define USB_VENDOR_ID_CYPRESS 0x04b4 +#define USB_DEVICE_ID_CYPRESS_MOUSE 0x0001 +#define USB_DEVICE_ID_CYPRESS_HIDCOM 0x5500 +#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE 0x7417 +#define USB_DEVICE_ID_CYPRESS_BARCODE_1 0xde61 +#define USB_DEVICE_ID_CYPRESS_BARCODE_2 0xde64 + +#define USB_VENDOR_ID_DELL 0x413c +#define USB_DEVICE_ID_DELL_W7658 0x2005 + +#define USB_VENDOR_ID_DELORME 0x1163 +#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100 +#define USB_DEVICE_ID_DELORME_EM_LT20 0x0200 + +#define USB_VENDOR_ID_DMI 0x0c0b +#define USB_DEVICE_ID_DMI_ENC 0x5fab + +#define USB_VENDOR_ID_ELO 0x04E7 +#define USB_DEVICE_ID_ELO_TS2700 0x0020 + +#define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f +#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100 + +#define USB_VENDOR_ID_EZKEY 0x0518 +#define USB_DEVICE_ID_BTC_8193 0x0002 + +#define USB_VENDOR_ID_GAMERON 0x0810 +#define USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR 0x0001 + +#define USB_VENDOR_ID_GENERAL_TOUCH 0x0dfc + +#define USB_VENDOR_ID_GLAB 0x06c2 +#define USB_DEVICE_ID_4_PHIDGETSERVO_30 0x0038 +#define USB_DEVICE_ID_1_PHIDGETSERVO_30 0x0039 +#define USB_DEVICE_ID_0_0_4_IF_KIT 0x0040 +#define USB_DEVICE_ID_0_16_16_IF_KIT 0x0044 +#define USB_DEVICE_ID_8_8_8_IF_KIT 0x0045 +#define USB_DEVICE_ID_0_8_7_IF_KIT 0x0051 +#define USB_DEVICE_ID_0_8_8_IF_KIT 0x0053 +#define USB_DEVICE_ID_PHIDGET_MOTORCONTROL 0x0058 + +#define USB_VENDOR_ID_GOTOP 0x08f2 +#define USB_DEVICE_ID_SUPER_Q2 0x007f +#define USB_DEVICE_ID_GOGOPEN 0x00ce +#define USB_DEVICE_ID_PENPOWER 0x00f4 + +#define USB_VENDOR_ID_GRETAGMACBETH 0x0971 +#define USB_DEVICE_ID_GRETAGMACBETH_HUEY 0x2005 + +#define USB_VENDOR_ID_GRIFFIN 0x077d +#define USB_DEVICE_ID_POWERMATE 0x0410 +#define USB_DEVICE_ID_SOUNDKNOB 0x04AA + +#define USB_VENDOR_ID_GTCO 0x078c +#define USB_DEVICE_ID_GTCO_90 0x0090 +#define USB_DEVICE_ID_GTCO_100 0x0100 +#define USB_DEVICE_ID_GTCO_101 0x0101 +#define USB_DEVICE_ID_GTCO_103 0x0103 +#define USB_DEVICE_ID_GTCO_104 0x0104 +#define USB_DEVICE_ID_GTCO_105 0x0105 +#define USB_DEVICE_ID_GTCO_106 0x0106 +#define USB_DEVICE_ID_GTCO_107 0x0107 +#define USB_DEVICE_ID_GTCO_108 0x0108 +#define USB_DEVICE_ID_GTCO_200 0x0200 +#define USB_DEVICE_ID_GTCO_201 0x0201 +#define USB_DEVICE_ID_GTCO_202 0x0202 +#define USB_DEVICE_ID_GTCO_203 0x0203 +#define USB_DEVICE_ID_GTCO_204 0x0204 +#define USB_DEVICE_ID_GTCO_205 0x0205 +#define USB_DEVICE_ID_GTCO_206 0x0206 +#define USB_DEVICE_ID_GTCO_207 0x0207 +#define USB_DEVICE_ID_GTCO_300 0x0300 +#define USB_DEVICE_ID_GTCO_301 0x0301 +#define USB_DEVICE_ID_GTCO_302 0x0302 +#define USB_DEVICE_ID_GTCO_303 0x0303 +#define USB_DEVICE_ID_GTCO_304 0x0304 +#define USB_DEVICE_ID_GTCO_305 0x0305 +#define USB_DEVICE_ID_GTCO_306 0x0306 +#define USB_DEVICE_ID_GTCO_307 0x0307 +#define USB_DEVICE_ID_GTCO_308 0x0308 +#define USB_DEVICE_ID_GTCO_309 0x0309 +#define USB_DEVICE_ID_GTCO_400 0x0400 +#define USB_DEVICE_ID_GTCO_401 0x0401 +#define USB_DEVICE_ID_GTCO_402 0x0402 +#define USB_DEVICE_ID_GTCO_403 0x0403 +#define USB_DEVICE_ID_GTCO_404 0x0404 +#define USB_DEVICE_ID_GTCO_405 0x0405 +#define USB_DEVICE_ID_GTCO_500 0x0500 +#define USB_DEVICE_ID_GTCO_501 0x0501 +#define USB_DEVICE_ID_GTCO_502 0x0502 +#define USB_DEVICE_ID_GTCO_503 0x0503 +#define USB_DEVICE_ID_GTCO_504 0x0504 +#define USB_DEVICE_ID_GTCO_1000 0x1000 +#define USB_DEVICE_ID_GTCO_1001 0x1001 +#define USB_DEVICE_ID_GTCO_1002 0x1002 +#define USB_DEVICE_ID_GTCO_1003 0x1003 +#define USB_DEVICE_ID_GTCO_1004 0x1004 +#define USB_DEVICE_ID_GTCO_1005 0x1005 +#define USB_DEVICE_ID_GTCO_1006 0x1006 +#define USB_DEVICE_ID_GTCO_1007 0x1007 +#define USB_VENDOR_ID_HAPP 0x078b +#define USB_DEVICE_ID_UGCI_DRIVING 0x0010 +#define USB_DEVICE_ID_UGCI_FLYING 0x0020 +#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030 + +#define USB_VENDOR_ID_IMATION 0x0718 +#define USB_DEVICE_ID_DISC_STAKKA 0xd000 + +#define USB_VENDOR_ID_KBGEAR 0x084e +#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 + +#define USB_VENDOR_ID_LD 0x0f11 +#define USB_DEVICE_ID_LD_CASSY 0x1000 +#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 +#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020 +#define USB_DEVICE_ID_LD_JWM 0x1080 +#define USB_DEVICE_ID_LD_DMMP 0x1081 +#define USB_DEVICE_ID_LD_UMIP 0x1090 +#define USB_DEVICE_ID_LD_XRAY1 0x1100 +#define USB_DEVICE_ID_LD_XRAY2 0x1101 +#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200 +#define USB_DEVICE_ID_LD_COM3LAB 0x2000 +#define USB_DEVICE_ID_LD_TELEPORT 0x2010 +#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020 +#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030 +#define USB_DEVICE_ID_LD_MACHINETEST 0x2040 + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 +#define USB_DEVICE_ID_LOGITECH_HARMONY 0xc110 +#define USB_DEVICE_ID_LOGITECH_HARMONY_2 0xc111 +#define USB_DEVICE_ID_LOGITECH_HARMONY_3 0xc112 +#define USB_DEVICE_ID_LOGITECH_HARMONY_4 0xc113 +#define USB_DEVICE_ID_LOGITECH_HARMONY_5 0xc114 +#define USB_DEVICE_ID_LOGITECH_HARMONY_6 0xc115 +#define USB_DEVICE_ID_LOGITECH_HARMONY_7 0xc116 +#define USB_DEVICE_ID_LOGITECH_HARMONY_8 0xc117 +#define USB_DEVICE_ID_LOGITECH_HARMONY_9 0xc118 +#define USB_DEVICE_ID_LOGITECH_HARMONY_10 0xc119 +#define USB_DEVICE_ID_LOGITECH_HARMONY_11 0xc11a +#define USB_DEVICE_ID_LOGITECH_HARMONY_12 0xc11b +#define USB_DEVICE_ID_LOGITECH_HARMONY_13 0xc11c +#define USB_DEVICE_ID_LOGITECH_HARMONY_14 0xc11d +#define USB_DEVICE_ID_LOGITECH_HARMONY_15 0xc11e +#define USB_DEVICE_ID_LOGITECH_HARMONY_16 0xc11f +#define USB_DEVICE_ID_LOGITECH_HARMONY_17 0xc120 +#define USB_DEVICE_ID_LOGITECH_HARMONY_18 0xc121 +#define USB_DEVICE_ID_LOGITECH_HARMONY_19 0xc122 +#define USB_DEVICE_ID_LOGITECH_HARMONY_20 0xc123 +#define USB_DEVICE_ID_LOGITECH_HARMONY_21 0xc124 +#define USB_DEVICE_ID_LOGITECH_HARMONY_22 0xc125 +#define USB_DEVICE_ID_LOGITECH_HARMONY_23 0xc126 +#define USB_DEVICE_ID_LOGITECH_HARMONY_24 0xc127 +#define USB_DEVICE_ID_LOGITECH_HARMONY_25 0xc128 +#define USB_DEVICE_ID_LOGITECH_HARMONY_26 0xc129 +#define USB_DEVICE_ID_LOGITECH_HARMONY_27 0xc12a +#define USB_DEVICE_ID_LOGITECH_HARMONY_28 0xc12b +#define USB_DEVICE_ID_LOGITECH_HARMONY_29 0xc12c +#define USB_DEVICE_ID_LOGITECH_HARMONY_30 0xc12d +#define USB_DEVICE_ID_LOGITECH_HARMONY_31 0xc12e +#define USB_DEVICE_ID_LOGITECH_HARMONY_32 0xc12f +#define USB_DEVICE_ID_LOGITECH_HARMONY_33 0xc130 +#define USB_DEVICE_ID_LOGITECH_HARMONY_34 0xc131 +#define USB_DEVICE_ID_LOGITECH_HARMONY_35 0xc132 +#define USB_DEVICE_ID_LOGITECH_HARMONY_36 0xc133 +#define USB_DEVICE_ID_LOGITECH_HARMONY_37 0xc134 +#define USB_DEVICE_ID_LOGITECH_HARMONY_38 0xc135 +#define USB_DEVICE_ID_LOGITECH_HARMONY_39 0xc136 +#define USB_DEVICE_ID_LOGITECH_HARMONY_40 0xc137 +#define USB_DEVICE_ID_LOGITECH_HARMONY_41 0xc138 +#define USB_DEVICE_ID_LOGITECH_HARMONY_42 0xc139 +#define USB_DEVICE_ID_LOGITECH_HARMONY_43 0xc13a +#define USB_DEVICE_ID_LOGITECH_HARMONY_44 0xc13b +#define USB_DEVICE_ID_LOGITECH_HARMONY_45 0xc13c +#define USB_DEVICE_ID_LOGITECH_HARMONY_46 0xc13d +#define USB_DEVICE_ID_LOGITECH_HARMONY_47 0xc13e +#define USB_DEVICE_ID_LOGITECH_HARMONY_48 0xc13f +#define USB_DEVICE_ID_LOGITECH_HARMONY_49 0xc140 +#define USB_DEVICE_ID_LOGITECH_HARMONY_50 0xc141 +#define USB_DEVICE_ID_LOGITECH_HARMONY_51 0xc142 +#define USB_DEVICE_ID_LOGITECH_HARMONY_52 0xc143 +#define USB_DEVICE_ID_LOGITECH_HARMONY_53 0xc144 +#define USB_DEVICE_ID_LOGITECH_HARMONY_54 0xc145 +#define USB_DEVICE_ID_LOGITECH_HARMONY_55 0xc146 +#define USB_DEVICE_ID_LOGITECH_HARMONY_56 0xc147 +#define USB_DEVICE_ID_LOGITECH_HARMONY_57 0xc148 +#define USB_DEVICE_ID_LOGITECH_HARMONY_58 0xc149 +#define USB_DEVICE_ID_LOGITECH_HARMONY_59 0xc14a +#define USB_DEVICE_ID_LOGITECH_HARMONY_60 0xc14b +#define USB_DEVICE_ID_LOGITECH_HARMONY_61 0xc14c +#define USB_DEVICE_ID_LOGITECH_HARMONY_62 0xc14d +#define USB_DEVICE_ID_LOGITECH_HARMONY_63 0xc14e +#define USB_DEVICE_ID_LOGITECH_HARMONY_64 0xc14f +#define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215 +#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294 +#define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a +#define USB_DEVICE_ID_LOGITECH_KBD 0xc311 +#define USB_DEVICE_ID_S510_RECEIVER 0xc50c +#define USB_DEVICE_ID_S510_RECEIVER_2 0xc517 +#define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512 +#define USB_DEVICE_ID_MX3000_RECEIVER 0xc513 +#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704 +#define USB_DEVICE_ID_DINOVO_EDGE 0xc714 +#define USB_DEVICE_ID_DINOVO_MINI 0xc71f + +#define USB_VENDOR_ID_MCC 0x09db +#define USB_DEVICE_ID_MCC_PMD1024LS 0x0076 +#define USB_DEVICE_ID_MCC_PMD1208LS 0x007a + +#define USB_VENDOR_ID_MGE 0x0463 +#define USB_DEVICE_ID_MGE_UPS 0xffff +#define USB_DEVICE_ID_MGE_UPS1 0x0001 + +#define USB_VENDOR_ID_MICROCHIP 0x04d8 +#define USB_DEVICE_ID_PICKIT1 0x0032 +#define USB_DEVICE_ID_PICKIT2 0x0033 + +#define USB_VENDOR_ID_MICROSOFT 0x045e +#define USB_DEVICE_ID_SIDEWINDER_GV 0x003b +#define USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0 0x009d +#define USB_DEVICE_ID_DESKTOP_RECV_1028 0x00f9 +#define USB_DEVICE_ID_MS_NE4K 0x00db +#define USB_DEVICE_ID_MS_LK6K 0x00f9 + +#define USB_VENDOR_ID_MONTEREY 0x0566 +#define USB_DEVICE_ID_GENIUS_KB29E 0x3004 + +#define USB_VENDOR_ID_NCR 0x0404 +#define USB_DEVICE_ID_NCR_FIRST 0x0300 +#define USB_DEVICE_ID_NCR_LAST 0x03ff + +#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400 +#define USB_DEVICE_ID_N_S_HARMONY 0xc359 + +#define USB_VENDOR_ID_NATSU 0x08b7 +#define USB_DEVICE_ID_NATSU_GAMEPAD 0x0001 + +#define USB_VENDOR_ID_NEC 0x073e +#define USB_DEVICE_ID_NEC_USB_GAME_PAD 0x0301 + +#define USB_VENDOR_ID_ONTRAK 0x0a07 +#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 + +#define USB_VENDOR_ID_PANJIT 0x134c + +#define USB_VENDOR_ID_PANTHERLORD 0x0810 +#define USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK 0x0001 + +#define USB_VENDOR_ID_PETALYNX 0x18b1 +#define USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE 0x0037 + +#define USB_VENDOR_ID_PLAYDOTCOM 0x0b43 +#define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII 0x0003 + +#define USB_VENDOR_ID_SAITEK 0x06a3 +#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 + +#define USB_VENDOR_ID_SAMSUNG 0x0419 +#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001 + +#define USB_VENDOR_ID_SONY 0x054c +#define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 + +#define USB_VENDOR_ID_SOUNDGRAPH 0x15c2 +#define USB_DEVICE_ID_SOUNDGRAPH_IMON_LCD 0x0038 + +#define USB_VENDOR_ID_SUN 0x0430 +#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab + +#define USB_VENDOR_ID_SUNPLUS 0x04fc +#define USB_DEVICE_ID_SUNPLUS_WDESKTOP 0x05d8 + +#define USB_VENDOR_ID_TOPMAX 0x0663 +#define USB_DEVICE_ID_TOPMAX_COBRAPAD 0x0103 + +#define USB_VENDOR_ID_TURBOX 0x062a +#define USB_DEVICE_ID_TURBOX_KEYBOARD 0x0201 + +#define USB_VENDOR_ID_VERNIER 0x08f7 +#define USB_DEVICE_ID_VERNIER_LABPRO 0x0001 +#define USB_DEVICE_ID_VERNIER_GOTEMP 0x0002 +#define USB_DEVICE_ID_VERNIER_SKIP 0x0003 +#define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004 +#define USB_DEVICE_ID_VERNIER_LCSPEC 0x0006 + +#define USB_VENDOR_ID_WACOM 0x056a + +#define USB_VENDOR_ID_WISEGROUP 0x0925 +#define USB_DEVICE_ID_1_PHIDGETSERVO_20 0x8101 +#define USB_DEVICE_ID_4_PHIDGETSERVO_20 0x8104 +#define USB_DEVICE_ID_8_8_4_IF_KIT 0x8201 +#define USB_DEVICE_ID_QUAD_USB_JOYPAD 0x8800 +#define USB_DEVICE_ID_DUAL_USB_JOYPAD 0x8866 + +#define USB_VENDOR_ID_WISEGROUP_LTD 0x6677 +#define USB_DEVICE_ID_SMARTJOY_DUAL_PLUS 0x8802 + +#define USB_VENDOR_ID_YEALINK 0x6993 +#define USB_DEVICE_ID_YEALINK_P1K_P4K_B2K 0xb001 + +#define USB_VENDOR_ID_KYE 0x0458 +#define USB_DEVICE_ID_KYE_GPEN_560 0x5003 + +/* + * Alphabetically sorted blacklist by quirk type. + */ + +static const struct hid_blacklist { + __u16 idVendor; + __u16 idProduct; + __u32 quirks; +} hid_blacklist[] = { + + { USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU, HID_QUIRK_2WHEEL_MOUSE_HACK_7 }, + { USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D, HID_QUIRK_2WHEEL_MOUSE_HACK_B8 }, + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE, HID_QUIRK_2WHEEL_MOUSE_HACK_5 }, + + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER, HID_QUIRK_BAD_RELATIVE_KEYS }, + + { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_NATSU, USB_DEVICE_ID_NATSU_GAMEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD, HID_QUIRK_BADPAD }, + { USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD }, + + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP, HID_QUIRK_DUPLICATE_USAGES }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE, HID_QUIRK_DUPLICATE_USAGES }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI, HID_QUIRK_DUPLICATE_USAGES }, + + { USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016, HID_QUIRK_FULLSPEED_INTERVAL }, + + { USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM, HID_QUIRK_HIDDEV }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4, HID_QUIRK_HIDDEV | HID_QUIRK_IGNORE_HIDINPUT }, + { USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE, HID_QUIRK_HIDDEV | HID_QUIRK_IGNORE_HIDINPUT }, + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV, HID_QUIRK_HIDINPUT }, + + { USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193, HID_QUIRK_HWHEEL_WHEEL_INVERT }, + + { USB_VENDOR_ID_ADS_TECH, USB_DEVICE_ID_ADS_TECH_RADIO_SI470X, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_01, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_10, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_21, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_22, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_23, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_24, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIRCABLE, USB_DEVICE_ID_AIRCABLE1, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ALCOR, USB_DEVICE_ID_ALCOR_USBRS232, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS_LCM, HID_QUIRK_IGNORE}, + { USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CIDC, 0x0103, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM109, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_ULTRAMOUSE, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GENERAL_TOUCH, 0x0001, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GENERAL_TOUCH, 0x0002, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GENERAL_TOUCH, 0x0003, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GENERAL_TOUCH, 0x0004, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_4_PHIDGETSERVO_30, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_1_PHIDGETSERVO_30, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_0_4_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_16_16_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_8_8_8_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_7_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_8_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GLAB, USB_DEVICE_ID_PHIDGET_MOTORCONTROL, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_SUPER_Q2, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_GOGOPEN, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_PENPOWER, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GRETAGMACBETH, USB_DEVICE_ID_GRETAGMACBETH_HUEY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_POWERMATE, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_SOUNDKNOB, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_90, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_100, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_101, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_103, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_104, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_105, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_106, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_107, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_108, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_200, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_201, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_202, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_203, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_204, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_205, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_206, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_207, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_300, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_301, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_302, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_303, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_304, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_305, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_306, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_307, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_308, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_309, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_400, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_401, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_402, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_403, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_404, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_405, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_500, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_501, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_502, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_503, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_504, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1000, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1001, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1002, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1003, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1004, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1005, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1006, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1007, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY1, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1024LS, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1208LS, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 30, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 108, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 118, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 500, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_PANJIT, 0x0001, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_PANJIT, 0x0002, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_PANJIT, 0x0003, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_PANJIT, 0x0004, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_SOUNDGRAPH, USB_DEVICE_ID_SOUNDGRAPH_IMON_LCD, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LABPRO, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LCSPEC, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_4_PHIDGETSERVO_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_1_PHIDGETSERVO_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_8_8_4_IF_KIT, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K, HID_QUIRK_IGNORE }, + + { USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_FLAIR, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_302, HID_QUIRK_IGNORE }, + + { USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT1, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT2, HID_QUIRK_IGNORE }, + + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD, HID_QUIRK_LOGITECH_IGNORE_DOUBLED_WHEEL | HID_QUIRK_LOGITECH_EXPANDED_KEYMAP }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500, HID_QUIRK_LOGITECH_IGNORE_DOUBLED_WHEEL | HID_QUIRK_LOGITECH_EXPANDED_KEYMAP }, + + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K, HID_QUIRK_MICROSOFT_KEYS }, + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K, HID_QUIRK_MICROSOFT_KEYS }, + + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE, HID_QUIRK_MIGHTYMOUSE | HID_QUIRK_INVERT_HWHEEL }, + + { USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK, HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS }, + { USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII, HID_QUIRK_MULTI_INPUT }, + + { USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER, HID_QUIRK_SONY_PS3_CONTROLLER | HID_QUIRK_HIDDEV }, + + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET }, + { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS }, + { USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, + + { USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, + + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_APPLE_ISO_KEYBOARD}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_APPLE_ISO_KEYBOARD}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_APPLE_ISO_KEYBOARD}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI, HID_QUIRK_APPLE_HAS_FN }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS, HID_QUIRK_APPLE_HAS_FN }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD | HID_QUIRK_IGNORE_MOUSE}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + + { USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_W7658, HID_QUIRK_RESET_LEDS }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KBD, HID_QUIRK_RESET_LEDS }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_2, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_3, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_4, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_5, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_6, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_7, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_8, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_9, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_10, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_11, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_12, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_13, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_14, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_15, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_16, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_17, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_18, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_19, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_21, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_22, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_23, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_24, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_25, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_26, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_27, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_28, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_29, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_30, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_31, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_32, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_33, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_34, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_35, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_36, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_37, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_38, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_39, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_40, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_41, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_42, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_43, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_44, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_45, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_46, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_47, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_48, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_49, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_50, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_51, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_52, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_53, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_54, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_55, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_56, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_57, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_58, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_59, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_60, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_61, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_62, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_63, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_64, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR, USB_DEVICE_ID_N_S_HARMONY, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560, HID_QUIRK_IGNORE }, + + { 0, 0 } +}; + +/* Quirks for devices which require report descriptor fixup go here */ +static const struct hid_rdesc_blacklist { + __u16 idVendor; + __u16 idProduct; + __u32 quirks; +} hid_rdesc_blacklist[] = { + + { USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION, HID_QUIRK_RDESC_CYMOTION }, + + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER, HID_QUIRK_RDESC_LOGITECH }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER, HID_QUIRK_RDESC_LOGITECH }, + { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2, HID_QUIRK_RDESC_LOGITECH }, + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_DESKTOP_RECV_1028, HID_QUIRK_RDESC_MICROSOFT_RECV_1028 }, + + { USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E, HID_QUIRK_RDESC_BUTTON_CONSUMER }, + + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS, HID_QUIRK_RDESC_MACBOOK_JIS }, + + { USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE, HID_QUIRK_RDESC_PETALYNX }, + + { USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE, HID_QUIRK_RDESC_SAMSUNG_REMOTE }, + + { USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP, HID_QUIRK_RDESC_SUNPLUS_WDESKTOP }, + + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1, HID_QUIRK_RDESC_SWAPPED_MIN_MAX }, + { USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2, HID_QUIRK_RDESC_SWAPPED_MIN_MAX }, + + { 0, 0 } +}; + +/* Dynamic HID quirks list - specified at runtime */ +struct quirks_list_struct { + struct hid_blacklist hid_bl_item; + struct list_head node; +}; + +static LIST_HEAD(dquirks_list); +static DECLARE_RWSEM(dquirks_rwsem); + +/* Runtime ("dynamic") quirks manipulation functions */ + +/** + * usbhid_exists_dquirk: find any dynamic quirks for a USB HID device + * @idVendor: the 16-bit USB vendor ID, in native byteorder + * @idProduct: the 16-bit USB product ID, in native byteorder + * + * Description: + * Scans dquirks_list for a matching dynamic quirk and returns + * the pointer to the relevant struct hid_blacklist if found. + * Must be called with a read lock held on dquirks_rwsem. + * + * Returns: NULL if no quirk found, struct hid_blacklist * if found. + */ +static struct hid_blacklist *usbhid_exists_dquirk(const u16 idVendor, + const u16 idProduct) +{ + struct quirks_list_struct *q; + struct hid_blacklist *bl_entry = NULL; + + list_for_each_entry(q, &dquirks_list, node) { + if (q->hid_bl_item.idVendor == idVendor && + q->hid_bl_item.idProduct == idProduct) { + bl_entry = &q->hid_bl_item; + break; + } + } + + if (bl_entry != NULL) + dbg_hid("Found dynamic quirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n", + bl_entry->quirks, bl_entry->idVendor, + bl_entry->idProduct); + + return bl_entry; +} + + +/** + * usbhid_modify_dquirk: add/replace a HID quirk + * @idVendor: the 16-bit USB vendor ID, in native byteorder + * @idProduct: the 16-bit USB product ID, in native byteorder + * @quirks: the u32 quirks value to add/replace + * + * Description: + * If an dynamic quirk exists in memory for this (idVendor, + * idProduct) pair, replace its quirks value with what was + * provided. Otherwise, add the quirk to the dynamic quirks list. + * + * Returns: 0 OK, -error on failure. + */ +static int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct, + const u32 quirks) +{ + struct quirks_list_struct *q_new, *q; + int list_edited = 0; + + if (!idVendor) { + dbg_hid("Cannot add a quirk with idVendor = 0\n"); + return -EINVAL; + } + + q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL); + if (!q_new) { + dbg_hid("Could not allocate quirks_list_struct\n"); + return -ENOMEM; + } + + q_new->hid_bl_item.idVendor = idVendor; + q_new->hid_bl_item.idProduct = idProduct; + q_new->hid_bl_item.quirks = quirks; + + down_write(&dquirks_rwsem); + + list_for_each_entry(q, &dquirks_list, node) { + + if (q->hid_bl_item.idVendor == idVendor && + q->hid_bl_item.idProduct == idProduct) { + + list_replace(&q->node, &q_new->node); + kfree(q); + list_edited = 1; + break; + + } + + } + + if (!list_edited) + list_add_tail(&q_new->node, &dquirks_list); + + up_write(&dquirks_rwsem); + + return 0; +} + +/** + * usbhid_remove_all_dquirks: remove all runtime HID quirks from memory + * + * Description: + * Free all memory associated with dynamic quirks - called before + * module unload. + * + */ +static void usbhid_remove_all_dquirks(void) +{ + struct quirks_list_struct *q, *temp; + + down_write(&dquirks_rwsem); + list_for_each_entry_safe(q, temp, &dquirks_list, node) { + list_del(&q->node); + kfree(q); + } + up_write(&dquirks_rwsem); + +} + +/** + * usbhid_quirks_init: apply USB HID quirks specified at module load time + */ +int usbhid_quirks_init(char **quirks_param) +{ + u16 idVendor, idProduct; + u32 quirks; + int n = 0, m; + + for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) { + + m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x", + &idVendor, &idProduct, &quirks); + + if (m != 3 || + usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) { + printk(KERN_WARNING + "Could not parse HID quirk module param %s\n", + quirks_param[n]); + } + } + + return 0; +} + +/** + * usbhid_quirks_exit: release memory associated with dynamic_quirks + * + * Description: + * Release all memory associated with dynamic quirks. Called upon + * module unload. + * + * Returns: nothing + */ +void usbhid_quirks_exit(void) +{ + usbhid_remove_all_dquirks(); +} + +/** + * usbhid_exists_squirk: return any static quirks for a USB HID device + * @idVendor: the 16-bit USB vendor ID, in native byteorder + * @idProduct: the 16-bit USB product ID, in native byteorder + * + * Description: + * Given a USB vendor ID and product ID, return a pointer to + * the hid_blacklist entry associated with that device. + * + * Returns: pointer if quirk found, or NULL if no quirks found. + */ +static const struct hid_blacklist *usbhid_exists_squirk(const u16 idVendor, + const u16 idProduct) +{ + const struct hid_blacklist *bl_entry = NULL; + int n = 0; + + for (; hid_blacklist[n].idVendor; n++) + if (hid_blacklist[n].idVendor == idVendor && + hid_blacklist[n].idProduct == idProduct) + bl_entry = &hid_blacklist[n]; + + if (bl_entry != NULL) + dbg_hid("Found squirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n", + bl_entry->quirks, bl_entry->idVendor, + bl_entry->idProduct); + return bl_entry; +} + +/** + * usbhid_lookup_quirk: return any quirks associated with a USB HID device + * @idVendor: the 16-bit USB vendor ID, in native byteorder + * @idProduct: the 16-bit USB product ID, in native byteorder + * + * Description: + * Given a USB vendor ID and product ID, return any quirks associated + * with that device. + * + * Returns: a u32 quirks value. + */ +u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct) +{ + u32 quirks = 0; + const struct hid_blacklist *bl_entry = NULL; + + /* Ignore all Wacom devices */ + if (idVendor == USB_VENDOR_ID_WACOM) + return HID_QUIRK_IGNORE; + + /* ignore all Code Mercenaries IOWarrior devices */ + if (idVendor == USB_VENDOR_ID_CODEMERCS) + if (idProduct >= USB_DEVICE_ID_CODEMERCS_IOW_FIRST && + idProduct <= USB_DEVICE_ID_CODEMERCS_IOW_LAST) + return HID_QUIRK_IGNORE; + + /* NCR devices must not be queried for reports */ + if (idVendor == USB_VENDOR_ID_NCR && + idProduct >= USB_DEVICE_ID_NCR_FIRST && + idProduct <= USB_DEVICE_ID_NCR_LAST) + return HID_QUIRK_NOGET; + + down_read(&dquirks_rwsem); + bl_entry = usbhid_exists_dquirk(idVendor, idProduct); + if (!bl_entry) + bl_entry = usbhid_exists_squirk(idVendor, idProduct); + if (bl_entry) + quirks = bl_entry->quirks; + up_read(&dquirks_rwsem); + + return quirks; +} + +EXPORT_SYMBOL_GPL(usbhid_lookup_quirk); + +/* + * Cherry Cymotion keyboard have an invalid HID report descriptor, + * that needs fixing before we can parse it. + */ +static void usbhid_fixup_cymotion_descriptor(char *rdesc, int rsize) +{ + if (rsize >= 17 && rdesc[11] == 0x3c && rdesc[12] == 0x02) { + printk(KERN_INFO "Fixing up Cherry Cymotion report descriptor\n"); + rdesc[11] = rdesc[16] = 0xff; + rdesc[12] = rdesc[17] = 0x03; + } +} + + +/* + * Certain Logitech keyboards send in report #3 keys which are far + * above the logical maximum described in descriptor. This extends + * the original value of 0x28c of logical maximum to 0x104d + */ +static void usbhid_fixup_logitech_descriptor(unsigned char *rdesc, int rsize) +{ + if (rsize >= 90 && rdesc[83] == 0x26 + && rdesc[84] == 0x8c + && rdesc[85] == 0x02) { + printk(KERN_INFO "Fixing up Logitech keyboard report descriptor\n"); + rdesc[84] = rdesc[89] = 0x4d; + rdesc[85] = rdesc[90] = 0x10; + } +} + +static void usbhid_fixup_sunplus_wdesktop(unsigned char *rdesc, int rsize) +{ + if (rsize >= 107 && rdesc[104] == 0x26 + && rdesc[105] == 0x80 + && rdesc[106] == 0x03) { + printk(KERN_INFO "Fixing up Sunplus Wireless Desktop report descriptor\n"); + rdesc[105] = rdesc[110] = 0x03; + rdesc[106] = rdesc[111] = 0x21; + } +} + +/* + * Samsung IrDA remote controller (reports as Cypress USB Mouse). + * + * Vendor specific report #4 has a size of 48 bit, + * and therefore is not accepted when inspecting the descriptors. + * As a workaround we reinterpret the report as: + * Variable type, count 6, size 8 bit, log. maximum 255 + * The burden to reconstruct the data is moved into user space. + */ +static void usbhid_fixup_samsung_irda_descriptor(unsigned char *rdesc, + int rsize) +{ + if (rsize >= 182 && rdesc[175] == 0x25 + && rdesc[176] == 0x40 + && rdesc[177] == 0x75 + && rdesc[178] == 0x30 + && rdesc[179] == 0x95 + && rdesc[180] == 0x01 + && rdesc[182] == 0x40) { + printk(KERN_INFO "Fixing up Samsung IrDA report descriptor\n"); + rdesc[176] = 0xff; + rdesc[178] = 0x08; + rdesc[180] = 0x06; + rdesc[182] = 0x42; + } +} + +/* Petalynx Maxter Remote has maximum for consumer page set too low */ +static void usbhid_fixup_petalynx_descriptor(unsigned char *rdesc, int rsize) +{ + if (rsize >= 60 && rdesc[39] == 0x2a + && rdesc[40] == 0xf5 + && rdesc[41] == 0x00 + && rdesc[59] == 0x26 + && rdesc[60] == 0xf9 + && rdesc[61] == 0x00) { + printk(KERN_INFO "Fixing up Petalynx Maxter Remote report descriptor\n"); + rdesc[60] = 0xfa; + rdesc[40] = 0xfa; + } +} + +/* + * Some USB barcode readers from cypress have usage min and usage max in + * the wrong order + */ +static void usbhid_fixup_cypress_descriptor(unsigned char *rdesc, int rsize) +{ + short fixed = 0; + int i; + + for (i = 0; i < rsize - 4; i++) { + if (rdesc[i] == 0x29 && rdesc [i+2] == 0x19) { + unsigned char tmp; + + rdesc[i] = 0x19; rdesc[i+2] = 0x29; + tmp = rdesc[i+3]; + rdesc[i+3] = rdesc[i+1]; + rdesc[i+1] = tmp; + } + } + + if (fixed) + printk(KERN_INFO "Fixing up Cypress report descriptor\n"); +} + +/* + * MacBook JIS keyboard has wrong logical maximum + */ +static void usbhid_fixup_macbook_descriptor(unsigned char *rdesc, int rsize) +{ + if (rsize >= 60 && rdesc[53] == 0x65 + && rdesc[59] == 0x65) { + printk(KERN_INFO "Fixing up MacBook JIS keyboard report descriptor\n"); + rdesc[53] = rdesc[59] = 0xe7; + } +} + +static void usbhid_fixup_button_consumer_descriptor(unsigned char *rdesc, int rsize) +{ + if (rsize >= 30 && rdesc[29] == 0x05 + && rdesc[30] == 0x09) { + printk(KERN_INFO "Fixing up button/consumer in HID report descriptor\n"); + rdesc[30] = 0x0c; + } +} + +/* + * Microsoft Wireless Desktop Receiver (Model 1028) has several + * 'Usage Min/Max' where it ought to have 'Physical Min/Max' + */ +static void usbhid_fixup_microsoft_descriptor(unsigned char *rdesc, int rsize) +{ + if (rsize == 571 && rdesc[284] == 0x19 + && rdesc[286] == 0x2a + && rdesc[304] == 0x19 + && rdesc[306] == 0x29 + && rdesc[352] == 0x1a + && rdesc[355] == 0x2a + && rdesc[557] == 0x19 + && rdesc[559] == 0x29) { + printk(KERN_INFO "Fixing up Microsoft Wireless Receiver Model 1028 report descriptor\n"); + rdesc[284] = rdesc[304] = rdesc[557] = 0x35; + rdesc[352] = 0x36; + rdesc[286] = rdesc[355] = 0x46; + rdesc[306] = rdesc[559] = 0x45; + } +} + +static void __usbhid_fixup_report_descriptor(__u32 quirks, char *rdesc, unsigned rsize) +{ + if ((quirks & HID_QUIRK_RDESC_CYMOTION)) + usbhid_fixup_cymotion_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_LOGITECH) + usbhid_fixup_logitech_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_SWAPPED_MIN_MAX) + usbhid_fixup_cypress_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_PETALYNX) + usbhid_fixup_petalynx_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_MACBOOK_JIS) + usbhid_fixup_macbook_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_BUTTON_CONSUMER) + usbhid_fixup_button_consumer_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_SAMSUNG_REMOTE) + usbhid_fixup_samsung_irda_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_MICROSOFT_RECV_1028) + usbhid_fixup_microsoft_descriptor(rdesc, rsize); + + if (quirks & HID_QUIRK_RDESC_SUNPLUS_WDESKTOP) + usbhid_fixup_sunplus_wdesktop(rdesc, rsize); +} + +/** + * usbhid_fixup_report_descriptor: check if report descriptor needs fixup + * + * Description: + * Walks the hid_rdesc_blacklist[] array and checks whether the device + * is known to have broken report descriptor that needs to be fixed up + * prior to entering the HID parser + * + * Returns: nothing + */ +void usbhid_fixup_report_descriptor(const u16 idVendor, const u16 idProduct, + char *rdesc, unsigned rsize, char **quirks_param) +{ + int n, m; + u16 paramVendor, paramProduct; + u32 quirks; + + /* static rdesc quirk entries */ + for (n = 0; hid_rdesc_blacklist[n].idVendor; n++) + if (hid_rdesc_blacklist[n].idVendor == idVendor && + hid_rdesc_blacklist[n].idProduct == idProduct) + __usbhid_fixup_report_descriptor(hid_rdesc_blacklist[n].quirks, + rdesc, rsize); + + /* runtime rdesc quirk entries handling */ + for (n = 0; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) { + m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x", + ¶mVendor, ¶mProduct, &quirks); + + if (m != 3) + printk(KERN_WARNING + "Could not parse HID quirk module param %s\n", + quirks_param[n]); + else if (paramVendor == idVendor && paramProduct == idProduct) + __usbhid_fixup_report_descriptor(quirks, rdesc, rsize); + } +} diff --git a/usr/src/dkms_source_tree/hid-tmff.c b/usr/src/dkms_source_tree/hid-tmff.c new file mode 100644 index 0000000..144578b --- /dev/null +++ b/usr/src/dkms_source_tree/hid-tmff.c @@ -0,0 +1,225 @@ +/* + * Force feedback support for various HID compliant devices by ThrustMaster: + * ThrustMaster FireStorm Dual Power 2 + * and possibly others whose device ids haven't been added. + * + * Modified to support ThrustMaster devices by Zinx Verituse + * on 2003-01-25 from the Logitech force feedback driver, + * which is by Johann Deneux. + * + * Copyright (c) 2003 Zinx Verituse + * Copyright (c) 2002 Johann Deneux + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#undef DEBUG +#include + +#include +#include "usbhid.h" + +/* Usages for thrustmaster devices I know about */ +#define THRUSTMASTER_USAGE_FF (HID_UP_GENDESK | 0xbb) + +struct dev_type { + u16 idVendor; + u16 idProduct; + const signed short *ff; +}; + +static const signed short ff_rumble[] = { + FF_RUMBLE, + -1 +}; + +static const signed short ff_joystick[] = { + FF_CONSTANT, + -1 +}; + +static const struct dev_type devices[] = { + { 0x44f, 0xb300, ff_rumble }, + { 0x44f, 0xb304, ff_rumble }, + { 0x44f, 0xb651, ff_rumble }, /* FGT Rumble Force Wheel */ + { 0x44f, 0xb654, ff_joystick }, /* FGT Force Feedback Wheel */ +}; + +struct tmff_device { + struct hid_report *report; + struct hid_field *ff_field; +}; + +/* Changes values from 0 to 0xffff into values from minimum to maximum */ +static inline int hid_tmff_scale_u16(unsigned int in, + int minimum, int maximum) +{ + int ret; + + ret = (in * (maximum - minimum) / 0xffff) + minimum; + if (ret < minimum) + return minimum; + if (ret > maximum) + return maximum; + return ret; +} + +/* Changes values from -0x80 to 0x7f into values from minimum to maximum */ +static inline int hid_tmff_scale_s8(int in, + int minimum, int maximum) +{ + int ret; + + ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum; + if (ret < minimum) + return minimum; + if (ret > maximum) + return maximum; + return ret; +} + +static int hid_tmff_play(struct input_dev *dev, void *data, struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct tmff_device *tmff = data; + struct hid_field *ff_field = tmff->ff_field; + int x, y; + int left, right; /* Rumbling */ + + switch (effect->type) { + case FF_CONSTANT: + x = hid_tmff_scale_s8(effect->u.ramp.start_level, + ff_field->logical_minimum, + ff_field->logical_maximum); + y = hid_tmff_scale_s8(effect->u.ramp.end_level, + ff_field->logical_minimum, + ff_field->logical_maximum); + + dbg_hid("(x, y)=(%04x, %04x)\n", x, y); + ff_field->value[0] = x; + ff_field->value[1] = y; + usbhid_submit_report(hid, tmff->report, USB_DIR_OUT); + break; + + case FF_RUMBLE: + left = hid_tmff_scale_u16(effect->u.rumble.weak_magnitude, + ff_field->logical_minimum, + ff_field->logical_maximum); + right = hid_tmff_scale_u16(effect->u.rumble.strong_magnitude, + ff_field->logical_minimum, + ff_field->logical_maximum); + + dbg_hid("(left,right)=(%08x, %08x)\n", left, right); + ff_field->value[0] = left; + ff_field->value[1] = right; + usbhid_submit_report(hid, tmff->report, USB_DIR_OUT); + break; + } + return 0; +} + +int hid_tmff_init(struct hid_device *hid) +{ + struct tmff_device *tmff; + struct hid_report *report; + struct list_head *report_list; + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct input_dev *input_dev = hidinput->input; + const signed short *ff_bits = ff_joystick; + int error; + int i; + + tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL); + if (!tmff) + return -ENOMEM; + + /* Find the report to use */ + report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + list_for_each_entry(report, report_list, list) { + int fieldnum; + + for (fieldnum = 0; fieldnum < report->maxfield; ++fieldnum) { + struct hid_field *field = report->field[fieldnum]; + + if (field->maxusage <= 0) + continue; + + switch (field->usage[0].hid) { + case THRUSTMASTER_USAGE_FF: + if (field->report_count < 2) { + warn("ignoring FF field with report_count < 2"); + continue; + } + + if (field->logical_maximum == field->logical_minimum) { + warn("ignoring FF field with logical_maximum == logical_minimum"); + continue; + } + + if (tmff->report && tmff->report != report) { + warn("ignoring FF field in other report"); + continue; + } + + if (tmff->ff_field && tmff->ff_field != field) { + warn("ignoring duplicate FF field"); + continue; + } + + tmff->report = report; + tmff->ff_field = field; + + for (i = 0; i < ARRAY_SIZE(devices); i++) { + if (input_dev->id.vendor == devices[i].idVendor && + input_dev->id.product == devices[i].idProduct) { + ff_bits = devices[i].ff; + break; + } + } + + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], input_dev->ffbit); + + break; + + default: + warn("ignoring unknown output usage %08x", field->usage[0].hid); + continue; + } + } + } + + if (!tmff->report) { + err("cant find FF field in output reports\n"); + error = -ENODEV; + goto fail; + } + + error = input_ff_create_memless(input_dev, tmff, hid_tmff_play); + if (error) + goto fail; + + info("Force feedback for ThrustMaster devices by Zinx Verituse "); + return 0; + + fail: + kfree(tmff); + return error; +} + diff --git a/usr/src/dkms_source_tree/hid-zpff.c b/usr/src/dkms_source_tree/hid-zpff.c new file mode 100644 index 0000000..5a68827 --- /dev/null +++ b/usr/src/dkms_source_tree/hid-zpff.c @@ -0,0 +1,107 @@ +/* + * Force feedback support for Zeroplus based devices + * + * Copyright (c) 2005, 2006 Anssi Hannula + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include "usbhid.h" + +struct zpff_device { + struct hid_report *report; +}; + +static int hid_zpff_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct zpff_device *zpff = data; + int left, right; + + /* + * The following is specified the other way around in the Zeroplus + * datasheet but the order below is correct for the XFX Executioner; + * however it is possible that the XFX Executioner is an exception + */ + + left = effect->u.rumble.strong_magnitude; + right = effect->u.rumble.weak_magnitude; + dbg_hid("called with 0x%04x 0x%04x\n", left, right); + + left = left * 0x7f / 0xffff; + right = right * 0x7f / 0xffff; + + zpff->report->field[2]->value[0] = left; + zpff->report->field[3]->value[0] = right; + dbg_hid("running with 0x%02x 0x%02x\n", left, right); + usbhid_submit_report(hid, zpff->report, USB_DIR_OUT); + + return 0; +} + +int hid_zpff_init(struct hid_device *hid) +{ + struct zpff_device *zpff; + struct hid_report *report; + struct hid_input *hidinput = list_entry(hid->inputs.next, + struct hid_input, list); + struct list_head *report_list = + &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + int error; + + if (list_empty(report_list)) { + printk(KERN_ERR "hid-zpff: no output report found\n"); + return -ENODEV; + } + + report = list_entry(report_list->next, struct hid_report, list); + + if (report->maxfield < 4) { + printk(KERN_ERR "hid-zpff: not enough fields in report\n"); + return -ENODEV; + } + + zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL); + if (!zpff) + return -ENOMEM; + + set_bit(FF_RUMBLE, dev->ffbit); + + error = input_ff_create_memless(dev, zpff, hid_zpff_play); + if (error) { + kfree(zpff); + return error; + } + + zpff->report = report; + zpff->report->field[0]->value[0] = 0x00; + zpff->report->field[1]->value[0] = 0x02; + zpff->report->field[2]->value[0] = 0x00; + zpff->report->field[3]->value[0] = 0x00; + usbhid_submit_report(hid, zpff->report, USB_DIR_OUT); + + printk(KERN_INFO "Force feedback for Zeroplus based devices by " + "Anssi Hannula \n"); + + return 0; +} diff --git a/usr/src/dkms_source_tree/hiddev.c b/usr/src/dkms_source_tree/hiddev.c new file mode 100644 index 0000000..842e9ed --- /dev/null +++ b/usr/src/dkms_source_tree/hiddev.c @@ -0,0 +1,893 @@ +/* + * Copyright (c) 2001 Paul Stewart + * Copyright (c) 2001 Vojtech Pavlik + * + * HID char devices, giving access to raw HID device events. + * + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to Paul Stewart + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usbhid.h" + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define HIDDEV_MINOR_BASE 0 +#define HIDDEV_MINORS 256 +#else +#define HIDDEV_MINOR_BASE 96 +#define HIDDEV_MINORS 16 +#endif +#define HIDDEV_BUFFER_SIZE 64 + +struct hiddev { + int exist; + int open; + wait_queue_head_t wait; + struct hid_device *hid; + struct list_head list; + spinlock_t list_lock; +}; + +struct hiddev_list { + struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE]; + int head; + int tail; + unsigned flags; + struct fasync_struct *fasync; + struct hiddev *hiddev; + struct list_head node; +}; + +static struct hiddev *hiddev_table[HIDDEV_MINORS]; + +/* + * Find a report, given the report's type and ID. The ID can be specified + * indirectly by REPORT_ID_FIRST (which returns the first report of the given + * type) or by (REPORT_ID_NEXT | old_id), which returns the next report of the + * given type which follows old_id. + */ +static struct hid_report * +hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo) +{ + unsigned int flags = rinfo->report_id & ~HID_REPORT_ID_MASK; + unsigned int rid = rinfo->report_id & HID_REPORT_ID_MASK; + struct hid_report_enum *report_enum; + struct hid_report *report; + struct list_head *list; + + if (rinfo->report_type < HID_REPORT_TYPE_MIN || + rinfo->report_type > HID_REPORT_TYPE_MAX) + return NULL; + + report_enum = hid->report_enum + + (rinfo->report_type - HID_REPORT_TYPE_MIN); + + switch (flags) { + case 0: /* Nothing to do -- report_id is already set correctly */ + break; + + case HID_REPORT_ID_FIRST: + if (list_empty(&report_enum->report_list)) + return NULL; + + list = report_enum->report_list.next; + report = list_entry(list, struct hid_report, list); + rinfo->report_id = report->id; + break; + + case HID_REPORT_ID_NEXT: + report = report_enum->report_id_hash[rid]; + if (!report) + return NULL; + + list = report->list.next; + if (list == &report_enum->report_list) + return NULL; + + report = list_entry(list, struct hid_report, list); + rinfo->report_id = report->id; + break; + + default: + return NULL; + } + + return report_enum->report_id_hash[rinfo->report_id]; +} + +/* + * Perform an exhaustive search of the report table for a usage, given its + * type and usage id. + */ +static struct hid_field * +hiddev_lookup_usage(struct hid_device *hid, struct hiddev_usage_ref *uref) +{ + int i, j; + struct hid_report *report; + struct hid_report_enum *report_enum; + struct hid_field *field; + + if (uref->report_type < HID_REPORT_TYPE_MIN || + uref->report_type > HID_REPORT_TYPE_MAX) + return NULL; + + report_enum = hid->report_enum + + (uref->report_type - HID_REPORT_TYPE_MIN); + + list_for_each_entry(report, &report_enum->report_list, list) { + for (i = 0; i < report->maxfield; i++) { + field = report->field[i]; + for (j = 0; j < field->maxusage; j++) { + if (field->usage[j].hid == uref->usage_code) { + uref->report_id = report->id; + uref->field_index = i; + uref->usage_index = j; + return field; + } + } + } + } + + return NULL; +} + +static void hiddev_send_event(struct hid_device *hid, + struct hiddev_usage_ref *uref) +{ + struct hiddev *hiddev = hid->hiddev; + struct hiddev_list *list; + unsigned long flags; + + spin_lock_irqsave(&hiddev->list_lock, flags); + list_for_each_entry(list, &hiddev->list, node) { + if (uref->field_index != HID_FIELD_INDEX_NONE || + (list->flags & HIDDEV_FLAG_REPORT) != 0) { + list->buffer[list->head] = *uref; + list->head = (list->head + 1) & + (HIDDEV_BUFFER_SIZE - 1); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + } + spin_unlock_irqrestore(&hiddev->list_lock, flags); + + wake_up_interruptible(&hiddev->wait); +} + +/* + * This is where hid.c calls into hiddev to pass an event that occurred over + * the interrupt pipe + */ +void hiddev_hid_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + unsigned type = field->report_type; + struct hiddev_usage_ref uref; + + uref.report_type = + (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : + ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : + ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0)); + uref.report_id = field->report->id; + uref.field_index = field->index; + uref.usage_index = (usage - field->usage); + uref.usage_code = usage->hid; + uref.value = value; + + hiddev_send_event(hid, &uref); +} +EXPORT_SYMBOL_GPL(hiddev_hid_event); + +void hiddev_report_event(struct hid_device *hid, struct hid_report *report) +{ + unsigned type = report->type; + struct hiddev_usage_ref uref; + + memset(&uref, 0, sizeof(uref)); + uref.report_type = + (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : + ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : + ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0)); + uref.report_id = report->id; + uref.field_index = HID_FIELD_INDEX_NONE; + + hiddev_send_event(hid, &uref); +} + +/* + * fasync file op + */ +static int hiddev_fasync(int fd, struct file *file, int on) +{ + int retval; + struct hiddev_list *list = file->private_data; + + retval = fasync_helper(fd, file, on, &list->fasync); + + return retval < 0 ? retval : 0; +} + + +/* + * release file op + */ +static int hiddev_release(struct inode * inode, struct file * file) +{ + struct hiddev_list *list = file->private_data; + unsigned long flags; + + hiddev_fasync(-1, file, 0); + + spin_lock_irqsave(&list->hiddev->list_lock, flags); + list_del(&list->node); + spin_unlock_irqrestore(&list->hiddev->list_lock, flags); + + if (!--list->hiddev->open) { + if (list->hiddev->exist) + usbhid_close(list->hiddev->hid); + else + kfree(list->hiddev); + } + + kfree(list); + + return 0; +} + +/* + * open file op + */ +static int hiddev_open(struct inode *inode, struct file *file) +{ + struct hiddev_list *list; + unsigned long flags; + + int i = iminor(inode) - HIDDEV_MINOR_BASE; + + if (i >= HIDDEV_MINORS || !hiddev_table[i]) + return -ENODEV; + + if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) + return -ENOMEM; + + list->hiddev = hiddev_table[i]; + + spin_lock_irqsave(&list->hiddev->list_lock, flags); + list_add_tail(&list->node, &hiddev_table[i]->list); + spin_unlock_irqrestore(&list->hiddev->list_lock, flags); + + file->private_data = list; + + if (!list->hiddev->open++) + if (list->hiddev->exist) + usbhid_open(hiddev_table[i]->hid); + + return 0; +} + +/* + * "write" file op + */ +static ssize_t hiddev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +/* + * "read" file op + */ +static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos) +{ + DECLARE_WAITQUEUE(wait, current); + struct hiddev_list *list = file->private_data; + int event_size; + int retval = 0; + + event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? + sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); + + if (count < event_size) + return 0; + + while (retval == 0) { + if (list->head == list->tail) { + add_wait_queue(&list->hiddev->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (list->head == list->tail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (!list->hiddev->exist) { + retval = -EIO; + break; + } + + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&list->hiddev->wait, &wait); + } + + if (retval) + return retval; + + + while (list->head != list->tail && + retval + event_size <= count) { + if ((list->flags & HIDDEV_FLAG_UREF) == 0) { + if (list->buffer[list->tail].field_index != + HID_FIELD_INDEX_NONE) { + struct hiddev_event event; + event.hid = list->buffer[list->tail].usage_code; + event.value = list->buffer[list->tail].value; + if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) + return -EFAULT; + retval += sizeof(struct hiddev_event); + } + } else { + if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || + (list->flags & HIDDEV_FLAG_REPORT) != 0) { + if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) + return -EFAULT; + retval += sizeof(struct hiddev_usage_ref); + } + } + list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1); + } + + } + + return retval; +} + +/* + * "poll" file op + * No kernel lock - fine + */ +static unsigned int hiddev_poll(struct file *file, poll_table *wait) +{ + struct hiddev_list *list = file->private_data; + + poll_wait(file, &list->hiddev->wait, wait); + if (list->head != list->tail) + return POLLIN | POLLRDNORM; + if (!list->hiddev->exist) + return POLLERR | POLLHUP; + return 0; +} + +/* + * "ioctl" file op + */ +static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg) +{ + struct hid_device *hid = hiddev->hid; + struct hiddev_report_info rinfo; + struct hiddev_usage_ref_multi *uref_multi = NULL; + struct hiddev_usage_ref *uref; + struct hid_report *report; + struct hid_field *field; + int i; + + uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); + if (!uref_multi) + return -ENOMEM; + lock_kernel(); + uref = &uref_multi->uref; + if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) { + if (copy_from_user(uref_multi, user_arg, + sizeof(*uref_multi))) + goto fault; + } else { + if (copy_from_user(uref, user_arg, sizeof(*uref))) + goto fault; + } + + switch (cmd) { + case HIDIOCGUCODE: + rinfo.report_type = uref->report_type; + rinfo.report_id = uref->report_id; + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + goto inval; + + if (uref->field_index >= report->maxfield) + goto inval; + + field = report->field[uref->field_index]; + if (uref->usage_index >= field->maxusage) + goto inval; + + uref->usage_code = field->usage[uref->usage_index].hid; + + if (copy_to_user(user_arg, uref, sizeof(*uref))) + goto fault; + + kfree(uref_multi); + return 0; + + default: + if (cmd != HIDIOCGUSAGE && + cmd != HIDIOCGUSAGES && + uref->report_type == HID_REPORT_TYPE_INPUT) + goto inval; + + if (uref->report_id == HID_REPORT_ID_UNKNOWN) { + field = hiddev_lookup_usage(hid, uref); + if (field == NULL) + goto inval; + } else { + rinfo.report_type = uref->report_type; + rinfo.report_id = uref->report_id; + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + goto inval; + + if (uref->field_index >= report->maxfield) + goto inval; + + field = report->field[uref->field_index]; + + if (cmd == HIDIOCGCOLLECTIONINDEX) { + if (uref->usage_index >= field->maxusage) + goto inval; + } else if (uref->usage_index >= field->report_count) + goto inval; + + else if ((cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) && + (uref_multi->num_values > HID_MAX_MULTI_USAGES || + uref->usage_index + uref_multi->num_values > field->report_count)) + goto inval; + } + + switch (cmd) { + case HIDIOCGUSAGE: + uref->value = field->value[uref->usage_index]; + if (copy_to_user(user_arg, uref, sizeof(*uref))) + goto fault; + goto goodreturn; + + case HIDIOCSUSAGE: + field->value[uref->usage_index] = uref->value; + goto goodreturn; + + case HIDIOCGCOLLECTIONINDEX: + kfree(uref_multi); + return field->usage[uref->usage_index].collection_index; + case HIDIOCGUSAGES: + for (i = 0; i < uref_multi->num_values; i++) + uref_multi->values[i] = + field->value[uref->usage_index + i]; + if (copy_to_user(user_arg, uref_multi, + sizeof(*uref_multi))) + goto fault; + goto goodreturn; + case HIDIOCSUSAGES: + for (i = 0; i < uref_multi->num_values; i++) + field->value[uref->usage_index + i] = + uref_multi->values[i]; + goto goodreturn; + } + +goodreturn: + unlock_kernel(); + kfree(uref_multi); + return 0; +fault: + unlock_kernel(); + kfree(uref_multi); + return -EFAULT; +inval: + unlock_kernel(); + kfree(uref_multi); + return -EINVAL; + } +} + +static noinline int hiddev_ioctl_string(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg) +{ + struct hid_device *hid = hiddev->hid; + struct usb_device *dev = hid_to_usb_dev(hid); + int idx, len; + char *buf; + + if (get_user(idx, (int __user *)user_arg)) + return -EFAULT; + + if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL) + return -ENOMEM; + + if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) { + kfree(buf); + return -EINVAL; + } + + if (copy_to_user(user_arg+sizeof(int), buf, len+1)) { + kfree(buf); + return -EFAULT; + } + + kfree(buf); + + return len; +} + +static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct hiddev_list *list = file->private_data; + struct hiddev *hiddev = list->hiddev; + struct hid_device *hid = hiddev->hid; + struct usb_device *dev = hid_to_usb_dev(hid); + struct hiddev_collection_info cinfo; + struct hiddev_report_info rinfo; + struct hiddev_field_info finfo; + struct hiddev_devinfo dinfo; + struct hid_report *report; + struct hid_field *field; + struct usbhid_device *usbhid = hid->driver_data; + void __user *user_arg = (void __user *)arg; + int i; + + /* Called without BKL by compat methods so no BKL taken */ + + /* FIXME: Who or what stop this racing with a disconnect ?? */ + if (!hiddev->exist) + return -EIO; + + switch (cmd) { + + case HIDIOCGVERSION: + return put_user(HID_VERSION, (int __user *)arg); + + case HIDIOCAPPLICATION: + if (arg < 0 || arg >= hid->maxapplication) + return -EINVAL; + + for (i = 0; i < hid->maxcollection; i++) + if (hid->collection[i].type == + HID_COLLECTION_APPLICATION && arg-- == 0) + break; + + if (i == hid->maxcollection) + return -EINVAL; + + return hid->collection[i].usage; + + case HIDIOCGDEVINFO: + dinfo.bustype = BUS_USB; + dinfo.busnum = dev->bus->busnum; + dinfo.devnum = dev->devnum; + dinfo.ifnum = usbhid->ifnum; + dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor); + dinfo.product = le16_to_cpu(dev->descriptor.idProduct); + dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice); + dinfo.num_applications = hid->maxapplication; + if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) + return -EFAULT; + + return 0; + + case HIDIOCGFLAG: + if (put_user(list->flags, (int __user *)arg)) + return -EFAULT; + + return 0; + + case HIDIOCSFLAG: + { + int newflags; + if (get_user(newflags, (int __user *)arg)) + return -EFAULT; + + if ((newflags & ~HIDDEV_FLAGS) != 0 || + ((newflags & HIDDEV_FLAG_REPORT) != 0 && + (newflags & HIDDEV_FLAG_UREF) == 0)) + return -EINVAL; + + list->flags = newflags; + + return 0; + } + + case HIDIOCGSTRING: + return hiddev_ioctl_string(hiddev, cmd, user_arg); + + case HIDIOCINITREPORT: + usbhid_init_reports(hid); + + return 0; + + case HIDIOCGREPORT: + if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) + return -EFAULT; + + if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT) + return -EINVAL; + + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + return -EINVAL; + + usbhid_submit_report(hid, report, USB_DIR_IN); + usbhid_wait_io(hid); + + return 0; + + case HIDIOCSREPORT: + if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) + return -EFAULT; + + if (rinfo.report_type == HID_REPORT_TYPE_INPUT) + return -EINVAL; + + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + return -EINVAL; + + usbhid_submit_report(hid, report, USB_DIR_OUT); + usbhid_wait_io(hid); + + return 0; + + case HIDIOCGREPORTINFO: + if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) + return -EFAULT; + + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + return -EINVAL; + + rinfo.num_fields = report->maxfield; + + if (copy_to_user(user_arg, &rinfo, sizeof(rinfo))) + return -EFAULT; + + return 0; + + case HIDIOCGFIELDINFO: + if (copy_from_user(&finfo, user_arg, sizeof(finfo))) + return -EFAULT; + rinfo.report_type = finfo.report_type; + rinfo.report_id = finfo.report_id; + if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) + return -EINVAL; + + if (finfo.field_index >= report->maxfield) + return -EINVAL; + + field = report->field[finfo.field_index]; + memset(&finfo, 0, sizeof(finfo)); + finfo.report_type = rinfo.report_type; + finfo.report_id = rinfo.report_id; + finfo.field_index = field->report_count - 1; + finfo.maxusage = field->maxusage; + finfo.flags = field->flags; + finfo.physical = field->physical; + finfo.logical = field->logical; + finfo.application = field->application; + finfo.logical_minimum = field->logical_minimum; + finfo.logical_maximum = field->logical_maximum; + finfo.physical_minimum = field->physical_minimum; + finfo.physical_maximum = field->physical_maximum; + finfo.unit_exponent = field->unit_exponent; + finfo.unit = field->unit; + + if (copy_to_user(user_arg, &finfo, sizeof(finfo))) + return -EFAULT; + + return 0; + + case HIDIOCGUCODE: + /* fall through */ + case HIDIOCGUSAGE: + case HIDIOCSUSAGE: + case HIDIOCGUSAGES: + case HIDIOCSUSAGES: + case HIDIOCGCOLLECTIONINDEX: + return hiddev_ioctl_usage(hiddev, cmd, user_arg); + + case HIDIOCGCOLLECTIONINFO: + if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) + return -EFAULT; + + if (cinfo.index >= hid->maxcollection) + return -EINVAL; + + cinfo.type = hid->collection[cinfo.index].type; + cinfo.usage = hid->collection[cinfo.index].usage; + cinfo.level = hid->collection[cinfo.index].level; + + if (copy_to_user(user_arg, &cinfo, sizeof(cinfo))) + return -EFAULT; + return 0; + + default: + + if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) + return -EINVAL; + + if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) { + int len; + if (!hid->name) + return 0; + len = strlen(hid->name) + 1; + if (len > _IOC_SIZE(cmd)) + len = _IOC_SIZE(cmd); + return copy_to_user(user_arg, hid->name, len) ? + -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) { + int len; + if (!hid->phys) + return 0; + len = strlen(hid->phys) + 1; + if (len > _IOC_SIZE(cmd)) + len = _IOC_SIZE(cmd); + return copy_to_user(user_arg, hid->phys, len) ? + -EFAULT : len; + } + } + return -EINVAL; +} + +#ifdef CONFIG_COMPAT +static long hiddev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct file_operations hiddev_fops = { + .owner = THIS_MODULE, + .read = hiddev_read, + .write = hiddev_write, + .poll = hiddev_poll, + .open = hiddev_open, + .release = hiddev_release, + .unlocked_ioctl = hiddev_ioctl, + .fasync = hiddev_fasync, +#ifdef CONFIG_COMPAT + .compat_ioctl = hiddev_compat_ioctl, +#endif +}; + +static struct usb_class_driver hiddev_class = { + .name = "hiddev%d", + .fops = &hiddev_fops, + .minor_base = HIDDEV_MINOR_BASE, +}; + +/* + * This is where hid.c calls us to connect a hid device to the hiddev driver + */ +int hiddev_connect(struct hid_device *hid) +{ + struct hiddev *hiddev; + struct usbhid_device *usbhid = hid->driver_data; + int i; + int retval; + + for (i = 0; i < hid->maxcollection; i++) + if (hid->collection[i].type == + HID_COLLECTION_APPLICATION && + !IS_INPUT_APPLICATION(hid->collection[i].usage)) + break; + + if (i == hid->maxcollection && (hid->quirks & HID_QUIRK_HIDDEV) == 0) + return -1; + + if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) + return -1; + + retval = usb_register_dev(usbhid->intf, &hiddev_class); + if (retval) { + err_hid("Not able to get a minor for this device."); + kfree(hiddev); + return -1; + } + + init_waitqueue_head(&hiddev->wait); + INIT_LIST_HEAD(&hiddev->list); + spin_lock_init(&hiddev->list_lock); + hiddev->hid = hid; + hiddev->exist = 1; + + hid->minor = usbhid->intf->minor; + hid->hiddev = hiddev; + + hiddev_table[usbhid->intf->minor - HIDDEV_MINOR_BASE] = hiddev; + + return 0; +} + +/* + * This is where hid.c calls us to disconnect a hiddev device from the + * corresponding hid device (usually because the usb device has disconnected) + */ +static struct usb_class_driver hiddev_class; +void hiddev_disconnect(struct hid_device *hid) +{ + struct hiddev *hiddev = hid->hiddev; + struct usbhid_device *usbhid = hid->driver_data; + + hiddev->exist = 0; + + hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL; + usb_deregister_dev(usbhid->intf, &hiddev_class); + + if (hiddev->open) { + usbhid_close(hiddev->hid); + wake_up_interruptible(&hiddev->wait); + } else { + kfree(hiddev); + } +} + +/* Currently this driver is a USB driver. It's not a conventional one in + * the sense that it doesn't probe at the USB level. Instead it waits to + * be connected by HID through the hiddev_connect / hiddev_disconnect + * routines. The reason to register as a USB device is to gain part of the + * minor number space from the USB major. + * + * In theory, should the HID code be generalized to more than one physical + * medium (say, IEEE 1384), this driver will probably need to register its + * own major number, and in doing so, no longer need to register with USB. + * At that point the probe routine and hiddev_driver struct below will no + * longer be useful. + */ + + +/* We never attach in this manner, and rely on HID to connect us. This + * is why there is no disconnect routine defined in the usb_driver either. + */ +static int hiddev_usbd_probe(struct usb_interface *intf, + const struct usb_device_id *hiddev_info) +{ + return -ENODEV; +} + + +static /* const */ struct usb_driver hiddev_driver = { + .name = "hiddev", + .probe = hiddev_usbd_probe, +}; + +int __init hiddev_init(void) +{ + return usb_register(&hiddev_driver); +} + +void hiddev_exit(void) +{ + usb_deregister(&hiddev_driver); +} diff --git a/usr/src/dkms_source_tree/patches/10-ignore-trackpad-on-unibody-macbooks.patch b/usr/src/dkms_source_tree/patches/10-ignore-trackpad-on-unibody-macbooks.patch new file mode 100644 index 0000000..a527d1b --- /dev/null +++ b/usr/src/dkms_source_tree/patches/10-ignore-trackpad-on-unibody-macbooks.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c +index b15f882..1c07576 100644 +--- a/drivers/hid/usbhid/hid-quirks.c ++++ b/drivers/hid/usbhid/hid-quirks.c +@@ -81,6 +81,9 @@ + #define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI 0x0230 + #define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO 0x0231 + #define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS 0x0232 ++#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI 0x0236 ++#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO 0x0237 ++#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS 0x0238 + #define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a + #define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b + #define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242 +@@ -660,6 +663,9 @@ static const struct hid_blacklist { + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE}, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, ++ { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE}, ++ { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_APPLE_ISO_KEYBOARD | HID_QUIRK_IGNORE_MOUSE }, ++ { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS, HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY, HID_QUIRK_APPLE_NUMLOCK_EMULATION | HID_QUIRK_APPLE_HAS_FN | HID_QUIRK_IGNORE_MOUSE }, + diff --git a/usr/src/dkms_source_tree/usbhid.h b/usr/src/dkms_source_tree/usbhid.h new file mode 100644 index 0000000..62d2d7c --- /dev/null +++ b/usr/src/dkms_source_tree/usbhid.h @@ -0,0 +1,88 @@ +#ifndef __USBHID_H +#define __USBHID_H + +/* + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2006 Jiri Kosina + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* API provided by hid-core.c for USB HID drivers */ +int usbhid_wait_io(struct hid_device* hid); +void usbhid_close(struct hid_device *hid); +int usbhid_open(struct hid_device *hid); +void usbhid_init_reports(struct hid_device *hid); +void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir); + +/* + * USB-specific HID struct, to be pointed to + * from struct hid_device->driver_data + */ + +struct usbhid_device { + struct hid_device *hid; /* pointer to corresponding HID dev */ + + struct usb_interface *intf; /* USB interface */ + int ifnum; /* USB interface number */ + + unsigned int bufsize; /* URB buffer size */ + + struct urb *urbin; /* Input URB */ + char *inbuf; /* Input buffer */ + dma_addr_t inbuf_dma; /* Input buffer dma */ + spinlock_t inlock; /* Input fifo spinlock */ + + struct urb *urbctrl; /* Control URB */ + struct usb_ctrlrequest *cr; /* Control request struct */ + dma_addr_t cr_dma; /* Control request struct dma */ + struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE]; /* Control fifo */ + unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */ + char *ctrlbuf; /* Control buffer */ + dma_addr_t ctrlbuf_dma; /* Control buffer dma */ + spinlock_t ctrllock; /* Control fifo spinlock */ + + struct urb *urbout; /* Output URB */ + struct hid_report *out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ + unsigned char outhead, outtail; /* Output pipe fifo head & tail */ + char *outbuf; /* Output buffer */ + dma_addr_t outbuf_dma; /* Output buffer dma */ + spinlock_t outlock; /* Output fifo spinlock */ + + unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ + struct timer_list io_retry; /* Retry timer */ + unsigned long stop_retry; /* Time to give up, in jiffies */ + unsigned int retry_delay; /* Delay length in ms */ + struct work_struct reset_work; /* Task context for resets */ + wait_queue_head_t wait; /* For sleeping */ +}; + +#define hid_to_usb_dev(hid_dev) \ + container_of(hid_dev->dev->parent, struct usb_device, dev) + +#endif + diff --git a/usr/src/dkms_source_tree/usbkbd.c b/usr/src/dkms_source_tree/usbkbd.c new file mode 100644 index 0000000..0caaafe --- /dev/null +++ b/usr/src/dkms_source_tree/usbkbd.c @@ -0,0 +1,365 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * USB HIDBP Keyboard support + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to , or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include +#include +#include +#include +#include +#include + +/* + * Version Information + */ +#define DRIVER_VERSION "" +#define DRIVER_AUTHOR "Vojtech Pavlik " +#define DRIVER_DESC "USB HID Boot Protocol keyboard driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +static const unsigned char usb_kbd_keycode[256] = { + 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, + 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, + 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, + 105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, + 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, + 191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, + 115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, + 122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, + 150,158,159,128,136,177,178,176,142,152,173,140 +}; + +struct usb_kbd { + struct input_dev *dev; + struct usb_device *usbdev; + unsigned char old[8]; + struct urb *irq, *led; + unsigned char newleds; + char name[128]; + char phys[64]; + + unsigned char *new; + struct usb_ctrlrequest *cr; + unsigned char *leds; + dma_addr_t cr_dma; + dma_addr_t new_dma; + dma_addr_t leds_dma; +}; + +static void usb_kbd_irq(struct urb *urb) +{ + struct usb_kbd *kbd = urb->context; + int i; + + switch (urb->status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + return; + /* -EPIPE: should clear the halt */ + default: /* error */ + goto resubmit; + } + + for (i = 0; i < 8; i++) + input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); + + for (i = 2; i < 8; i++) { + + if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) { + if (usb_kbd_keycode[kbd->old[i]]) + input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0); + else + info("Unknown key (scancode %#x) released.", kbd->old[i]); + } + + if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) { + if (usb_kbd_keycode[kbd->new[i]]) + input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1); + else + info("Unknown key (scancode %#x) pressed.", kbd->new[i]); + } + } + + input_sync(kbd->dev); + + memcpy(kbd->old, kbd->new, 8); + +resubmit: + i = usb_submit_urb (urb, GFP_ATOMIC); + if (i) + err_hid ("can't resubmit intr, %s-%s/input0, status %d", + kbd->usbdev->bus->bus_name, + kbd->usbdev->devpath, i); +} + +static int usb_kbd_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct usb_kbd *kbd = input_get_drvdata(dev); + + if (type != EV_LED) + return -1; + + kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | + (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | + (!!test_bit(LED_NUML, dev->led)); + + if (kbd->led->status == -EINPROGRESS) + return 0; + + if (*(kbd->leds) == kbd->newleds) + return 0; + + *(kbd->leds) = kbd->newleds; + kbd->led->dev = kbd->usbdev; + if (usb_submit_urb(kbd->led, GFP_ATOMIC)) + err_hid("usb_submit_urb(leds) failed"); + + return 0; +} + +static void usb_kbd_led(struct urb *urb) +{ + struct usb_kbd *kbd = urb->context; + + if (urb->status) + warn("led urb status %d received", urb->status); + + if (*(kbd->leds) == kbd->newleds) + return; + + *(kbd->leds) = kbd->newleds; + kbd->led->dev = kbd->usbdev; + if (usb_submit_urb(kbd->led, GFP_ATOMIC)) + err_hid("usb_submit_urb(leds) failed"); +} + +static int usb_kbd_open(struct input_dev *dev) +{ + struct usb_kbd *kbd = input_get_drvdata(dev); + + kbd->irq->dev = kbd->usbdev; + if (usb_submit_urb(kbd->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usb_kbd_close(struct input_dev *dev) +{ + struct usb_kbd *kbd = input_get_drvdata(dev); + + usb_kill_urb(kbd->irq); +} + +static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) +{ + if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))) + return -1; + if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL))) + return -1; + if (!(kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma))) + return -1; + if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma))) + return -1; + if (!(kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) + return -1; + + return 0; +} + +static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd) +{ + usb_free_urb(kbd->irq); + usb_free_urb(kbd->led); + usb_buffer_free(dev, 8, kbd->new, kbd->new_dma); + usb_buffer_free(dev, sizeof(struct usb_ctrlrequest), kbd->cr, kbd->cr_dma); + usb_buffer_free(dev, 1, kbd->leds, kbd->leds_dma); +} + +static int usb_kbd_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(iface); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_kbd *kbd; + struct input_dev *input_dev; + int i, pipe, maxp; + int error = -ENOMEM; + + interface = iface->cur_altsetting; + + if (interface->desc.bNumEndpoints != 1) + return -ENODEV; + + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!kbd || !input_dev) + goto fail1; + + if (usb_kbd_alloc_mem(dev, kbd)) + goto fail2; + + kbd->usbdev = dev; + kbd->dev = input_dev; + + if (dev->manufacturer) + strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); + + if (dev->product) { + if (dev->manufacturer) + strlcat(kbd->name, " ", sizeof(kbd->name)); + strlcat(kbd->name, dev->product, sizeof(kbd->name)); + } + + if (!strlen(kbd->name)) + snprintf(kbd->name, sizeof(kbd->name), + "USB HIDBP Keyboard %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); + strlcpy(kbd->phys, "/input0", sizeof(kbd->phys)); + + input_dev->name = kbd->name; + input_dev->phys = kbd->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &iface->dev; + + input_set_drvdata(input_dev, kbd); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | + BIT_MASK(EV_REP); + input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) | + BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) | + BIT_MASK(LED_KANA); + + for (i = 0; i < 255; i++) + set_bit(usb_kbd_keycode[i], input_dev->keybit); + clear_bit(0, input_dev->keybit); + + input_dev->event = usb_kbd_event; + input_dev->open = usb_kbd_open; + input_dev->close = usb_kbd_close; + + usb_fill_int_urb(kbd->irq, dev, pipe, + kbd->new, (maxp > 8 ? 8 : maxp), + usb_kbd_irq, kbd, endpoint->bInterval); + kbd->irq->transfer_dma = kbd->new_dma; + kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; + kbd->cr->bRequest = 0x09; + kbd->cr->wValue = cpu_to_le16(0x200); + kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + kbd->cr->wLength = cpu_to_le16(1); + + usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0), + (void *) kbd->cr, kbd->leds, 1, + usb_kbd_led, kbd); + kbd->led->setup_dma = kbd->cr_dma; + kbd->led->transfer_dma = kbd->leds_dma; + kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP); + + error = input_register_device(kbd->dev); + if (error) + goto fail2; + + usb_set_intfdata(iface, kbd); + return 0; + +fail2: + usb_kbd_free_mem(dev, kbd); +fail1: + input_free_device(input_dev); + kfree(kbd); + return error; +} + +static void usb_kbd_disconnect(struct usb_interface *intf) +{ + struct usb_kbd *kbd = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (kbd) { + usb_kill_urb(kbd->irq); + input_unregister_device(kbd->dev); + usb_kbd_free_mem(interface_to_usbdev(intf), kbd); + kfree(kbd); + } +} + +static struct usb_device_id usb_kbd_id_table [] = { + { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, + USB_INTERFACE_PROTOCOL_KEYBOARD) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_kbd_id_table); + +static struct usb_driver usb_kbd_driver = { + .name = "usbkbd", + .probe = usb_kbd_probe, + .disconnect = usb_kbd_disconnect, + .id_table = usb_kbd_id_table, +}; + +static int __init usb_kbd_init(void) +{ + int result = usb_register(&usb_kbd_driver); + if (result == 0) + info(DRIVER_VERSION ":" DRIVER_DESC); + return result; +} + +static void __exit usb_kbd_exit(void) +{ + usb_deregister(&usb_kbd_driver); +} + +module_init(usb_kbd_init); +module_exit(usb_kbd_exit); diff --git a/usr/src/dkms_source_tree/usbmouse.c b/usr/src/dkms_source_tree/usbmouse.c new file mode 100644 index 0000000..35689ef --- /dev/null +++ b/usr/src/dkms_source_tree/usbmouse.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 1999-2001 Vojtech Pavlik + * + * USB HIDBP Mouse support + */ + +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to , or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include +#include +#include +#include +#include +#include + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.6" +#define DRIVER_AUTHOR "Vojtech Pavlik " +#define DRIVER_DESC "USB HID Boot Protocol mouse driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +struct usb_mouse { + char name[128]; + char phys[64]; + struct usb_device *usbdev; + struct input_dev *dev; + struct urb *irq; + + signed char *data; + dma_addr_t data_dma; +}; + +static void usb_mouse_irq(struct urb *urb) +{ + struct usb_mouse *mouse = urb->context; + signed char *data = mouse->data; + struct input_dev *dev = mouse->dev; + int status; + + switch (urb->status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + return; + /* -EPIPE: should clear the halt */ + default: /* error */ + goto resubmit; + } + + input_report_key(dev, BTN_LEFT, data[0] & 0x01); + input_report_key(dev, BTN_RIGHT, data[0] & 0x02); + input_report_key(dev, BTN_MIDDLE, data[0] & 0x04); + input_report_key(dev, BTN_SIDE, data[0] & 0x08); + input_report_key(dev, BTN_EXTRA, data[0] & 0x10); + + input_report_rel(dev, REL_X, data[1]); + input_report_rel(dev, REL_Y, data[2]); + input_report_rel(dev, REL_WHEEL, data[3]); + + input_sync(dev); +resubmit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("can't resubmit intr, %s-%s/input0, status %d", + mouse->usbdev->bus->bus_name, + mouse->usbdev->devpath, status); +} + +static int usb_mouse_open(struct input_dev *dev) +{ + struct usb_mouse *mouse = input_get_drvdata(dev); + + mouse->irq->dev = mouse->usbdev; + if (usb_submit_urb(mouse->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usb_mouse_close(struct input_dev *dev) +{ + struct usb_mouse *mouse = input_get_drvdata(dev); + + usb_kill_urb(mouse->irq); +} + +static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_mouse *mouse; + struct input_dev *input_dev; + int pipe, maxp; + int error = -ENOMEM; + + interface = intf->cur_altsetting; + + if (interface->desc.bNumEndpoints != 1) + return -ENODEV; + + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!mouse || !input_dev) + goto fail1; + + mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma); + if (!mouse->data) + goto fail1; + + mouse->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!mouse->irq) + goto fail2; + + mouse->usbdev = dev; + mouse->dev = input_dev; + + if (dev->manufacturer) + strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name)); + + if (dev->product) { + if (dev->manufacturer) + strlcat(mouse->name, " ", sizeof(mouse->name)); + strlcat(mouse->name, dev->product, sizeof(mouse->name)); + } + + if (!strlen(mouse->name)) + snprintf(mouse->name, sizeof(mouse->name), + "USB HIDBP Mouse %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + + usb_make_path(dev, mouse->phys, sizeof(mouse->phys)); + strlcat(mouse->phys, "/input0", sizeof(mouse->phys)); + + input_dev->name = mouse->name; + input_dev->phys = mouse->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | + BIT_MASK(BTN_EXTRA); + input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); + + input_set_drvdata(input_dev, mouse); + + input_dev->open = usb_mouse_open; + input_dev->close = usb_mouse_close; + + usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, + (maxp > 8 ? 8 : maxp), + usb_mouse_irq, mouse, endpoint->bInterval); + mouse->irq->transfer_dma = mouse->data_dma; + mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(mouse->dev); + if (error) + goto fail3; + + usb_set_intfdata(intf, mouse); + return 0; + +fail3: + usb_free_urb(mouse->irq); +fail2: + usb_buffer_free(dev, 8, mouse->data, mouse->data_dma); +fail1: + input_free_device(input_dev); + kfree(mouse); + return error; +} + +static void usb_mouse_disconnect(struct usb_interface *intf) +{ + struct usb_mouse *mouse = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (mouse) { + usb_kill_urb(mouse->irq); + input_unregister_device(mouse->dev); + usb_free_urb(mouse->irq); + usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma); + kfree(mouse); + } +} + +static struct usb_device_id usb_mouse_id_table [] = { + { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, + USB_INTERFACE_PROTOCOL_MOUSE) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_mouse_id_table); + +static struct usb_driver usb_mouse_driver = { + .name = "usbmouse", + .probe = usb_mouse_probe, + .disconnect = usb_mouse_disconnect, + .id_table = usb_mouse_id_table, +}; + +static int __init usb_mouse_init(void) +{ + int retval = usb_register(&usb_mouse_driver); + if (retval == 0) + info(DRIVER_VERSION ":" DRIVER_DESC); + return retval; +} + +static void __exit usb_mouse_exit(void) +{ + usb_deregister(&usb_mouse_driver); +} + +module_init(usb_mouse_init); +module_exit(usb_mouse_exit); -- cgit v1.2.3