GIT 5fdc0516ab243fb15d96b0c5d1378c742951a1e1 git+ssh://master.kernel.org/pub/scm/linux/kernel/git/jikos/hid.git#mm commit a1eddba44e7250028408fe3f6224926cb7ef7a95 Author: Jiri Kosina Date: Mon Aug 20 12:13:34 2007 +0200 HID: Report usage codes of keys as EV_MSC scancode events Current HID layer does not report usage codes to the input layer. This feature was previously removed, because it caused unnecessary storm of events in cases of positioning devices, etc. This patch adds reporting of usage codes as EV_MSC events only for key events. We issue the EV_MSC event only if the state of the key corresponding to the given code has changed, so that we don't report usages that are sent in every report even if the state hasn't changed (for example Shift/Caps Lock/... states as sent by various keyboards). This functionality is required at least by KeyTouch in order to provide convenient means for remapping the usage codes. Cc: Marvin Raaijmakers Signed-off-by: Jiri Kosina commit 851ede0338c9e228659af3b8f9ad972ee4eaf2ff Author: Jiri Kosina Date: Sat Aug 11 23:39:42 2007 +0200 HID: ignore all non-LED usages in output fields in hid-input We have to ignore all non-LED usages in output fields if the report descriptor of the device specifies any. If we don't do so, the devices which contain the same usages both in input and output reports with different parameters will mess things up. In hid-input, we currently care only for the input usages, with exception for LEDs. All other output usages should be properly handled by appropriate force-feedback driver. Fixes auto-calibration for Saitek Cyborg Evo Force joystick. Reported-by: Renato Golin Signed-off-by: Jiri Kosina commit faf142b45d49be44d74a9bb809f0e36057075dda Author: Jiri Kosina Date: Thu Aug 9 14:04:56 2007 +0200 HID: fix whitespace damage Fixes some trivial whitespace damage in hid-input.c Signed-off-by: Jiri Kosina commit 6b857b7bd6730c8750d1b752a1c4d7f4adcad800 Author: Dmitry Torokhov Date: Mon Jul 30 14:56:26 2007 +0200 HID: add support for Thrustmaster FGT Force Feedback wheel Rework thrustmaster force-feedback module to support devices having different types of force feedback effects. Add signatures of Thrustmaster FGT Rumble Force and Thrustmaster FGT Force Feedback wheels to the list of devices dupported by the module. Parts of the patch were lifted off a simalar patch by Anssi Hannula Signed-off-by: Dmitry Torokhov Signed-off-by: Jiri Kosina commit 389b1da49c7d2d516cc269ed3a1934a4de46f90e Author: Oliver Neukum Date: Wed Jul 11 14:48:58 2007 +0200 HID: minimal autosuspend support for USB HID devices Autosuspend for USB HID devices remains problematic as far as mice and keyboards are concerned. While I am working on a grand solution, here's a minimalist patch that works for those devices not continously in use. Signed-off-by: Oliver Neukum Signed-off-by: Jiri Kosina commit 0f48b3ce23a34dbe70dcd04b4216fbcb965ab906 Author: Jiri Kosina Date: Thu Aug 9 13:24:11 2007 +0200 HID: add support for Microsoft Natural Ergonomic Keyboard 4000 This keyboard emits a few usages that are not handled properly by hid-input. The usages from MSVENDOR page are colliding with Chicony Tactical Pad device, so we have to distinguish in runtime. Ugly ... Also, the buttons 1-5 have to be handled in a non-standard way, as they are emitted by the keyboard in a bitfield-like fashion, but the field is not presented as bit-field by the keyboard. The keys can't be pressed simultaneously, so the handling we have is correct. This patch also extends hid_keyboard[] with KPLeftParenthesis and KPRightParenthesis as defined by Keyboard page in HUT 1.12. The corresponding usages are also emitted by this keyboard. Signed-off-by: Jiri Kosina commit 1ae0104c1c0005d0f04af6bea0fa98f644d6a464 Author: Jiri Kosina Date: Wed Aug 8 17:19:08 2007 +0200 Input: add KEY_SPELLCHECK HUT 1.12 defines Spell Check usage 0x1ab in Consumer page. There are keyboards out there emitting this usage code (for example Microsoft Natural Ergonomic Keyboard 4000). Add this key so that HID code could map usages to it. Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina commit 02c23aef290d728d30eba7d4ed7588b79f26683c Author: Jiri Kosina Date: Mon May 14 09:57:40 2007 +0200 HID: add hidraw interface hidraw is an interface that is going to obsolete hiddev one day. Many userland applications are using libusb instead of using kernel-provided hiddev interface. This is caused by various reasons - the HID parser in kernel doesn't handle all the HID hardware on the planet properly, some devices might require its own specific quirks/drivers, etc. hiddev interface tries to do its best to parse all the received reports properly, and presents only parsed usages into userspace. This is however often not enough, and that's the reason why many userland applications just don't use hiddev at all, and rather use libusb to read raw USB events and process them on their own. Another drawback of hiddev is that it is USB-specific. hidraw interface provides userspace readers with really raw HID reports, no matter what the low-level transport layer is (USB/BT), and gives the userland applications all the freedom to process the HID reports in a way they wish to. Signed-off-by: Jiri Kosina commit 043fc88802f5626de912b849149c392f267dccb1 Author: Jiri Kosina Date: Mon May 14 09:54:30 2007 +0200 USB HID: provide hook for hidraw write() Add hook in usbhid for write() callback from hidraw. Sends the report to the device through control pipe. Signed-off-by: Jiri Kosina drivers/hid/Kconfig | 19 ++ drivers/hid/Makefile | 2 drivers/hid/hid-core.c | 16 ++ drivers/hid/hid-debug.c | 2 drivers/hid/hid-input.c | 80 +++++++- drivers/hid/hidraw.c | 395 +++++++++++++++++++++++++++++++++++++++++ drivers/hid/usbhid/Kconfig | 5 - drivers/hid/usbhid/hid-core.c | 54 +++++- drivers/hid/usbhid/hid-ff.c | 2 drivers/hid/usbhid/hid-tmff.c | 161 ++++++++++++----- include/linux/hid.h | 5 + include/linux/hidraw.h | 86 +++++++++ include/linux/input.h | 1 13 files changed, 770 insertions(+), 58 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 19667fc..cacf89e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -46,6 +46,25 @@ config HID_DEBUG If unsure, say N +config HIDRAW + bool "/dev/hidraw raw HID device support" + depends on HID + ---help--- + Say Y here if you want to support HID devices (from the USB + specification standpoint) that aren't strictly user interface + devices, like monitor controls and Uninterruptable Power Supplies. + + This module supports these devices separately using a separate + event interface on /dev/hidraw. + + There is also a /dev/hiddev configuration option in the USB HID + configuration menu. In comparison to hiddev, this device does not process + the hid events at all (no parsing, no lookups). This lets applications + to work on raw hid events when they want to, and avoid using transport-specific + userspace libhid/libusb libraries. + + If unsure, say Y. + source "drivers/hid/usbhid/Kconfig" endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 68d1376..1ac5103 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,7 +4,9 @@ # hid-objs := hid-core.o hid-input.o obj-$(CONFIG_HID) += hid.o + hid-$(CONFIG_HID_DEBUG) += hid-debug.o +hid-$(CONFIG_HIDRAW) += hidraw.o obj-$(CONFIG_USB_HID) += usbhid/ obj-$(CONFIG_USB_MOUSE) += usbhid/ diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 317cf8a..2884b03 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -30,6 +30,7 @@ #include #include #include #include +#include /* * Version Information @@ -979,6 +980,8 @@ int hid_input_report(struct hid_device * if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event) hid->hiddev_report_event(hid, report); + if (hid->claimed & HID_CLAIMED_HIDRAW) + hidraw_report_event(hid, data, size); for (n = 0; n < report->maxfield; n++) hid_input_field(hid, report->field[n], data, interrupt); @@ -990,5 +993,18 @@ int hid_input_report(struct hid_device * } EXPORT_SYMBOL_GPL(hid_input_report); +static int __init hid_init(void) +{ + return hidraw_init(); +} + +static void __exit hid_exit(void) +{ + hidraw_exit(); +} + +module_init(hid_init); +module_exit(hid_exit); + MODULE_LICENSE(DRIVER_LICENSE); diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index a13757b..27e4cf0 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -697,7 +697,7 @@ static char *keys[KEY_MAX + 1] = { [KEY_DEL_LINE] = "DeleteLine", [KEY_SEND] = "Send", [KEY_REPLY] = "Reply", [KEY_FORWARDMAIL] = "ForwardMail", [KEY_SAVE] = "Save", - [KEY_DOCUMENTS] = "Documents", + [KEY_DOCUMENTS] = "Documents", [KEY_SPELLCHECK] = "SpellCheck", [KEY_FN] = "Fn", [KEY_FN_ESC] = "Fn+ESC", [KEY_FN_1] = "Fn+1", [KEY_FN_2] = "Fn+2", [KEY_FN_B] = "Fn+B", [KEY_FN_D] = "Fn+D", diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 8edbd30..128214a 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -53,7 +53,7 @@ static const unsigned char hid_keyboard[ 115,114,unk,unk,unk,121,unk, 89, 93,124, 92, 94, 95,unk,unk,unk, 122,123, 90, 91, 85,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, - unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, + unk,unk,unk,unk,unk,unk,179,180,unk,unk,unk,unk,unk,unk,unk,unk, unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk, 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, @@ -86,6 +86,10 @@ #define map_led(c) do { usage->code = c; #define map_abs_clear(c) do { map_abs(c); clear_bit(c, bit); } while (0) #define map_key_clear(c) do { map_key(c); clear_bit(c, bit); } while (0) +/* hardware needing special handling due to colliding MSVENDOR page usages */ +#define IS_CHICONY_TACTICAL_PAD(x) (x->vendor == 0x04f2 && device->product == 0x0418) +#define IS_MS_NEK4K(x) (x->vendor == 0x045e && x->product == 0x00db) + #ifdef CONFIG_USB_HIDINPUT_POWERBOOK struct hidinput_key_translation { @@ -295,7 +299,7 @@ static int hidinput_getkeycode(struct in { struct hid_device *hid = dev->private; struct hid_usage *usage; - + usage = hidinput_find_key(hid, scancode, 0); if (usage) { *keycode = usage->code; @@ -310,15 +314,15 @@ static int hidinput_setkeycode(struct in struct hid_device *hid = dev->private; struct hid_usage *usage; int old_keycode; - + if (keycode < 0 || keycode > KEY_MAX) return -EINVAL; - + usage = hidinput_find_key(hid, scancode, 0); if (usage) { old_keycode = usage->code; usage->code = keycode; - + clear_bit(old_keycode, dev->keybit); set_bit(usage->code, dev->keybit); dbg_hid(KERN_DEBUG "Assigned keycode %d to HID usage code %x\n", keycode, scancode); @@ -326,10 +330,10 @@ static int hidinput_setkeycode(struct in * by another key */ if (hidinput_find_key (hid, 0, old_keycode)) set_bit(old_keycode, dev->keybit); - + return 0; } - + return -EINVAL; } @@ -351,6 +355,13 @@ static void hidinput_configure_usage(str if (field->flags & HID_MAIN_ITEM_CONSTANT) goto ignore; + /* only LED usages are supported in output fields */ + if (field->report_type == HID_OUTPUT_REPORT && + (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) { + dbg_hid_line(" [non-LED output field] "); + goto ignore; + } + switch (usage->hid & HID_USAGE_PAGE) { case HID_UP_UNDEFINED: @@ -614,6 +625,7 @@ static void hidinput_configure_usage(str case 0x19e: map_key_clear(KEY_COFFEE); break; case 0x1a6: map_key_clear(KEY_HELP); break; case 0x1a7: map_key_clear(KEY_DOCUMENTS); break; + case 0x1ab: map_key_clear(KEY_SPELLCHECK); break; case 0x1bc: map_key_clear(KEY_MESSENGER); break; case 0x1bd: map_key_clear(KEY_INFO); break; case 0x201: map_key_clear(KEY_NEW); break; @@ -720,8 +732,15 @@ static void hidinput_configure_usage(str case HID_UP_MSVENDOR: - /* special case - Chicony Chicony KU-0418 tactical pad */ - if (device->vendor == 0x04f2 && device->product == 0x0418) { + /* Unfortunately, there are multiple devices which + * emit usages from MSVENDOR page that require different + * handling. If this list grows too much in the future, + * more general handling will have to be introduced here + * (i.e. another blacklist). + */ + + /* Chicony Chicony KU-0418 tactical pad */ + if (IS_CHICONY_TACTICAL_PAD(device)) { set_bit(EV_REP, input->evbit); switch(usage->hid & HID_USAGE) { case 0xff01: map_key_clear(BTN_1); break; @@ -737,6 +756,20 @@ static void hidinput_configure_usage(str case 0xff0b: map_key_clear(BTN_B); break; default: goto ignore; } + + /* Microsoft Natural Ergonomic Keyboard 4000 */ + } else if (IS_MS_NEK4K(device)) { + switch(usage->hid & HID_USAGE) { + case 0xff05: + set_bit(EV_REP, input->evbit); + map_key_clear(BTN_0); + set_bit(BTN_1, input->keybit); + set_bit(BTN_2, input->keybit); + set_bit(BTN_3, input->keybit); + set_bit(BTN_4, input->keybit); + set_bit(BTN_5, input->keybit); + default: goto ignore; + } } else { goto ignore; } @@ -888,6 +921,11 @@ static void hidinput_configure_usage(str set_bit(KEY_VOLUMEDOWN, input->keybit); } + if (usage->type == EV_KEY) { + set_bit(EV_MSC, input->evbit); + set_bit(MSC_SCAN, input->mscbit); + } + hid_resolv_event(usage->type, usage->code); dbg_hid_line("\n"); @@ -991,6 +1029,30 @@ void hidinput_hid_event(struct hid_devic return; } + /* Handling MS NEK4K special buttons */ + if (IS_MS_NEK4K(hid) && usage->hid == (HID_UP_MSVENDOR | 0xff05)) { + int key = 0; + static int last_key = 0; + switch (value) { + case 0x01: key = BTN_1; break; + case 0x02: key = BTN_2; break; + case 0x04: key = BTN_3; break; + case 0x08: key = BTN_4; break; + case 0x10: key = BTN_5; break; + default: break; + } + if (key) { + input_event(input, usage->type, key, 1); + last_key = key; + } else { + input_event(input, usage->type, last_key, 0); + } + } + + /* report the usage code as scancode if the key status has changed */ + if (usage->type == EV_KEY && !!test_bit(usage->code, input->key) != value) + input_event(input, EV_MSC, MSC_SCAN, usage->hid); + input_event(input, usage->type, usage->code, value); if ((field->flags & HID_MAIN_ITEM_RELATIVE) && (usage->type == EV_KEY)) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c new file mode 100644 index 0000000..20ebba1 --- /dev/null +++ b/drivers/hid/hidraw.c @@ -0,0 +1,395 @@ +/* + * HID raw devices, giving access to raw HID events. + * + * In comparison to hiddev, this device does not process the + * hid events at all (no parsing, no lookups). This lets applications + * to work on raw hid events as they want to, and avoids a need to + * use a transport-specific userspace libhid/libusb libraries. + * + * Copyright (c) 2007 Jiri Kosina + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int hidraw_major; +static struct cdev hidraw_cdev; +static struct class *hidraw_class; +static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES]; +static DEFINE_SPINLOCK(minors_lock); + +static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct hidraw_list *list = file->private_data; + int ret = 0, len; + char *report; + DECLARE_WAITQUEUE(wait, current); + + while (ret == 0) { + + mutex_lock(&list->read_mutex); + + if (list->head == list->tail) { + add_wait_queue(&list->hidraw->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (list->head == list->tail) { + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + if (!list->hidraw->exist) { + ret = -EIO; + break; + } + + /* allow O_NONBLOCK to work well from other threads */ + mutex_unlock(&list->read_mutex); + schedule(); + mutex_lock(&list->read_mutex); + set_current_state(TASK_INTERRUPTIBLE); + } + + set_current_state(TASK_RUNNING); + remove_wait_queue(&list->hidraw->wait, &wait); + } + + if (ret) + goto out; + + report = list->buffer[list->tail].value; + len = list->buffer[list->tail].len > count ? + count : list->buffer[list->tail].len; + + if (copy_to_user(buffer, list->buffer[list->tail].value, len)) { + ret = -EFAULT; + goto out; + } + ret += len; + + kfree(list->buffer[list->tail].value); + list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); + } +out: + mutex_unlock(&list->read_mutex); + return ret; +} + +/* the first byte is expected to be a report number */ +static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + unsigned int minor = iminor(file->f_path.dentry->d_inode); + struct hid_device *dev = hidraw_table[minor]->hid; + __u8 *buf; + int ret = 0; + + if (!dev->hid_output_raw_report) + return -ENODEV; + + if (count > HID_MIN_BUFFER_SIZE) { + printk(KERN_WARNING "hidraw: pid %d passed too large report\n", + current->pid); + return -EINVAL; + } + + if (count < 2) { + printk(KERN_WARNING "hidraw: pid %d passed too short report\n", + current->pid); + return -EINVAL; + } + + buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, buffer, count)) { + ret = -EFAULT; + goto out; + } + + ret = dev->hid_output_raw_report(dev, buf, count); +out: + kfree(buf); + return ret; +} + +static unsigned int hidraw_poll(struct file *file, poll_table *wait) +{ + struct hidraw_list *list = file->private_data; + + poll_wait(file, &list->hidraw->wait, wait); + if (list->head != list->tail) + return POLLIN | POLLRDNORM; + if (!list->hidraw->exist) + return POLLERR | POLLHUP; + return 0; +} + +static int hidraw_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct hidraw *dev; + struct hidraw_list *list; + int err = 0; + + if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { + err = -ENOMEM; + goto out; + } + + spin_lock(&minors_lock); + if (!hidraw_table[minor]) { + printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", + minor); + kfree(list); + err = -ENODEV; + goto out_unlock; + } + + list->hidraw = hidraw_table[minor]; + mutex_init(&list->read_mutex); + list_add_tail(&list->node, &hidraw_table[minor]->list); + file->private_data = list; + + dev = hidraw_table[minor]; + if (!dev->open++) + dev->hid->hid_open(dev->hid); + +out_unlock: + spin_unlock(&minors_lock); +out: + return err; + +} + +static int hidraw_release(struct inode * inode, struct file * file) +{ + unsigned int minor = iminor(inode); + struct hidraw *dev; + struct hidraw_list *list = file->private_data; + + if (!hidraw_table[minor]) { + printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", + minor); + return -ENODEV; + } + + list_del(&list->node); + dev = hidraw_table[minor]; + if (!dev->open--) { + if (list->hidraw->exist) + dev->hid->hid_close(dev->hid); + else + kfree(list->hidraw); + } + + return 0; +} + +static int hidraw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned int minor = iminor(inode); + struct hidraw *dev = hidraw_table[minor]; + void __user *user_arg = (void __user*) arg; + + switch (cmd) { + case HIDIOCGRDESCSIZE: + if (put_user(dev->hid->rsize, (int __user *)arg)) + return -EFAULT; + return 0; + + case HIDIOCGRDESC: + { + __u32 len; + + if (get_user(len, (int __user *)arg)) + return -EFAULT; + if (copy_to_user(*((__u8 **)(user_arg + + sizeof(__u32))), + dev->hid->rdesc, len)) + return -EFAULT; + return 0; + } + case HIDIOCGRAWINFO: + { + struct hidraw_devinfo dinfo; + + dinfo.bustype = dev->hid->bus; + dinfo.vendor = dev->hid->vendor; + dinfo.product = dev->hid->product; + if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) + return -EFAULT; + + return 0; + } + default: + printk(KERN_EMERG "hidraw: unsupported ioctl() %x\n", + cmd); + } + return -EINVAL; +} + +static const struct file_operations hidraw_ops = { + .owner = THIS_MODULE, + .read = hidraw_read, + .write = hidraw_write, + .poll = hidraw_poll, + .open = hidraw_open, + .release = hidraw_release, + .ioctl = hidraw_ioctl, +}; + +void hidraw_report_event(struct hid_device *hid, u8 *data, int len) +{ + struct hidraw *dev = hid->hidraw; + struct hidraw_list *list; + + list_for_each_entry(list, &dev->list, node) { + list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC); + list->buffer[list->head].len = len; + list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); + kill_fasync(&list->fasync, SIGIO, POLL_IN); + } + + wake_up_interruptible(&dev->wait); +} +EXPORT_SYMBOL_GPL(hidraw_report_event); + +int hidraw_connect(struct hid_device *hid) +{ + int minor, result = -EINVAL; + struct hidraw *dev; + + /* TODO currently we accept any HID device. This should later + * probably be fixed to accept only those devices which provide + * non-input applications + */ + + if (!(dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL))) + return -1; + + spin_lock(&minors_lock); + + for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) { + if (hidraw_table[minor]) + continue; + hidraw_table[minor] = dev; + result = 0; + break; + } + + spin_unlock(&minors_lock); + + if (result) + goto out; + + dev->dev = device_create(hidraw_class, NULL, MKDEV(hidraw_major, minor), + "%s%d", "hidraw", minor); + + if (IS_ERR(dev->dev)) { + spin_lock(&minors_lock); + hidraw_table[minor] = NULL; + spin_unlock(&minors_lock); + result = PTR_ERR(dev->dev); + goto out; + } + + init_waitqueue_head(&dev->wait); + INIT_LIST_HEAD(&dev->list); + + dev->hid = hid; + dev->minor = minor; + + dev->exist = 1; + hid->hidraw = dev; + +out: + return result; + +} +EXPORT_SYMBOL_GPL(hidraw_connect); + +void hidraw_disconnect(struct hid_device *hid) +{ + struct hidraw *hidraw = hid->hidraw; + + hidraw->exist = 0; + + spin_lock(&minors_lock); + hidraw_table[hidraw->minor] = NULL; + spin_unlock(&minors_lock); + + device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); + + if (hidraw->open) { + hid->hid_close(hid); + wake_up_interruptible(&hidraw->wait); + } else { + kfree(hidraw); + } +} +EXPORT_SYMBOL_GPL(hidraw_disconnect); + +int __init hidraw_init(void) +{ + int result; + dev_t dev_id; + + result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR, + HIDRAW_MAX_DEVICES, "hidraw"); + + hidraw_major = MAJOR(dev_id); + + if (result < 0) { + printk(KERN_WARNING "hidraw: can't get major number\n"); + result = 0; + goto out; + } + + hidraw_class = class_create(THIS_MODULE, "hidraw"); + if (IS_ERR(hidraw_class)) { + result = PTR_ERR(hidraw_class); + unregister_chrdev(hidraw_major, "hidraw"); + goto out; + } + + cdev_init(&hidraw_cdev, &hidraw_ops); + cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); +out: + return result; +} + +void __exit hidraw_exit(void) +{ + dev_t dev_id = MKDEV(hidraw_major, 0); + + cdev_del(&hidraw_cdev); + class_destroy(hidraw_class); + unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); + +} diff --git a/drivers/hid/usbhid/Kconfig b/drivers/hid/usbhid/Kconfig index 1b4b572..b27023f 100644 --- a/drivers/hid/usbhid/Kconfig +++ b/drivers/hid/usbhid/Kconfig @@ -79,11 +79,12 @@ config PANTHERLORD_FF to enable force feedback support for it. config THRUSTMASTER_FF - bool "ThrustMaster FireStorm Dual Power 2 support (EXPERIMENTAL)" + bool "ThrustMaster devices support (EXPERIMENTAL)" depends on HID_FF && EXPERIMENTAL select INPUT_FF_MEMLESS if USB_HID help - Say Y here if you have a THRUSTMASTER FireStore Dual Power 2, + Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or + a THRUSTMASTER Ferrari GT Rumble Force or Force Feedback Wheel, and want to enable force feedback support for it. Note: if you say N here, this device will still be supported, but without force feedback. diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 0a1f2b5..b38e559 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -32,6 +32,7 @@ #include #include #include #include +#include #include "usbhid.h" /* @@ -512,7 +513,16 @@ static int hid_get_class_descriptor(stru int usbhid_open(struct hid_device *hid) { - ++hid->open; + 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; @@ -522,8 +532,10 @@ void usbhid_close(struct hid_device *hid { struct usbhid_device *usbhid = hid->driver_data; - if (!--hid->open) + if (!--hid->open) { usb_kill_urb(usbhid->urbin); + usb_autopm_put_interface(usbhid->intf); + } } /* @@ -628,6 +640,28 @@ static int hid_alloc_buffers(struct usb_ 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, + cpu_to_le16(((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; @@ -871,6 +905,7 @@ #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: @@ -909,6 +944,8 @@ static void hid_disconnect(struct usb_in 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); @@ -941,11 +978,13 @@ static int hid_probe(struct usb_interfac 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 not claimed by input or hiddev\n"); + printk ("HID device claimed by neither input, hiddev nor hidraw\n"); hid_disconnect(intf); return -ENODEV; } @@ -961,10 +1000,16 @@ static int hid_probe(struct usb_interfac if (hid->claimed & HID_CLAIMED_INPUT) printk("input"); - if (hid->claimed == (HID_CLAIMED_INPUT | HID_CLAIMED_HIDDEV)) + 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++) { @@ -1048,6 +1093,7 @@ static struct usb_driver hid_driver = { .pre_reset = hid_pre_reset, .post_reset = hid_post_reset, .id_table = hid_usb_ids, + .supports_autosuspend = 1, }; static int __init hid_init(void) diff --git a/drivers/hid/usbhid/hid-ff.c b/drivers/hid/usbhid/hid-ff.c index 23431fb..5dacd8e 100644 --- a/drivers/hid/usbhid/hid-ff.c +++ b/drivers/hid/usbhid/hid-ff.c @@ -67,6 +67,8 @@ #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 }, diff --git a/drivers/hid/usbhid/hid-tmff.c b/drivers/hid/usbhid/hid-tmff.c index 555bb48..69882a7 100644 --- a/drivers/hid/usbhid/hid-tmff.c +++ b/drivers/hid/usbhid/hid-tmff.c @@ -36,16 +36,39 @@ #include #include "usbhid.h" /* Usages for thrustmaster devices I know about */ -#define THRUSTMASTER_USAGE_RUMBLE_LR (HID_UP_GENDESK | 0xbb) +#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 *rumble; + struct hid_field *ff_field; }; /* Changes values from 0 to 0xffff into values from minimum to maximum */ -static inline int hid_tmff_scale(unsigned int in, int minimum, int maximum) +static inline int hid_tmff_scale_u16(unsigned int in, + int minimum, int maximum) { int ret; @@ -57,22 +80,57 @@ static inline int hid_tmff_scale(unsigne 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 */ - left = hid_tmff_scale(effect->u.rumble.weak_magnitude, - tmff->rumble->logical_minimum, tmff->rumble->logical_maximum); - right = hid_tmff_scale(effect->u.rumble.strong_magnitude, - tmff->rumble->logical_minimum, tmff->rumble->logical_maximum); - - tmff->rumble->value[0] = left; - tmff->rumble->value[1] = right; - dbg_hid("(left,right)=(%08x, %08x)\n", left, right); - usbhid_submit_report(hid, tmff->report, USB_DIR_OUT); - + 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; } @@ -82,14 +140,16 @@ int hid_tmff_init(struct hid_device *hid struct list_head *pos; 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 */ - __list_for_each(pos, &hid->report_enum[HID_OUTPUT_REPORT].report_list) { + list_for_each(pos, &hid->report_enum[HID_OUTPUT_REPORT].report_list) { struct hid_report *report = (struct hid_report *)pos; int fieldnum; @@ -100,48 +160,65 @@ int hid_tmff_init(struct hid_device *hid continue; switch (field->usage[0].hid) { - case THRUSTMASTER_USAGE_RUMBLE_LR: - if (field->report_count < 2) { - warn("ignoring THRUSTMASTER_USAGE_RUMBLE_LR with report_count < 2"); - continue; - } + 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 THRUSTMASTER_USAGE_RUMBLE_LR with logical_maximum == logical_minimum"); - 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 THRUSTMASTER_USAGE_RUMBLE_LR in other report"); - continue; - } + if (tmff->report && tmff->report != report) { + warn("ignoring FF field in other report"); + continue; + } - if (tmff->rumble && tmff->rumble != field) { - warn("ignoring duplicate THRUSTMASTER_USAGE_RUMBLE_LR"); - 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; } + } - tmff->report = report; - tmff->rumble = field; + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], input_dev->ffbit); - set_bit(FF_RUMBLE, input_dev->ffbit); - break; + break; - default: - warn("ignoring unknown output usage %08x", field->usage[0].hid); - continue; + default: + warn("ignoring unknown output usage %08x", field->usage[0].hid); + continue; } } } - error = input_ff_create_memless(input_dev, tmff, hid_tmff_play); - if (error) { - kfree(tmff); - return error; + if (!tmff->report) { + err("cant find FF field in output reports\n"); + error = -ENODEV; + goto fail; } - info("Force feedback for ThrustMaster rumble pad devices by Zinx Verituse "); + 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/include/linux/hid.h b/include/linux/hid.h index 898103b..0ac2b52 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -403,6 +403,7 @@ struct hid_control_fifo { #define HID_CLAIMED_INPUT 1 #define HID_CLAIMED_HIDDEV 2 +#define HID_CLAIMED_HIDRAW 4 #define HID_CTRL_RUNNING 1 #define HID_OUT_RUNNING 2 @@ -438,6 +439,7 @@ struct hid_device { /* device repo struct list_head inputs; /* The list of inputs */ void *hiddev; /* The hiddev structure */ + void *hidraw; int minor; /* Hiddev minor number */ wait_queue_head_t wait; /* For sleeping */ @@ -458,6 +460,9 @@ struct hid_device { /* device repo void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field, struct hid_usage *, __s32); void (*hiddev_report_event) (struct hid_device *, struct hid_report *); + + /* handler for raw output data, used by hidraw */ + int (*hid_output_raw_report) (struct hid_device *, __u8 *, size_t); #ifdef CONFIG_USB_HIDINPUT_POWERBOOK unsigned long pb_pressed_fn[NBITS(KEY_MAX)]; unsigned long pb_pressed_numlock[NBITS(KEY_MAX)]; diff --git a/include/linux/hidraw.h b/include/linux/hidraw.h new file mode 100644 index 0000000..6676cd5 --- /dev/null +++ b/include/linux/hidraw.h @@ -0,0 +1,86 @@ +#ifndef _HIDRAW_H +#define _HIDRAW_H + +/* + * Copyright (c) 2007 Jiri Kosina + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +struct hidraw_report_descriptor { + __u32 size; + __u8 *value; +}; + +struct hidraw_devinfo { + __u32 bustype; + __s16 vendor; + __s16 product; +}; + +/* ioctl interface */ +#define HIDIOCGRDESCSIZE _IOR('H', 0x01, int) +#define HIDIOCGRDESC _IOR('H', 0x02, struct hidraw_report_descriptor) +#define HIDIOCGRAWINFO _IOR('H', 0x03, struct hidraw_devinfo) + +#define HIDRAW_FIRST_MINOR 0 +#define HIDRAW_MAX_DEVICES 64 +/* number of reports to buffer */ +#define HIDRAW_BUFFER_SIZE 64 + + +/* kernel-only API declarations */ +#ifdef __KERNEL__ + +#include + +struct hidraw { + unsigned int minor; + int exist; + int open; + wait_queue_head_t wait; + struct hid_device *hid; + struct device *dev; + struct list_head list; +}; + +struct hidraw_report { + __u8 *value; + int len; +}; + +struct hidraw_list { + struct hidraw_report buffer[HIDRAW_BUFFER_SIZE]; + int head; + int tail; + struct fasync_struct *fasync; + struct hidraw *hidraw; + struct list_head node; + struct mutex read_mutex; +}; + +#ifdef CONFIG_HIDRAW +int hidraw_init(void); +void hidraw_exit(void); +void hidraw_report_event(struct hid_device *, u8 *, int); +int hidraw_connect(struct hid_device *); +void hidraw_disconnect(struct hid_device *); +#else +static inline int hidraw_init(void) { return 0; } +static inline void hidraw_exit(void) { } +static inline void hidraw_report_event(struct hid_device *hid, u8 *data, int len) { } +static inline int hidraw_connect(struct hid_device *hid) { return -1; } +static inline void hidraw_disconnect(struct hid_device *hid) { } +#endif + +#endif + +#endif diff --git a/include/linux/input.h b/include/linux/input.h index e02c6a6..331de2f 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -516,6 +516,7 @@ #define KEY_VOICEMAIL 0x1ac /* AL Voice #define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ #define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ #define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ #define KEY_DEL_EOL 0x1c0 #define KEY_DEL_EOS 0x1c1