WSL2-Linux-Kernel/drivers/hid/usbhid/hiddev.c

946 строки
22 KiB
C
Исходник Обычный вид История

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2001 Paul Stewart
* Copyright (c) 2001 Vojtech Pavlik
*
* HID char devices, giving access to raw HID device events.
*/
/*
*
* Should you need to contact me, the author, you can do so either by
* e-mail - mail your message to Paul Stewart <stewart@wetlogic.net>
*/
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/hiddev.h>
#include <linux/compat.h>
#include <linux/vmalloc.h>
#include <linux/nospec.h>
#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 2048
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;
struct mutex thread_lock;
};
/*
* 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,
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 17:55:46 +04:00
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)
{
struct hiddev_list *list = file->private_data;
return fasync_helper(fd, file, on, &list->fasync);
}
/*
* release file op
*/
static int hiddev_release(struct inode * inode, struct file * file)
{
struct hiddev_list *list = file->private_data;
unsigned long flags;
spin_lock_irqsave(&list->hiddev->list_lock, flags);
list_del(&list->node);
spin_unlock_irqrestore(&list->hiddev->list_lock, flags);
mutex_lock(&list->hiddev->existancelock);
if (!--list->hiddev->open) {
if (list->hiddev->exist) {
hid_hw_close(list->hiddev->hid);
hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL);
} else {
mutex_unlock(&list->hiddev->existancelock);
kfree(list->hiddev);
vfree(list);
return 0;
}
}
mutex_unlock(&list->hiddev->existancelock);
vfree(list);
return 0;
}
static int __hiddev_open(struct hiddev *hiddev, struct file *file)
{
struct hiddev_list *list;
int error;
lockdep_assert_held(&hiddev->existancelock);
list = vzalloc(sizeof(*list));
if (!list)
return -ENOMEM;
mutex_init(&list->thread_lock);
list->hiddev = hiddev;
if (!hiddev->open++) {
error = hid_hw_power(hiddev->hid, PM_HINT_FULLON);
if (error < 0)
goto err_drop_count;
error = hid_hw_open(hiddev->hid);
if (error < 0)
goto err_normal_power;
}
spin_lock_irq(&hiddev->list_lock);
list_add_tail(&list->node, &hiddev->list);
spin_unlock_irq(&hiddev->list_lock);
file->private_data = list;
return 0;
err_normal_power:
hid_hw_power(hiddev->hid, PM_HINT_NORMAL);
err_drop_count:
hiddev->open--;
vfree(list);
return error;
}
/*
* open file op
*/
static int hiddev_open(struct inode *inode, struct file *file)
{
struct usb_interface *intf;
struct hid_device *hid;
struct hiddev *hiddev;
int res;
intf = usbhid_find_interface(iminor(inode));
if (!intf)
return -ENODEV;
hid = usb_get_intfdata(intf);
hiddev = hid->hiddev;
mutex_lock(&hiddev->existancelock);
res = hiddev->exist ? __hiddev_open(hiddev, file) : -ENODEV;
mutex_unlock(&hiddev->existancelock);
return res;
}
/*
* "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)
{
DEFINE_WAIT(wait);
struct hiddev_list *list = file->private_data;
int event_size;
int retval;
event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ?
sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event);
if (count < event_size)
return 0;
/* lock against other threads */
retval = mutex_lock_interruptible(&list->thread_lock);
if (retval)
return -ERESTARTSYS;
while (retval == 0) {
if (list->head == list->tail) {
prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE);
while (list->head == list->tail) {
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (!list->hiddev->exist) {
retval = -EIO;
break;
}
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
/* let O_NONBLOCK tasks run */
mutex_unlock(&list->thread_lock);
schedule();
if (mutex_lock_interruptible(&list->thread_lock)) {
finish_wait(&list->hiddev->wait, &wait);
return -EINTR;
}
set_current_state(TASK_INTERRUPTIBLE);
}
finish_wait(&list->hiddev->wait, &wait);
}
if (retval) {
mutex_unlock(&list->thread_lock);
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))) {
mutex_unlock(&list->thread_lock);
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))) {
mutex_unlock(&list->thread_lock);
return -EFAULT;
}
retval += sizeof(struct hiddev_usage_ref);
}
}
list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
}
}
mutex_unlock(&list->thread_lock);
return retval;
}
/*
* "poll" file op
* No kernel lock - fine
*/
static __poll_t 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 EPOLLIN | EPOLLRDNORM | EPOLLOUT;
if (!list->hiddev->exist)
return EPOLLERR | EPOLLHUP;
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;
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;
uref->field_index = array_index_nospec(uref->field_index,
report->maxfield);
field = report->field[uref->field_index];
if (uref->usage_index >= field->maxusage)
goto inval;
uref->usage_index = array_index_nospec(uref->usage_index,
field->maxusage);
uref->usage_code = field->usage[uref->usage_index].hid;
if (copy_to_user(user_arg, uref, sizeof(*uref)))
goto fault;
goto goodreturn;
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;
uref->field_index = array_index_nospec(uref->field_index,
report->maxfield);
field = report->field[uref->field_index];
if (cmd == HIDIOCGCOLLECTIONINDEX) {
if (uref->usage_index >= field->maxusage)
goto inval;
uref->usage_index =
array_index_nospec(uref->usage_index,
field->maxusage);
} else if (uref->usage_index >= field->report_count)
goto inval;
}
if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
if (uref_multi->num_values > HID_MAX_MULTI_USAGES ||
uref->usage_index + uref_multi->num_values >
field->report_count)
goto inval;
uref->usage_index =
array_index_nospec(uref->usage_index,
field->report_count -
uref_multi->num_values);
}
switch (cmd) {
case HIDIOCGUSAGE:
if (uref->usage_index >= field->report_count)
goto inval;
uref->value = field->value[uref->usage_index];
if (copy_to_user(user_arg, uref, sizeof(*uref)))
goto fault;
goto goodreturn;
case HIDIOCSUSAGE:
if (uref->usage_index >= field->report_count)
goto inval;
field->value[uref->usage_index] = uref->value;
goto goodreturn;
case HIDIOCGCOLLECTIONINDEX:
i = field->usage[uref->usage_index].collection_index;
kfree(uref_multi);
return i;
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:
kfree(uref_multi);
return 0;
fault:
kfree(uref_multi);
return -EFAULT;
inval:
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;
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;
void __user *user_arg = (void __user *)arg;
int i, r = -EINVAL;
/* Called without BKL by compat methods so no BKL taken */
mutex_lock(&hiddev->existancelock);
if (!hiddev->exist) {
r = -ENODEV;
goto ret_unlock;
}
hid = hiddev->hid;
switch (cmd) {
case HIDIOCGVERSION:
r = put_user(HID_VERSION, (int __user *)arg) ?
-EFAULT : 0;
break;
case HIDIOCAPPLICATION:
if (arg >= hid->maxapplication)
break;
for (i = 0; i < hid->maxcollection; i++)
if (hid->collection[i].type ==
HID_COLLECTION_APPLICATION && arg-- == 0)
break;
if (i < hid->maxcollection)
r = hid->collection[i].usage;
break;
case HIDIOCGDEVINFO:
{
struct usb_device *dev = hid_to_usb_dev(hid);
struct usbhid_device *usbhid = hid->driver_data;
memset(&dinfo, 0, sizeof(dinfo));
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;
r = copy_to_user(user_arg, &dinfo, sizeof(dinfo)) ?
-EFAULT : 0;
break;
}
case HIDIOCGFLAG:
r = put_user(list->flags, (int __user *)arg) ?
-EFAULT : 0;
break;
case HIDIOCSFLAG:
{
int newflags;
if (get_user(newflags, (int __user *)arg)) {
r = -EFAULT;
break;
}
if ((newflags & ~HIDDEV_FLAGS) != 0 ||
((newflags & HIDDEV_FLAG_REPORT) != 0 &&
(newflags & HIDDEV_FLAG_UREF) == 0))
break;
list->flags = newflags;
r = 0;
break;
}
case HIDIOCGSTRING:
r = hiddev_ioctl_string(hiddev, cmd, user_arg);
break;
case HIDIOCINITREPORT:
usbhid_init_reports(hid);
HID: remove initial reading of reports at connect It looks like a bunch of devices do not like to be polled for their reports at init time. When you look into the details, it seems that for those that are requiring the quirk HID_QUIRK_NO_INIT_REPORTS, the driver fails to retrieve part of the features/inputs while others (more generic) work. IMO, it should be acceptable to remove the need for the quirk in the general case. On the small amount of cases where we actually need to read the current values, the driver in charge (hid-mt or wacom) already retrieves the features manually. There are 2 cases where we might need to retrieve the reports at init: 1. hiddev devices with specific use-space tool 2. a device that would require the driver to fetch a specific feature/input at plug For case 2, I have seen this a few time on hid-multitouch. It is solved in hid-multitouch directly by fetching the feature. I hope it won't be too common and this can be solved on a per-case basis (crossing fingers). For case 1, we moved the implementation of HID_QUIRK_NO_INIT_REPORTS in hiddev. When somebody starts calling ioctls that needs an initial update, the hiddev device will fetch the initial state of the reports to mimic the current behavior. This adds a small amount of time during the first HIDIOCGUSAGE(S), but it should be acceptable in most cases. To keep the currently known broken devices, we have to keep around HID_QUIRK_NO_INIT_REPORTS, but the scope will only be for hiddev. Note that I don't think hidraw would be affected and I checked that the FF drivers that need to interact with the report fields are all using output reports, which are not initialized by usbhid_init_reports(). NO_INIT_INPUT_REPORTS is then replaced by HID_QUIRK_NO_INIT_REPORTS: there is no point keeping it for just one device. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2017-03-08 17:11:14 +03:00
hiddev->initialized = true;
r = 0;
break;
case HIDIOCGREPORT:
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
r = -EFAULT;
break;
}
if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
break;
report = hiddev_lookup_report(hid, &rinfo);
if (report == NULL)
break;
hid_hw_request(hid, report, HID_REQ_GET_REPORT);
hid_hw_wait(hid);
r = 0;
break;
case HIDIOCSREPORT:
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
r = -EFAULT;
break;
}
if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
break;
report = hiddev_lookup_report(hid, &rinfo);
if (report == NULL)
break;
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
hid_hw_wait(hid);
r = 0;
break;
case HIDIOCGREPORTINFO:
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
r = -EFAULT;
break;
}
report = hiddev_lookup_report(hid, &rinfo);
if (report == NULL)
break;
rinfo.num_fields = report->maxfield;
r = copy_to_user(user_arg, &rinfo, sizeof(rinfo)) ?
-EFAULT : 0;
break;
case HIDIOCGFIELDINFO:
if (copy_from_user(&finfo, user_arg, sizeof(finfo))) {
r = -EFAULT;
break;
}
rinfo.report_type = finfo.report_type;
rinfo.report_id = finfo.report_id;
report = hiddev_lookup_report(hid, &rinfo);
if (report == NULL)
break;
if (finfo.field_index >= report->maxfield)
break;
finfo.field_index = array_index_nospec(finfo.field_index,
report->maxfield);
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;
r = copy_to_user(user_arg, &finfo, sizeof(finfo)) ?
-EFAULT : 0;
break;
case HIDIOCGUCODE:
case HIDIOCGUSAGE:
case HIDIOCSUSAGE:
case HIDIOCGUSAGES:
case HIDIOCSUSAGES:
case HIDIOCGCOLLECTIONINDEX:
HID: remove initial reading of reports at connect It looks like a bunch of devices do not like to be polled for their reports at init time. When you look into the details, it seems that for those that are requiring the quirk HID_QUIRK_NO_INIT_REPORTS, the driver fails to retrieve part of the features/inputs while others (more generic) work. IMO, it should be acceptable to remove the need for the quirk in the general case. On the small amount of cases where we actually need to read the current values, the driver in charge (hid-mt or wacom) already retrieves the features manually. There are 2 cases where we might need to retrieve the reports at init: 1. hiddev devices with specific use-space tool 2. a device that would require the driver to fetch a specific feature/input at plug For case 2, I have seen this a few time on hid-multitouch. It is solved in hid-multitouch directly by fetching the feature. I hope it won't be too common and this can be solved on a per-case basis (crossing fingers). For case 1, we moved the implementation of HID_QUIRK_NO_INIT_REPORTS in hiddev. When somebody starts calling ioctls that needs an initial update, the hiddev device will fetch the initial state of the reports to mimic the current behavior. This adds a small amount of time during the first HIDIOCGUSAGE(S), but it should be acceptable in most cases. To keep the currently known broken devices, we have to keep around HID_QUIRK_NO_INIT_REPORTS, but the scope will only be for hiddev. Note that I don't think hidraw would be affected and I checked that the FF drivers that need to interact with the report fields are all using output reports, which are not initialized by usbhid_init_reports(). NO_INIT_INPUT_REPORTS is then replaced by HID_QUIRK_NO_INIT_REPORTS: there is no point keeping it for just one device. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2017-03-08 17:11:14 +03:00
if (!hiddev->initialized) {
usbhid_init_reports(hid);
hiddev->initialized = true;
}
r = hiddev_ioctl_usage(hiddev, cmd, user_arg);
break;
case HIDIOCGCOLLECTIONINFO:
if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) {
r = -EFAULT;
break;
}
if (cinfo.index >= hid->maxcollection)
break;
cinfo.index = array_index_nospec(cinfo.index,
hid->maxcollection);
cinfo.type = hid->collection[cinfo.index].type;
cinfo.usage = hid->collection[cinfo.index].usage;
cinfo.level = hid->collection[cinfo.index].level;
r = copy_to_user(user_arg, &cinfo, sizeof(cinfo)) ?
-EFAULT : 0;
break;
default:
if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
break;
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
int len = strlen(hid->name) + 1;
if (len > _IOC_SIZE(cmd))
len = _IOC_SIZE(cmd);
r = copy_to_user(user_arg, hid->name, len) ?
-EFAULT : len;
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) {
int len = strlen(hid->phys) + 1;
if (len > _IOC_SIZE(cmd))
len = _IOC_SIZE(cmd);
r = copy_to_user(user_arg, hid->phys, len) ?
-EFAULT : len;
break;
}
}
ret_unlock:
mutex_unlock(&hiddev->existancelock);
return r;
}
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,
.compat_ioctl = compat_ptr_ioctl,
llseek: automatically add .llseek fop All file_operations should get a .llseek operation so we can make nonseekable_open the default for future file operations without a .llseek pointer. The three cases that we can automatically detect are no_llseek, seq_lseek and default_llseek. For cases where we can we can automatically prove that the file offset is always ignored, we use noop_llseek, which maintains the current behavior of not returning an error from a seek. New drivers should normally not use noop_llseek but instead use no_llseek and call nonseekable_open at open time. Existing drivers can be converted to do the same when the maintainer knows for certain that no user code relies on calling seek on the device file. The generated code is often incorrectly indented and right now contains comments that clarify for each added line why a specific variant was chosen. In the version that gets submitted upstream, the comments will be gone and I will manually fix the indentation, because there does not seem to be a way to do that using coccinelle. Some amount of new code is currently sitting in linux-next that should get the same modifications, which I will do at the end of the merge window. Many thanks to Julia Lawall for helping me learn to write a semantic patch that does all this. ===== begin semantic patch ===== // This adds an llseek= method to all file operations, // as a preparation for making no_llseek the default. // // The rules are // - use no_llseek explicitly if we do nonseekable_open // - use seq_lseek for sequential files // - use default_llseek if we know we access f_pos // - use noop_llseek if we know we don't access f_pos, // but we still want to allow users to call lseek // @ open1 exists @ identifier nested_open; @@ nested_open(...) { <+... nonseekable_open(...) ...+> } @ open exists@ identifier open_f; identifier i, f; identifier open1.nested_open; @@ int open_f(struct inode *i, struct file *f) { <+... ( nonseekable_open(...) | nested_open(...) ) ...+> } @ read disable optional_qualifier exists @ identifier read_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; expression E; identifier func; @@ ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off) { <+... ( *off = E | *off += E | func(..., off, ...) | E = *off ) ...+> } @ read_no_fpos disable optional_qualifier exists @ identifier read_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; @@ ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off) { ... when != off } @ write @ identifier write_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; expression E; identifier func; @@ ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off) { <+... ( *off = E | *off += E | func(..., off, ...) | E = *off ) ...+> } @ write_no_fpos @ identifier write_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; @@ ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off) { ... when != off } @ fops0 @ identifier fops; @@ struct file_operations fops = { ... }; @ has_llseek depends on fops0 @ identifier fops0.fops; identifier llseek_f; @@ struct file_operations fops = { ... .llseek = llseek_f, ... }; @ has_read depends on fops0 @ identifier fops0.fops; identifier read_f; @@ struct file_operations fops = { ... .read = read_f, ... }; @ has_write depends on fops0 @ identifier fops0.fops; identifier write_f; @@ struct file_operations fops = { ... .write = write_f, ... }; @ has_open depends on fops0 @ identifier fops0.fops; identifier open_f; @@ struct file_operations fops = { ... .open = open_f, ... }; // use no_llseek if we call nonseekable_open //////////////////////////////////////////// @ nonseekable1 depends on !has_llseek && has_open @ identifier fops0.fops; identifier nso ~= "nonseekable_open"; @@ struct file_operations fops = { ... .open = nso, ... +.llseek = no_llseek, /* nonseekable */ }; @ nonseekable2 depends on !has_llseek @ identifier fops0.fops; identifier open.open_f; @@ struct file_operations fops = { ... .open = open_f, ... +.llseek = no_llseek, /* open uses nonseekable */ }; // use seq_lseek for sequential files ///////////////////////////////////// @ seq depends on !has_llseek @ identifier fops0.fops; identifier sr ~= "seq_read"; @@ struct file_operations fops = { ... .read = sr, ... +.llseek = seq_lseek, /* we have seq_read */ }; // use default_llseek if there is a readdir /////////////////////////////////////////// @ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier readdir_e; @@ // any other fop is used that changes pos struct file_operations fops = { ... .readdir = readdir_e, ... +.llseek = default_llseek, /* readdir is present */ }; // use default_llseek if at least one of read/write touches f_pos ///////////////////////////////////////////////////////////////// @ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read.read_f; @@ // read fops use offset struct file_operations fops = { ... .read = read_f, ... +.llseek = default_llseek, /* read accesses f_pos */ }; @ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier write.write_f; @@ // write fops use offset struct file_operations fops = { ... .write = write_f, ... + .llseek = default_llseek, /* write accesses f_pos */ }; // Use noop_llseek if neither read nor write accesses f_pos /////////////////////////////////////////////////////////// @ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read_no_fpos.read_f; identifier write_no_fpos.write_f; @@ // write fops use offset struct file_operations fops = { ... .write = write_f, .read = read_f, ... +.llseek = noop_llseek, /* read and write both use no f_pos */ }; @ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier write_no_fpos.write_f; @@ struct file_operations fops = { ... .write = write_f, ... +.llseek = noop_llseek, /* write uses no f_pos */ }; @ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read_no_fpos.read_f; @@ struct file_operations fops = { ... .read = read_f, ... +.llseek = noop_llseek, /* read uses no f_pos */ }; @ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; @@ struct file_operations fops = { ... +.llseek = noop_llseek, /* no read or write fn */ }; ===== End semantic patch ===== Signed-off-by: Arnd Bergmann <arnd@arndb.de> Cc: Julia Lawall <julia@diku.dk> Cc: Christoph Hellwig <hch@infradead.org>
2010-08-15 20:52:59 +04:00
.llseek = noop_llseek,
};
static char *hiddev_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
}
static struct usb_class_driver hiddev_class = {
.name = "hiddev%d",
.devnode = hiddev_devnode,
.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, unsigned int force)
{
struct hiddev *hiddev;
struct usbhid_device *usbhid = hid->driver_data;
int retval;
if (!force) {
unsigned int i;
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)
return -EINVAL;
}
if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
return -ENOMEM;
init_waitqueue_head(&hiddev->wait);
INIT_LIST_HEAD(&hiddev->list);
spin_lock_init(&hiddev->list_lock);
mutex_init(&hiddev->existancelock);
hid->hiddev = hiddev;
hiddev->hid = hid;
hiddev->exist = 1;
retval = usb_register_dev(usbhid->intf, &hiddev_class);
if (retval) {
hid_err(hid, "Not able to get a minor for this device\n");
hid->hiddev = NULL;
kfree(hiddev);
return retval;
}
HID: remove initial reading of reports at connect It looks like a bunch of devices do not like to be polled for their reports at init time. When you look into the details, it seems that for those that are requiring the quirk HID_QUIRK_NO_INIT_REPORTS, the driver fails to retrieve part of the features/inputs while others (more generic) work. IMO, it should be acceptable to remove the need for the quirk in the general case. On the small amount of cases where we actually need to read the current values, the driver in charge (hid-mt or wacom) already retrieves the features manually. There are 2 cases where we might need to retrieve the reports at init: 1. hiddev devices with specific use-space tool 2. a device that would require the driver to fetch a specific feature/input at plug For case 2, I have seen this a few time on hid-multitouch. It is solved in hid-multitouch directly by fetching the feature. I hope it won't be too common and this can be solved on a per-case basis (crossing fingers). For case 1, we moved the implementation of HID_QUIRK_NO_INIT_REPORTS in hiddev. When somebody starts calling ioctls that needs an initial update, the hiddev device will fetch the initial state of the reports to mimic the current behavior. This adds a small amount of time during the first HIDIOCGUSAGE(S), but it should be acceptable in most cases. To keep the currently known broken devices, we have to keep around HID_QUIRK_NO_INIT_REPORTS, but the scope will only be for hiddev. Note that I don't think hidraw would be affected and I checked that the FF drivers that need to interact with the report fields are all using output reports, which are not initialized by usbhid_init_reports(). NO_INIT_INPUT_REPORTS is then replaced by HID_QUIRK_NO_INIT_REPORTS: there is no point keeping it for just one device. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2017-03-08 17:11:14 +03:00
/*
* If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize
* the reports.
*/
hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS;
hiddev->minor = usbhid->intf->minor;
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;
HID: usbhid: fix dead lock between open and disconect There is no reason to hold hiddev->existancelock before calling usb_deregister_dev, so move it out of the lock. The patch fixes the lockdep warning below. [ 5733.386271] ====================================================== [ 5733.386274] [ INFO: possible circular locking dependency detected ] [ 5733.386278] 3.2.0-custom-next-20120111+ #1 Not tainted [ 5733.386281] ------------------------------------------------------- [ 5733.386284] khubd/186 is trying to acquire lock: [ 5733.386288] (minor_rwsem){++++.+}, at: [<ffffffffa0011a04>] usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386311] [ 5733.386312] but task is already holding lock: [ 5733.386315] (&hiddev->existancelock){+.+...}, at: [<ffffffffa0094d17>] hiddev_disconnect+0x26/0x87 [usbhid] [ 5733.386328] [ 5733.386329] which lock already depends on the new lock. [ 5733.386330] [ 5733.386333] [ 5733.386334] the existing dependency chain (in reverse order) is: [ 5733.386336] [ 5733.386337] -> #1 (&hiddev->existancelock){+.+...}: [ 5733.386346] [<ffffffff81082d26>] lock_acquire+0xcb/0x10e [ 5733.386357] [<ffffffff813df961>] __mutex_lock_common+0x60/0x465 [ 5733.386366] [<ffffffff813dfe4d>] mutex_lock_nested+0x36/0x3b [ 5733.386371] [<ffffffffa0094ad6>] hiddev_open+0x113/0x193 [usbhid] [ 5733.386378] [<ffffffffa0011971>] usb_open+0x66/0xc2 [usbcore] [ 5733.386390] [<ffffffff8111a8b5>] chrdev_open+0x12b/0x154 [ 5733.386402] [<ffffffff811159a8>] __dentry_open.isra.16+0x20b/0x355 [ 5733.386408] [<ffffffff811165dc>] nameidata_to_filp+0x43/0x4a [ 5733.386413] [<ffffffff81122ed5>] do_last+0x536/0x570 [ 5733.386419] [<ffffffff8112300b>] path_openat+0xce/0x301 [ 5733.386423] [<ffffffff81123327>] do_filp_open+0x33/0x81 [ 5733.386427] [<ffffffff8111664d>] do_sys_open+0x6a/0xfc [ 5733.386431] [<ffffffff811166fb>] sys_open+0x1c/0x1e [ 5733.386434] [<ffffffff813e7c79>] system_call_fastpath+0x16/0x1b [ 5733.386441] [ 5733.386441] -> #0 (minor_rwsem){++++.+}: [ 5733.386448] [<ffffffff8108255d>] __lock_acquire+0xa80/0xd74 [ 5733.386454] [<ffffffff81082d26>] lock_acquire+0xcb/0x10e [ 5733.386458] [<ffffffff813e01f5>] down_write+0x44/0x77 [ 5733.386464] [<ffffffffa0011a04>] usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386475] [<ffffffffa0094d2d>] hiddev_disconnect+0x3c/0x87 [usbhid] [ 5733.386483] [<ffffffff8132df51>] hid_disconnect+0x3f/0x54 [ 5733.386491] [<ffffffff8132dfb4>] hid_device_remove+0x4e/0x7a [ 5733.386496] [<ffffffff812c0957>] __device_release_driver+0x81/0xcd [ 5733.386502] [<ffffffff812c09c3>] device_release_driver+0x20/0x2d [ 5733.386507] [<ffffffff812c0564>] bus_remove_device+0x114/0x128 [ 5733.386512] [<ffffffff812bdd6f>] device_del+0x131/0x183 [ 5733.386519] [<ffffffff8132def3>] hid_destroy_device+0x1e/0x3d [ 5733.386525] [<ffffffffa00916b0>] usbhid_disconnect+0x36/0x42 [usbhid] [ 5733.386530] [<ffffffffa000fb60>] usb_unbind_interface+0x57/0x11f [usbcore] [ 5733.386542] [<ffffffff812c0957>] __device_release_driver+0x81/0xcd [ 5733.386547] [<ffffffff812c09c3>] device_release_driver+0x20/0x2d [ 5733.386552] [<ffffffff812c0564>] bus_remove_device+0x114/0x128 [ 5733.386557] [<ffffffff812bdd6f>] device_del+0x131/0x183 [ 5733.386562] [<ffffffffa000de61>] usb_disable_device+0xa8/0x1d8 [usbcore] [ 5733.386573] [<ffffffffa0006bd2>] usb_disconnect+0xab/0x11f [usbcore] [ 5733.386583] [<ffffffffa0008aa0>] hub_thread+0x73b/0x1157 [usbcore] [ 5733.386593] [<ffffffff8105dc0f>] kthread+0x95/0x9d [ 5733.386601] [<ffffffff813e90b4>] kernel_thread_helper+0x4/0x10 [ 5733.386607] [ 5733.386608] other info that might help us debug this: [ 5733.386609] [ 5733.386612] Possible unsafe locking scenario: [ 5733.386613] [ 5733.386615] CPU0 CPU1 [ 5733.386618] ---- ---- [ 5733.386620] lock(&hiddev->existancelock); [ 5733.386625] lock(minor_rwsem); [ 5733.386630] lock(&hiddev->existancelock); [ 5733.386635] lock(minor_rwsem); [ 5733.386639] [ 5733.386640] *** DEADLOCK *** [ 5733.386641] [ 5733.386644] 6 locks held by khubd/186: [ 5733.386646] #0: (&__lockdep_no_validate__){......}, at: [<ffffffffa00084af>] hub_thread+0x14a/0x1157 [usbcore] [ 5733.386661] #1: (&__lockdep_no_validate__){......}, at: [<ffffffffa0006b77>] usb_disconnect+0x50/0x11f [usbcore] [ 5733.386677] #2: (hcd->bandwidth_mutex){+.+.+.}, at: [<ffffffffa0006bc8>] usb_disconnect+0xa1/0x11f [usbcore] [ 5733.386693] #3: (&__lockdep_no_validate__){......}, at: [<ffffffff812c09bb>] device_release_driver+0x18/0x2d [ 5733.386704] #4: (&__lockdep_no_validate__){......}, at: [<ffffffff812c09bb>] device_release_driver+0x18/0x2d [ 5733.386714] #5: (&hiddev->existancelock){+.+...}, at: [<ffffffffa0094d17>] hiddev_disconnect+0x26/0x87 [usbhid] [ 5733.386727] [ 5733.386727] stack backtrace: [ 5733.386731] Pid: 186, comm: khubd Not tainted 3.2.0-custom-next-20120111+ #1 [ 5733.386734] Call Trace: [ 5733.386741] [<ffffffff81062881>] ? up+0x34/0x3b [ 5733.386747] [<ffffffff813d9ef3>] print_circular_bug+0x1f8/0x209 [ 5733.386752] [<ffffffff8108255d>] __lock_acquire+0xa80/0xd74 [ 5733.386756] [<ffffffff810808b4>] ? trace_hardirqs_on_caller+0x15d/0x1a3 [ 5733.386763] [<ffffffff81043a3f>] ? vprintk+0x3f4/0x419 [ 5733.386774] [<ffffffffa0011a04>] ? usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386779] [<ffffffff81082d26>] lock_acquire+0xcb/0x10e [ 5733.386789] [<ffffffffa0011a04>] ? usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386797] [<ffffffff813e01f5>] down_write+0x44/0x77 [ 5733.386807] [<ffffffffa0011a04>] ? usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386818] [<ffffffffa0011a04>] usb_deregister_dev+0x37/0x9e [usbcore] [ 5733.386825] [<ffffffffa0094d2d>] hiddev_disconnect+0x3c/0x87 [usbhid] [ 5733.386830] [<ffffffff8132df51>] hid_disconnect+0x3f/0x54 [ 5733.386834] [<ffffffff8132dfb4>] hid_device_remove+0x4e/0x7a [ 5733.386839] [<ffffffff812c0957>] __device_release_driver+0x81/0xcd [ 5733.386844] [<ffffffff812c09c3>] device_release_driver+0x20/0x2d [ 5733.386848] [<ffffffff812c0564>] bus_remove_device+0x114/0x128 [ 5733.386854] [<ffffffff812bdd6f>] device_del+0x131/0x183 [ 5733.386859] [<ffffffff8132def3>] hid_destroy_device+0x1e/0x3d [ 5733.386865] [<ffffffffa00916b0>] usbhid_disconnect+0x36/0x42 [usbhid] [ 5733.386876] [<ffffffffa000fb60>] usb_unbind_interface+0x57/0x11f [usbcore] [ 5733.386882] [<ffffffff812c0957>] __device_release_driver+0x81/0xcd [ 5733.386886] [<ffffffff812c09c3>] device_release_driver+0x20/0x2d [ 5733.386890] [<ffffffff812c0564>] bus_remove_device+0x114/0x128 [ 5733.386895] [<ffffffff812bdd6f>] device_del+0x131/0x183 [ 5733.386905] [<ffffffffa000de61>] usb_disable_device+0xa8/0x1d8 [usbcore] [ 5733.386916] [<ffffffffa0006bd2>] usb_disconnect+0xab/0x11f [usbcore] [ 5733.386921] [<ffffffff813dff82>] ? __mutex_unlock_slowpath+0x130/0x141 [ 5733.386929] [<ffffffffa0008aa0>] hub_thread+0x73b/0x1157 [usbcore] [ 5733.386935] [<ffffffff8106a51d>] ? finish_task_switch+0x78/0x150 [ 5733.386941] [<ffffffff8105e396>] ? __init_waitqueue_head+0x4c/0x4c [ 5733.386950] [<ffffffffa0008365>] ? usb_remote_wakeup+0x56/0x56 [usbcore] [ 5733.386955] [<ffffffff8105dc0f>] kthread+0x95/0x9d [ 5733.386961] [<ffffffff813e90b4>] kernel_thread_helper+0x4/0x10 [ 5733.386966] [<ffffffff813e24b8>] ? retint_restore_args+0x13/0x13 [ 5733.386970] [<ffffffff8105db7a>] ? __init_kthread_worker+0x55/0x55 [ 5733.386974] [<ffffffff813e90b0>] ? gs_change+0x13/0x13 Signed-off-by: Ming Lei <ming.lei@canonical.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2012-01-12 13:42:22 +04:00
usb_deregister_dev(usbhid->intf, &hiddev_class);
mutex_lock(&hiddev->existancelock);
hiddev->exist = 0;
if (hiddev->open) {
hid_hw_close(hiddev->hid);
wake_up_interruptible(&hiddev->wait);
mutex_unlock(&hiddev->existancelock);
} else {
mutex_unlock(&hiddev->existancelock);
kfree(hiddev);
}
}