Commit 52d4e661 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (21 commits)
  HID: hidraw_connect() memleak fix
  HID: add hidraw interface
  USB HID: provide hook for hidraw write()
  HID: hiddev: Add 32bit ioctl compatibilty
  HID: Add GeneralTouch touchscreen to the blacklist
  HID: add support for Microsoft Wireless Laser Keyboard 6000
  Input: add KEY_LOGOFF
  USBHID: report descriptor fix for MacBook JIS keyboard
  HID: trivial fixes in hid-debug
  HID: fix input mapping for Microsoft Ergonomic Keyboard
  HID: use hid-plff driver for GreenAsia 0e8f:0003 devices
  USBHID: Add HID_QUIRK_NOGET for ELO Touch Screen 2700 display
  HID: enable hiddev for the SantaRosa MacBookPro IR receiver
  USBHID: add CM109 device to blacklist
  HID: Report usage codes of keys as EV_MSC scancode events
  HID: ignore all non-LED usages in output fields in hid-input
  HID: fix whitespace damage
  HID: add support for Thrustmaster FGT Force Feedback wheel
  HID: minimal autosuspend support for USB HID devices
  HID: add support for Microsoft Natural Ergonomic Keyboard 4000
  ...
parents f248488b d057fd4c
......@@ -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
......@@ -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/
......
......@@ -30,6 +30,7 @@
#include <linux/hid.h>
#include <linux/hiddev.h>
#include <linux/hid-debug.h>
#include <linux/hidraw.h>
/*
* Version Information
......@@ -979,6 +980,8 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
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 *hid, int type, u8 *data, int size, int i
}
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);
......@@ -34,7 +34,7 @@
struct hid_usage_entry {
unsigned page;
unsigned usage;
char *description;
const char *description;
};
static const struct hid_usage_entry hid_usage_table[] = {
......@@ -365,8 +365,8 @@ void hid_resolv_usage(unsigned usage) {
}
EXPORT_SYMBOL_GPL(hid_resolv_usage);
__inline__ static void tab(int n) {
while (n--) printk(" ");
static void tab(int n) {
printk(KERN_DEBUG "%*s", n, "");
}
void hid_dump_field(struct hid_field *field, int n) {
......@@ -401,8 +401,8 @@ void hid_dump_field(struct hid_field *field, int n) {
tab(n); printk("Unit Exponent(%d)\n", field->unit_exponent);
}
if (field->unit) {
char *systems[5] = { "None", "SI Linear", "SI Rotation", "English Linear", "English Rotation" };
char *units[5][8] = {
static const char *systems[5] = { "None", "SI Linear", "SI Rotation", "English Linear", "English Rotation" };
static const char *units[5][8] = {
{ "None", "None", "None", "None", "None", "None", "None", "None" },
{ "None", "Centimeter", "Gram", "Seconds", "Kelvin", "Ampere", "Candela", "None" },
{ "None", "Radians", "Gram", "Seconds", "Kelvin", "Ampere", "Candela", "None" },
......@@ -457,7 +457,7 @@ void hid_dump_field(struct hid_field *field, int n) {
printk("%s", HID_MAIN_ITEM_RELATIVE & j ? "Relative " : "Absolute ");
printk("%s", HID_MAIN_ITEM_WRAP & j ? "Wrap " : "");
printk("%s", HID_MAIN_ITEM_NONLINEAR & j ? "NonLinear " : "");
printk("%s", HID_MAIN_ITEM_NO_PREFERRED & j ? "NoPrefferedState " : "");
printk("%s", HID_MAIN_ITEM_NO_PREFERRED & j ? "NoPreferredState " : "");
printk("%s", HID_MAIN_ITEM_NULL_STATE & j ? "NullState " : "");
printk("%s", HID_MAIN_ITEM_VOLATILE & j ? "Volatile " : "");
printk("%s", HID_MAIN_ITEM_BUFFERED_BYTE & j ? "BufferedByte " : "");
......@@ -470,7 +470,7 @@ void hid_dump_device(struct hid_device *device) {
struct hid_report *report;
struct list_head *list;
unsigned i,k;
static char *table[] = {"INPUT", "OUTPUT", "FEATURE"};
static const char *table[] = {"INPUT", "OUTPUT", "FEATURE"};
if (!hid_debug)
return;
......@@ -501,13 +501,13 @@ void hid_dump_input(struct hid_usage *usage, __s32 value) {
if (!hid_debug)
return;
printk("hid-debug: input ");
printk(KERN_DEBUG "hid-debug: input ");
hid_resolv_usage(usage->hid);
printk(" = %d\n", value);
}
EXPORT_SYMBOL_GPL(hid_dump_input);
static char *events[EV_MAX + 1] = {
static const char *events[EV_MAX + 1] = {
[EV_SYN] = "Sync", [EV_KEY] = "Key",
[EV_REL] = "Relative", [EV_ABS] = "Absolute",
[EV_MSC] = "Misc", [EV_LED] = "LED",
......@@ -516,10 +516,10 @@ static char *events[EV_MAX + 1] = {
[EV_FF_STATUS] = "ForceFeedbackStatus",
};
static char *syncs[2] = {
static const char *syncs[2] = {
[SYN_REPORT] = "Report", [SYN_CONFIG] = "Config",
};
static char *keys[KEY_MAX + 1] = {
static const char *keys[KEY_MAX + 1] = {
[KEY_RESERVED] = "Reserved", [KEY_ESC] = "Esc",
[KEY_1] = "1", [KEY_2] = "2",
[KEY_3] = "3", [KEY_4] = "4",
......@@ -697,7 +697,8 @@ 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_LOGOFF] = "Logoff",
[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",
......@@ -715,7 +716,7 @@ static char *keys[KEY_MAX + 1] = {
[KEY_SWITCHVIDEOMODE] = "SwitchVideoMode",
};
static char *relatives[REL_MAX + 1] = {
static const char *relatives[REL_MAX + 1] = {
[REL_X] = "X", [REL_Y] = "Y",
[REL_Z] = "Z", [REL_RX] = "Rx",
[REL_RY] = "Ry", [REL_RZ] = "Rz",
......@@ -723,7 +724,7 @@ static char *relatives[REL_MAX + 1] = {
[REL_WHEEL] = "Wheel", [REL_MISC] = "Misc",
};
static char *absolutes[ABS_MAX + 1] = {
static const char *absolutes[ABS_MAX + 1] = {
[ABS_X] = "X", [ABS_Y] = "Y",
[ABS_Z] = "Z", [ABS_RX] = "Rx",
[ABS_RY] = "Ry", [ABS_RZ] = "Rz",
......@@ -739,12 +740,12 @@ static char *absolutes[ABS_MAX + 1] = {
[ABS_VOLUME] = "Volume", [ABS_MISC] = "Misc",
};
static char *misc[MSC_MAX + 1] = {
static const char *misc[MSC_MAX + 1] = {
[MSC_SERIAL] = "Serial", [MSC_PULSELED] = "Pulseled",
[MSC_GESTURE] = "Gesture", [MSC_RAW] = "RawData"
};
static char *leds[LED_MAX + 1] = {
static const char *leds[LED_MAX + 1] = {
[LED_NUML] = "NumLock", [LED_CAPSL] = "CapsLock",
[LED_SCROLLL] = "ScrollLock", [LED_COMPOSE] = "Compose",
[LED_KANA] = "Kana", [LED_SLEEP] = "Sleep",
......@@ -752,16 +753,16 @@ static char *leds[LED_MAX + 1] = {
[LED_MISC] = "Misc",
};
static char *repeats[REP_MAX + 1] = {
static const char *repeats[REP_MAX + 1] = {
[REP_DELAY] = "Delay", [REP_PERIOD] = "Period"
};
static char *sounds[SND_MAX + 1] = {
static const char *sounds[SND_MAX + 1] = {
[SND_CLICK] = "Click", [SND_BELL] = "Bell",
[SND_TONE] = "Tone"
};
static char **names[EV_MAX + 1] = {
static const char **names[EV_MAX + 1] = {
[EV_SYN] = syncs, [EV_KEY] = keys,
[EV_REL] = relatives, [EV_ABS] = absolutes,
[EV_MSC] = misc, [EV_LED] = leds,
......@@ -777,4 +778,3 @@ void hid_resolv_event(__u8 type, __u16 code) {
names[type] ? (names[type][code] ? names[type][code] : "?") : "?");
}
EXPORT_SYMBOL_GPL(hid_resolv_event);
......@@ -53,7 +53,7 @@ static const unsigned char hid_keyboard[256] = {
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 @@ static const struct {
#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_KB(x) (x->vendor == 0x045e && (x->product == 0x00db || x->product == 0x00f9))
#ifdef CONFIG_USB_HIDINPUT_POWERBOOK
struct hidinput_key_translation {
......@@ -295,7 +299,7 @@ static int hidinput_getkeycode(struct input_dev *dev, int scancode,
{
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 input_dev *dev, int scancode,
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 input_dev *dev, int scancode,
* 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(struct hid_input *hidinput, struct hid_fiel
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:
......@@ -595,6 +606,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x0f6: map_key_clear(KEY_NEXT); break;
case 0x0fa: map_key_clear(KEY_BACK); break;
case 0x182: map_key_clear(KEY_BOOKMARKS); break;
case 0x183: map_key_clear(KEY_CONFIG); break;
case 0x184: map_key_clear(KEY_WORDPROCESSOR); break;
case 0x185: map_key_clear(KEY_EDITOR); break;
......@@ -611,9 +623,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x192: map_key_clear(KEY_CALC); break;
case 0x194: map_key_clear(KEY_FILE); break;
case 0x196: map_key_clear(KEY_WWW); break;
case 0x19c: map_key_clear(KEY_LOGOFF); break;
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 0x1b6: map_key_clear(KEY_MEDIA); break;
case 0x1b7: map_key_clear(KEY_SOUND); 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 +736,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
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 +760,26 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0xff0b: map_key_clear(BTN_B); break;
default: goto ignore;
}
/* Microsoft Natural Ergonomic Keyboard 4000 */
} else if (IS_MS_KB(device)) {
switch(usage->hid & HID_USAGE) {
case 0xfd06:
map_key_clear(KEY_CHAT);
break;
case 0xfd07:
map_key_clear(KEY_PHONE);
break;
case 0xff05:
set_bit(EV_REP, input->evbit);
map_key_clear(KEY_F13);
set_bit(KEY_F14, input->keybit);
set_bit(KEY_F15, input->keybit);
set_bit(KEY_F16, input->keybit);
set_bit(KEY_F17, input->keybit);
set_bit(KEY_F18, input->keybit);
default: goto ignore;
}
} else {
goto ignore;
}
......@@ -888,6 +931,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
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 +1039,29 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return;
}
/* Handling MS keyboards special buttons */
if (IS_MS_KB(hid) && usage->hid == (HID_UP_MSVENDOR | 0xff05)) {
int key = 0;
static int last_key = 0;
switch (value) {
case 0x01: key = KEY_F14; break;
case 0x02: key = KEY_F15; break;
case 0x04: key = KEY_F16; break;
case 0x08: key = KEY_F17; break;
case 0x10: key = KEY_F18; 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))
......@@ -1051,6 +1122,9 @@ int hidinput_connect(struct hid_device *hid)
int i, j, k;
int max_report_type = HID_OUTPUT_REPORT;
if (hid->quirks & HID_QUIRK_IGNORE_HIDINPUT)
return -1;
INIT_LIST_HEAD(&hid->inputs);
for (i = 0; i < hid->maxcollection; i++)
......
/*
* 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 <linux/fs.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/major.h>
#include <linux/hid.h>
#include <linux/mutex.h>
#include <linux/hidraw.h>
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;
}