2019-05-27 13:55:05 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* 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>
|
2017-02-03 01:15:33 +07:00
|
|
|
#include <linux/sched/signal.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <linux/usb.h>
|
2006-12-09 00:40:44 +07:00
|
|
|
#include <linux/hid.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/hiddev.h>
|
2007-10-14 17:03:58 +07:00
|
|
|
#include <linux/compat.h>
|
2012-04-27 01:16:00 +07:00
|
|
|
#include <linux/vmalloc.h>
|
2018-06-30 05:08:44 +07:00
|
|
|
#include <linux/nospec.h>
|
2006-12-09 00:40:44 +07:00
|
|
|
#include "usbhid.h"
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
#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
|
2009-08-20 17:04:14 +07:00
|
|
|
#define HIDDEV_BUFFER_SIZE 2048
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
struct hiddev_list {
|
|
|
|
struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE];
|
|
|
|
int head;
|
|
|
|
int tail;
|
|
|
|
unsigned flags;
|
|
|
|
struct fasync_struct *fasync;
|
|
|
|
struct hiddev *hiddev;
|
2006-07-19 12:09:10 +07:00
|
|
|
struct list_head node;
|
2008-12-16 16:55:15 +07:00
|
|
|
struct mutex thread_lock;
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)
|
|
|
|
{
|
2006-07-19 12:09:10 +07:00
|
|
|
unsigned int flags = rinfo->report_id & ~HID_REPORT_ID_MASK;
|
|
|
|
unsigned int rid = rinfo->report_id & HID_REPORT_ID_MASK;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct hid_report_enum *report_enum;
|
2006-07-19 12:09:10 +07:00
|
|
|
struct hid_report *report;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct list_head *list;
|
|
|
|
|
|
|
|
if (rinfo->report_type < HID_REPORT_TYPE_MIN ||
|
2006-07-19 12:09:10 +07:00
|
|
|
rinfo->report_type > HID_REPORT_TYPE_MAX)
|
|
|
|
return NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
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:
|
2006-07-19 12:09:10 +07:00
|
|
|
if (list_empty(&report_enum->report_list))
|
2005-04-17 05:20:36 +07:00
|
|
|
return NULL;
|
2006-07-19 12:09:10 +07:00
|
|
|
|
|
|
|
list = report_enum->report_list.next;
|
|
|
|
report = list_entry(list, struct hid_report, list);
|
|
|
|
rinfo->report_id = report->id;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
2005-05-29 14:29:01 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
case HID_REPORT_ID_NEXT:
|
2006-07-19 12:09:10 +07:00
|
|
|
report = report_enum->report_id_hash[rid];
|
|
|
|
if (!report)
|
2005-04-17 05:20:36 +07:00
|
|
|
return NULL;
|
2006-07-19 12:09:10 +07:00
|
|
|
|
|
|
|
list = report->list.next;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (list == &report_enum->report_list)
|
|
|
|
return NULL;
|
2006-07-19 12:09:10 +07:00
|
|
|
|
|
|
|
report = list_entry(list, struct hid_report, list);
|
|
|
|
rinfo->report_id = report->id;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
2005-05-29 14:29:01 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
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 ||
|
2006-07-19 12:09:10 +07:00
|
|
|
uref->report_type > HID_REPORT_TYPE_MAX)
|
|
|
|
return NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
report_enum = hid->report_enum +
|
|
|
|
(uref->report_type - HID_REPORT_TYPE_MIN);
|
|
|
|
|
2006-07-19 12:09:10 +07:00
|
|
|
list_for_each_entry(report, &report_enum->report_list, list) {
|
2005-04-17 05:20:36 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-07-19 12:09:10 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hiddev_send_event(struct hid_device *hid,
|
|
|
|
struct hiddev_usage_ref *uref)
|
|
|
|
{
|
|
|
|
struct hiddev *hiddev = hid->hiddev;
|
2006-07-19 12:09:10 +07:00
|
|
|
struct hiddev_list *list;
|
2007-05-10 13:45:56 +07:00
|
|
|
unsigned long flags;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-05-10 13:45:56 +07:00
|
|
|
spin_lock_irqsave(&hiddev->list_lock, flags);
|
2006-07-19 12:09:10 +07:00
|
|
|
list_for_each_entry(list, &hiddev->list, node) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (uref->field_index != HID_FIELD_INDEX_NONE ||
|
|
|
|
(list->flags & HIDDEV_FLAG_REPORT) != 0) {
|
|
|
|
list->buffer[list->head] = *uref;
|
2005-05-29 14:29:01 +07:00
|
|
|
list->head = (list->head + 1) &
|
2005-04-17 05:20:36 +07:00
|
|
|
(HIDDEV_BUFFER_SIZE - 1);
|
|
|
|
kill_fasync(&list->fasync, SIGIO, POLL_IN);
|
|
|
|
}
|
|
|
|
}
|
2007-05-10 13:45:56 +07:00
|
|
|
spin_unlock_irqrestore(&hiddev->list_lock, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
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 20:55:46 +07:00
|
|
|
struct hid_usage *usage, __s32 value)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned type = field->report_type;
|
|
|
|
struct hiddev_usage_ref uref;
|
|
|
|
|
2005-05-29 14:29:01 +07:00
|
|
|
uref.report_type =
|
2005-04-17 05:20:36 +07:00
|
|
|
(type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
|
2005-05-29 14:29:01 +07:00
|
|
|
((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
|
2006-07-19 12:09:10 +07:00
|
|
|
((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
|
2005-04-17 05:20:36 +07:00
|
|
|
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);
|
|
|
|
}
|
2006-12-09 00:40:53 +07:00
|
|
|
EXPORT_SYMBOL_GPL(hiddev_hid_event);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
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));
|
2005-05-29 14:29:01 +07:00
|
|
|
uref.report_type =
|
2005-04-17 05:20:36 +07:00
|
|
|
(type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
|
2005-05-29 14:29:01 +07:00
|
|
|
((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
|
2006-07-19 12:09:10 +07:00
|
|
|
((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
|
2005-04-17 05:20:36 +07:00
|
|
|
uref.report_id = report->id;
|
|
|
|
uref.field_index = HID_FIELD_INDEX_NONE;
|
|
|
|
|
|
|
|
hiddev_send_event(hid, &uref);
|
|
|
|
}
|
2006-12-09 00:41:17 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* fasync file op
|
|
|
|
*/
|
|
|
|
static int hiddev_fasync(int fd, struct file *file, int on)
|
|
|
|
{
|
|
|
|
struct hiddev_list *list = file->private_data;
|
2006-07-19 12:09:10 +07:00
|
|
|
|
2009-02-02 04:52:56 +07:00
|
|
|
return fasync_helper(fd, file, on, &list->fasync);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* release file op
|
|
|
|
*/
|
|
|
|
static int hiddev_release(struct inode * inode, struct file * file)
|
|
|
|
{
|
|
|
|
struct hiddev_list *list = file->private_data;
|
2007-05-10 13:45:56 +07:00
|
|
|
unsigned long flags;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-05-10 13:45:56 +07:00
|
|
|
spin_lock_irqsave(&list->hiddev->list_lock, flags);
|
2006-07-19 12:09:10 +07:00
|
|
|
list_del(&list->node);
|
2007-05-10 13:45:56 +07:00
|
|
|
spin_unlock_irqrestore(&list->hiddev->list_lock, flags);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-05-20 15:50:13 +07:00
|
|
|
mutex_lock(&list->hiddev->existancelock);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!--list->hiddev->open) {
|
2008-12-17 21:38:03 +07:00
|
|
|
if (list->hiddev->exist) {
|
2017-06-07 13:59:31 +07:00
|
|
|
hid_hw_close(list->hiddev->hid);
|
2017-06-07 13:59:32 +07:00
|
|
|
hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL);
|
2008-12-17 21:38:03 +07:00
|
|
|
} else {
|
2011-05-26 15:49:16 +07:00
|
|
|
mutex_unlock(&list->hiddev->existancelock);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(list->hiddev);
|
2012-04-27 01:16:00 +07:00
|
|
|
vfree(list);
|
2011-05-26 15:49:16 +07:00
|
|
|
return 0;
|
2008-12-17 21:38:03 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2011-05-20 15:50:13 +07:00
|
|
|
mutex_unlock(&list->hiddev->existancelock);
|
2012-04-27 01:16:00 +07:00
|
|
|
vfree(list);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* open file op
|
|
|
|
*/
|
2006-07-19 12:09:10 +07:00
|
|
|
static int hiddev_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
2005-04-17 05:20:36 +07:00
|
|
|
struct hiddev_list *list;
|
2010-07-11 20:34:05 +07:00
|
|
|
struct usb_interface *intf;
|
2010-08-13 17:19:45 +07:00
|
|
|
struct hid_device *hid;
|
2010-07-11 20:34:05 +07:00
|
|
|
struct hiddev *hiddev;
|
|
|
|
int res;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-09-13 02:32:35 +07:00
|
|
|
intf = usbhid_find_interface(iminor(inode));
|
2010-07-11 20:34:05 +07:00
|
|
|
if (!intf)
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENODEV;
|
2010-08-13 17:19:45 +07:00
|
|
|
hid = usb_get_intfdata(intf);
|
|
|
|
hiddev = hid->hiddev;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-04-27 01:16:00 +07:00
|
|
|
if (!(list = vzalloc(sizeof(struct hiddev_list))))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
2008-12-16 16:55:15 +07:00
|
|
|
mutex_init(&list->thread_lock);
|
2010-07-11 20:34:05 +07:00
|
|
|
list->hiddev = hiddev;
|
2005-04-17 05:20:36 +07:00
|
|
|
file->private_data = list;
|
|
|
|
|
2008-12-16 16:55:15 +07:00
|
|
|
/*
|
|
|
|
* no need for locking because the USB major number
|
|
|
|
* is shared which usbcore guards against disconnect
|
|
|
|
*/
|
|
|
|
if (list->hiddev->exist) {
|
|
|
|
if (!list->hiddev->open++) {
|
2017-06-07 13:59:31 +07:00
|
|
|
res = hid_hw_open(hiddev->hid);
|
|
|
|
if (res < 0)
|
2008-12-16 16:55:15 +07:00
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res = -ENODEV;
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irq(&list->hiddev->list_lock);
|
2010-07-11 20:34:05 +07:00
|
|
|
list_add_tail(&list->node, &hiddev->list);
|
2008-12-16 16:55:15 +07:00
|
|
|
spin_unlock_irq(&list->hiddev->list_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2011-05-20 15:50:13 +07:00
|
|
|
mutex_lock(&hiddev->existancelock);
|
2019-08-06 15:38:58 +07:00
|
|
|
/*
|
|
|
|
* recheck exist with existance lock held to
|
|
|
|
* avoid opening a disconnected device
|
|
|
|
*/
|
|
|
|
if (!list->hiddev->exist) {
|
|
|
|
res = -ENODEV;
|
|
|
|
goto bail_unlock;
|
|
|
|
}
|
2008-12-17 21:38:03 +07:00
|
|
|
if (!list->hiddev->open++)
|
|
|
|
if (list->hiddev->exist) {
|
2010-07-11 20:34:05 +07:00
|
|
|
struct hid_device *hid = hiddev->hid;
|
2017-06-07 13:59:32 +07:00
|
|
|
res = hid_hw_power(hid, PM_HINT_FULLON);
|
|
|
|
if (res < 0)
|
2011-05-20 15:50:13 +07:00
|
|
|
goto bail_unlock;
|
2017-06-07 13:59:31 +07:00
|
|
|
res = hid_hw_open(hid);
|
|
|
|
if (res < 0)
|
2017-06-07 13:59:32 +07:00
|
|
|
goto bail_normal_power;
|
2008-12-17 21:38:03 +07:00
|
|
|
}
|
2011-05-20 15:50:13 +07:00
|
|
|
mutex_unlock(&hiddev->existancelock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2017-06-07 13:59:32 +07:00
|
|
|
bail_normal_power:
|
|
|
|
hid_hw_power(hid, PM_HINT_NORMAL);
|
2011-05-20 15:50:13 +07:00
|
|
|
bail_unlock:
|
|
|
|
mutex_unlock(&hiddev->existancelock);
|
2019-08-06 15:40:15 +07:00
|
|
|
|
|
|
|
spin_lock_irq(&list->hiddev->list_lock);
|
|
|
|
list_del(&list->node);
|
|
|
|
spin_unlock_irq(&list->hiddev->list_lock);
|
2008-12-16 16:55:15 +07:00
|
|
|
bail:
|
|
|
|
file->private_data = NULL;
|
2012-04-27 01:16:00 +07:00
|
|
|
vfree(list);
|
2008-12-16 16:55:15 +07:00
|
|
|
return res;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "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)
|
|
|
|
{
|
2009-03-11 04:44:01 +07:00
|
|
|
DEFINE_WAIT(wait);
|
2005-04-17 05:20:36 +07:00
|
|
|
struct hiddev_list *list = file->private_data;
|
|
|
|
int event_size;
|
2008-12-16 16:55:15 +07:00
|
|
|
int retval;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ?
|
|
|
|
sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event);
|
|
|
|
|
|
|
|
if (count < event_size)
|
|
|
|
return 0;
|
|
|
|
|
2008-12-16 16:55:15 +07:00
|
|
|
/* lock against other threads */
|
|
|
|
retval = mutex_lock_interruptible(&list->thread_lock);
|
|
|
|
if (retval)
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
while (retval == 0) {
|
|
|
|
if (list->head == list->tail) {
|
2008-12-16 16:55:15 +07:00
|
|
|
prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE);
|
2005-05-29 14:29:01 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
while (list->head == list->tail) {
|
|
|
|
if (signal_pending(current)) {
|
|
|
|
retval = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!list->hiddev->exist) {
|
|
|
|
retval = -EIO;
|
|
|
|
break;
|
|
|
|
}
|
2012-11-28 06:10:44 +07:00
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
retval = -EAGAIN;
|
|
|
|
break;
|
|
|
|
}
|
2005-05-29 14:29:01 +07:00
|
|
|
|
2008-12-16 16:55:15 +07:00
|
|
|
/* let O_NONBLOCK tasks run */
|
|
|
|
mutex_unlock(&list->thread_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
schedule();
|
2011-04-29 01:53:58 +07:00
|
|
|
if (mutex_lock_interruptible(&list->thread_lock)) {
|
|
|
|
finish_wait(&list->hiddev->wait, &wait);
|
2008-12-16 16:55:15 +07:00
|
|
|
return -EINTR;
|
2011-04-29 01:53:58 +07:00
|
|
|
}
|
2006-05-21 04:59:59 +07:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-12-16 16:55:15 +07:00
|
|
|
finish_wait(&list->hiddev->wait, &wait);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2008-12-16 16:55:15 +07:00
|
|
|
if (retval) {
|
|
|
|
mutex_unlock(&list->thread_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return retval;
|
2008-12-16 16:55:15 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
|
2005-05-29 14:29:01 +07:00
|
|
|
while (list->head != list->tail &&
|
2005-04-17 05:20:36 +07:00
|
|
|
retval + event_size <= count) {
|
|
|
|
if ((list->flags & HIDDEV_FLAG_UREF) == 0) {
|
2008-12-16 16:55:15 +07:00
|
|
|
if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE) {
|
2005-04-17 05:20:36 +07:00
|
|
|
struct hiddev_event event;
|
2008-12-16 16:55:15 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
event.hid = list->buffer[list->tail].usage_code;
|
|
|
|
event.value = list->buffer[list->tail].value;
|
2008-12-16 16:55:15 +07:00
|
|
|
if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) {
|
|
|
|
mutex_unlock(&list->thread_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EFAULT;
|
2008-12-16 16:55:15 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
retval += sizeof(struct hiddev_event);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE ||
|
|
|
|
(list->flags & HIDDEV_FLAG_REPORT) != 0) {
|
2008-12-16 16:55:15 +07:00
|
|
|
|
|
|
|
if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) {
|
|
|
|
mutex_unlock(&list->thread_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EFAULT;
|
2008-12-16 16:55:15 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
retval += sizeof(struct hiddev_usage_ref);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2008-12-16 16:55:15 +07:00
|
|
|
mutex_unlock(&list->thread_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "poll" file op
|
|
|
|
* No kernel lock - fine
|
|
|
|
*/
|
2017-07-03 17:39:46 +07:00
|
|
|
static __poll_t hiddev_poll(struct file *file, poll_table *wait)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct hiddev_list *list = file->private_data;
|
2006-07-19 12:09:10 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
poll_wait(file, &list->hiddev->wait, wait);
|
|
|
|
if (list->head != list->tail)
|
2019-07-19 03:50:58 +07:00
|
|
|
return EPOLLIN | EPOLLRDNORM | EPOLLOUT;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!list->hiddev->exist)
|
2018-02-12 05:34:03 +07:00
|
|
|
return EPOLLERR | EPOLLHUP;
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "ioctl" file op
|
|
|
|
*/
|
2008-03-03 17:48:43 +07:00
|
|
|
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;
|
2018-06-30 05:08:44 +07:00
|
|
|
uref->field_index = array_index_nospec(uref->field_index,
|
|
|
|
report->maxfield);
|
2008-03-03 17:48:43 +07:00
|
|
|
|
|
|
|
field = report->field[uref->field_index];
|
|
|
|
if (uref->usage_index >= field->maxusage)
|
|
|
|
goto inval;
|
2018-06-30 05:08:44 +07:00
|
|
|
uref->usage_index = array_index_nospec(uref->usage_index,
|
|
|
|
field->maxusage);
|
2008-03-03 17:48:43 +07:00
|
|
|
|
|
|
|
uref->usage_code = field->usage[uref->usage_index].hid;
|
|
|
|
|
|
|
|
if (copy_to_user(user_arg, uref, sizeof(*uref)))
|
|
|
|
goto fault;
|
|
|
|
|
2008-10-23 06:47:34 +07:00
|
|
|
goto goodreturn;
|
2008-03-03 17:48:43 +07:00
|
|
|
|
|
|
|
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;
|
2018-06-30 05:08:44 +07:00
|
|
|
uref->field_index = array_index_nospec(uref->field_index,
|
|
|
|
report->maxfield);
|
2008-03-03 17:48:43 +07:00
|
|
|
|
|
|
|
field = report->field[uref->field_index];
|
|
|
|
|
|
|
|
if (cmd == HIDIOCGCOLLECTIONINDEX) {
|
|
|
|
if (uref->usage_index >= field->maxusage)
|
|
|
|
goto inval;
|
HID: hiddev: fix potential Spectre v1
uref->usage_index can be indirectly controlled by userspace, hence leading
to a potential exploitation of the Spectre variant 1 vulnerability.
This field is used as an array index by the hiddev_ioctl_usage() function,
when 'cmd' is either HIDIOCGCOLLECTIONINDEX, HIDIOCGUSAGES or
HIDIOCSUSAGES.
For cmd == HIDIOCGCOLLECTIONINDEX case, uref->usage_index is compared to
field->maxusage and then used as an index to dereference field->usage
array. The same thing happens to the cmd == HIDIOC{G,S}USAGES cases, where
uref->usage_index is checked against an array maximum value and then it is
used as an index in an array.
This is a summary of the HIDIOCGCOLLECTIONINDEX case, which matches the
traditional Spectre V1 first load:
copy_from_user(uref, user_arg, sizeof(*uref))
if (uref->usage_index >= field->maxusage)
goto inval;
i = field->usage[uref->usage_index].collection_index;
return i;
This patch fixes this by sanitizing field uref->usage_index before using it
to index field->usage (HIDIOCGCOLLECTIONINDEX) or field->value in
HIDIOC{G,S}USAGES arrays, thus, avoiding speculation in the first load.
Cc: <stable@vger.kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
--
v2: Contemplate cmd == HIDIOC{G,S}USAGES case
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2018-10-20 03:01:33 +07:00
|
|
|
uref->usage_index =
|
|
|
|
array_index_nospec(uref->usage_index,
|
|
|
|
field->maxusage);
|
2008-03-03 17:48:43 +07:00
|
|
|
} else if (uref->usage_index >= field->report_count)
|
|
|
|
goto inval;
|
2011-03-26 08:47:35 +07:00
|
|
|
}
|
2008-03-03 17:48:43 +07:00
|
|
|
|
HID: hiddev: fix potential Spectre v1
uref->usage_index can be indirectly controlled by userspace, hence leading
to a potential exploitation of the Spectre variant 1 vulnerability.
This field is used as an array index by the hiddev_ioctl_usage() function,
when 'cmd' is either HIDIOCGCOLLECTIONINDEX, HIDIOCGUSAGES or
HIDIOCSUSAGES.
For cmd == HIDIOCGCOLLECTIONINDEX case, uref->usage_index is compared to
field->maxusage and then used as an index to dereference field->usage
array. The same thing happens to the cmd == HIDIOC{G,S}USAGES cases, where
uref->usage_index is checked against an array maximum value and then it is
used as an index in an array.
This is a summary of the HIDIOCGCOLLECTIONINDEX case, which matches the
traditional Spectre V1 first load:
copy_from_user(uref, user_arg, sizeof(*uref))
if (uref->usage_index >= field->maxusage)
goto inval;
i = field->usage[uref->usage_index].collection_index;
return i;
This patch fixes this by sanitizing field uref->usage_index before using it
to index field->usage (HIDIOCGCOLLECTIONINDEX) or field->value in
HIDIOC{G,S}USAGES arrays, thus, avoiding speculation in the first load.
Cc: <stable@vger.kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
--
v2: Contemplate cmd == HIDIOC{G,S}USAGES case
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2018-10-20 03:01:33 +07:00
|
|
|
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);
|
|
|
|
}
|
2016-06-23 21:59:47 +07:00
|
|
|
|
2008-03-03 17:48:43 +07:00
|
|
|
switch (cmd) {
|
|
|
|
case HIDIOCGUSAGE:
|
|
|
|
uref->value = field->value[uref->usage_index];
|
|
|
|
if (copy_to_user(user_arg, uref, sizeof(*uref)))
|
|
|
|
goto fault;
|
|
|
|
goto goodreturn;
|
|
|
|
|
|
|
|
case HIDIOCSUSAGE:
|
|
|
|
field->value[uref->usage_index] = uref->value;
|
|
|
|
goto goodreturn;
|
|
|
|
|
|
|
|
case HIDIOCGCOLLECTIONINDEX:
|
2009-06-20 04:24:11 +07:00
|
|
|
i = field->usage[uref->usage_index].collection_index;
|
2008-03-03 17:48:43 +07:00
|
|
|
kfree(uref_multi);
|
2009-06-20 04:24:11 +07:00
|
|
|
return i;
|
2008-03-03 17:48:43 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-05-26 16:25:20 +07:00
|
|
|
static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct hiddev_list *list = file->private_data;
|
|
|
|
struct hiddev *hiddev = list->hiddev;
|
2010-12-06 21:51:41 +07:00
|
|
|
struct hid_device *hid;
|
2005-04-17 05:20:36 +07:00
|
|
|
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;
|
2010-12-06 22:16:11 +07:00
|
|
|
int i, r = -EINVAL;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
2008-05-26 16:25:20 +07:00
|
|
|
/* Called without BKL by compat methods so no BKL taken */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
mutex_lock(&hiddev->existancelock);
|
|
|
|
if (!hiddev->exist) {
|
|
|
|
r = -ENODEV;
|
|
|
|
goto ret_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
hid = hiddev->hid;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
|
|
|
case HIDIOCGVERSION:
|
2010-12-06 22:16:11 +07:00
|
|
|
r = put_user(HID_VERSION, (int __user *)arg) ?
|
|
|
|
-EFAULT : 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCAPPLICATION:
|
2012-11-16 13:50:43 +07:00
|
|
|
if (arg >= hid->maxapplication)
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
for (i = 0; i < hid->maxcollection; i++)
|
2005-05-29 14:29:01 +07:00
|
|
|
if (hid->collection[i].type ==
|
2005-04-17 05:20:36 +07:00
|
|
|
HID_COLLECTION_APPLICATION && arg-- == 0)
|
|
|
|
break;
|
2005-05-29 14:29:01 +07:00
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
if (i < hid->maxcollection)
|
2010-12-06 21:51:41 +07:00
|
|
|
r = hid->collection[i].usage;
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGDEVINFO:
|
2010-12-06 22:16:11 +07:00
|
|
|
{
|
|
|
|
struct usb_device *dev = hid_to_usb_dev(hid);
|
|
|
|
struct usbhid_device *usbhid = hid->driver_data;
|
|
|
|
|
2011-09-23 13:21:13 +07:00
|
|
|
memset(&dinfo, 0, sizeof(dinfo));
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
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;
|
2010-12-06 21:51:41 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
case HIDIOCGFLAG:
|
2010-12-06 22:16:11 +07:00
|
|
|
r = put_user(list->flags, (int __user *)arg) ?
|
|
|
|
-EFAULT : 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCSFLAG:
|
|
|
|
{
|
|
|
|
int newflags;
|
2010-12-06 22:16:11 +07:00
|
|
|
|
|
|
|
if (get_user(newflags, (int __user *)arg)) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if ((newflags & ~HIDDEV_FLAGS) != 0 ||
|
|
|
|
((newflags & HIDDEV_FLAG_REPORT) != 0 &&
|
|
|
|
(newflags & HIDDEV_FLAG_UREF) == 0))
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
list->flags = newflags;
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
case HIDIOCGSTRING:
|
2010-12-06 22:16:11 +07:00
|
|
|
r = hiddev_ioctl_string(hiddev, cmd, user_arg);
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCINITREPORT:
|
2006-12-09 00:41:03 +07:00
|
|
|
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 21:11:14 +07:00
|
|
|
hiddev->initialized = true;
|
2010-12-06 22:16:11 +07:00
|
|
|
r = 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGREPORT:
|
2010-12-06 22:16:11 +07:00
|
|
|
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
|
|
|
report = hiddev_lookup_report(hid, &rinfo);
|
2010-12-06 22:16:11 +07:00
|
|
|
if (report == NULL)
|
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
2013-02-25 17:31:46 +07:00
|
|
|
hid_hw_request(hid, report, HID_REQ_GET_REPORT);
|
2013-02-25 17:31:47 +07:00
|
|
|
hid_hw_wait(hid);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCSREPORT:
|
2010-12-06 22:16:11 +07:00
|
|
|
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
|
|
|
report = hiddev_lookup_report(hid, &rinfo);
|
2010-12-06 22:16:11 +07:00
|
|
|
if (report == NULL)
|
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
2013-02-25 17:31:46 +07:00
|
|
|
hid_hw_request(hid, report, HID_REQ_SET_REPORT);
|
2013-02-25 17:31:47 +07:00
|
|
|
hid_hw_wait(hid);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGREPORTINFO:
|
2010-12-06 22:16:11 +07:00
|
|
|
if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
report = hiddev_lookup_report(hid, &rinfo);
|
2010-12-06 22:16:11 +07:00
|
|
|
if (report == NULL)
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
rinfo.num_fields = report->maxfield;
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = copy_to_user(user_arg, &rinfo, sizeof(rinfo)) ?
|
|
|
|
-EFAULT : 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGFIELDINFO:
|
2010-12-06 22:16:11 +07:00
|
|
|
if (copy_from_user(&finfo, user_arg, sizeof(finfo))) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
rinfo.report_type = finfo.report_type;
|
|
|
|
rinfo.report_id = finfo.report_id;
|
|
|
|
|
2010-12-06 21:51:41 +07:00
|
|
|
report = hiddev_lookup_report(hid, &rinfo);
|
2010-12-06 22:16:11 +07:00
|
|
|
if (report == NULL)
|
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
if (finfo.field_index >= report->maxfield)
|
|
|
|
break;
|
2018-06-30 05:08:44 +07:00
|
|
|
finfo.field_index = array_index_nospec(finfo.field_index,
|
|
|
|
report->maxfield);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = copy_to_user(user_arg, &finfo, sizeof(finfo)) ?
|
|
|
|
-EFAULT : 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGUCODE:
|
2008-03-03 17:48:43 +07:00
|
|
|
/* fall through */
|
2005-04-17 05:20:36 +07:00
|
|
|
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 21:11:14 +07:00
|
|
|
if (!hiddev->initialized) {
|
|
|
|
usbhid_init_reports(hid);
|
|
|
|
hiddev->initialized = true;
|
|
|
|
}
|
2010-12-06 22:16:11 +07:00
|
|
|
r = hiddev_ioctl_usage(hiddev, cmd, user_arg);
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
case HIDIOCGCOLLECTIONINFO:
|
2010-12-06 22:16:11 +07:00
|
|
|
if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) {
|
|
|
|
r = -EFAULT;
|
|
|
|
break;
|
2010-12-06 21:51:41 +07:00
|
|
|
}
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
if (cinfo.index >= hid->maxcollection)
|
|
|
|
break;
|
2018-06-30 05:08:44 +07:00
|
|
|
cinfo.index = array_index_nospec(cinfo.index,
|
|
|
|
hid->maxcollection);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
cinfo.type = hid->collection[cinfo.index].type;
|
|
|
|
cinfo.usage = hid->collection[cinfo.index].usage;
|
|
|
|
cinfo.level = hid->collection[cinfo.index].level;
|
|
|
|
|
2010-12-06 22:16:11 +07:00
|
|
|
r = copy_to_user(user_arg, &cinfo, sizeof(cinfo)) ?
|
|
|
|
-EFAULT : 0;
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
default:
|
|
|
|
if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
|
2011-05-15 23:07:42 +07:00
|
|
|
int len = strlen(hid->name) + 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (len > _IOC_SIZE(cmd))
|
|
|
|
len = _IOC_SIZE(cmd);
|
2010-12-06 21:51:41 +07:00
|
|
|
r = copy_to_user(user_arg, hid->name, len) ?
|
2005-04-17 05:20:36 +07:00
|
|
|
-EFAULT : len;
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) {
|
2011-05-15 23:07:42 +07:00
|
|
|
int len = strlen(hid->phys) + 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (len > _IOC_SIZE(cmd))
|
|
|
|
len = _IOC_SIZE(cmd);
|
2010-12-06 21:51:41 +07:00
|
|
|
r = copy_to_user(user_arg, hid->phys, len) ?
|
2005-04-17 05:20:36 +07:00
|
|
|
-EFAULT : len;
|
2010-12-06 22:16:11 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2010-12-06 22:16:11 +07:00
|
|
|
|
|
|
|
ret_unlock:
|
|
|
|
mutex_unlock(&hiddev->existancelock);
|
|
|
|
return r;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2007-10-14 17:03:58 +07:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
static long hiddev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
2008-05-27 16:36:40 +07:00
|
|
|
return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
|
2007-10-14 17:03:58 +07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2006-08-06 06:37:11 +07:00
|
|
|
static const struct file_operations hiddev_fops = {
|
2005-04-17 05:20:36 +07:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = hiddev_read,
|
|
|
|
.write = hiddev_write,
|
|
|
|
.poll = hiddev_poll,
|
|
|
|
.open = hiddev_open,
|
|
|
|
.release = hiddev_release,
|
2008-05-26 16:25:20 +07:00
|
|
|
.unlocked_ioctl = hiddev_ioctl,
|
2005-04-17 05:20:36 +07:00
|
|
|
.fasync = hiddev_fasync,
|
2007-10-14 17:03:58 +07:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
.compat_ioctl = hiddev_compat_ioctl,
|
|
|
|
#endif
|
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 23:52:59 +07:00
|
|
|
.llseek = noop_llseek,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
2011-07-24 07:24:48 +07:00
|
|
|
static char *hiddev_devnode(struct device *dev, umode_t *mode)
|
2009-04-30 20:23:42 +07:00
|
|
|
{
|
|
|
|
return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static struct usb_class_driver hiddev_class = {
|
2005-06-21 11:15:16 +07:00
|
|
|
.name = "hiddev%d",
|
2009-09-19 04:01:12 +07:00
|
|
|
.devnode = hiddev_devnode,
|
2005-04-17 05:20:36 +07:00
|
|
|
.fops = &hiddev_fops,
|
2005-05-29 14:29:01 +07:00
|
|
|
.minor_base = HIDDEV_MINOR_BASE,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is where hid.c calls us to connect a hid device to the hiddev driver
|
|
|
|
*/
|
2008-06-27 05:04:24 +07:00
|
|
|
int hiddev_connect(struct hid_device *hid, unsigned int force)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct hiddev *hiddev;
|
2006-12-09 00:41:03 +07:00
|
|
|
struct usbhid_device *usbhid = hid->driver_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
int retval;
|
|
|
|
|
2008-06-27 05:04:24 +07:00
|
|
|
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;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-06-27 05:04:24 +07:00
|
|
|
if (i == hid->maxcollection)
|
|
|
|
return -1;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-01-07 02:54:29 +07:00
|
|
|
if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
init_waitqueue_head(&hiddev->wait);
|
2006-07-19 12:09:10 +07:00
|
|
|
INIT_LIST_HEAD(&hiddev->list);
|
2007-05-10 13:45:56 +07:00
|
|
|
spin_lock_init(&hiddev->list_lock);
|
2008-12-16 16:55:15 +07:00
|
|
|
mutex_init(&hiddev->existancelock);
|
2009-01-07 19:25:36 +07:00
|
|
|
hid->hiddev = hiddev;
|
2005-04-17 05:20:36 +07:00
|
|
|
hiddev->hid = hid;
|
|
|
|
hiddev->exist = 1;
|
2008-12-16 16:55:15 +07:00
|
|
|
retval = usb_register_dev(usbhid->intf, &hiddev_class);
|
|
|
|
if (retval) {
|
2010-12-10 10:29:03 +07:00
|
|
|
hid_err(hid, "Not able to get a minor for this device\n");
|
2009-01-07 19:25:36 +07:00
|
|
|
hid->hiddev = NULL;
|
2008-12-16 16:55:15 +07:00
|
|
|
kfree(hiddev);
|
|
|
|
return -1;
|
|
|
|
}
|
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 21:11:14 +07: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;
|
|
|
|
|
2017-03-03 15:54:01 +07:00
|
|
|
hiddev->minor = usbhid->intf->minor;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
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;
|
2006-12-09 00:41:03 +07:00
|
|
|
struct usbhid_device *usbhid = hid->driver_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
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 16:42:22 +07:00
|
|
|
usb_deregister_dev(usbhid->intf, &hiddev_class);
|
|
|
|
|
2008-12-16 16:55:15 +07:00
|
|
|
mutex_lock(&hiddev->existancelock);
|
2005-04-17 05:20:36 +07:00
|
|
|
hiddev->exist = 0;
|
|
|
|
|
|
|
|
if (hiddev->open) {
|
2011-05-24 16:43:18 +07:00
|
|
|
mutex_unlock(&hiddev->existancelock);
|
2017-06-07 13:59:31 +07:00
|
|
|
hid_hw_close(hiddev->hid);
|
2005-04-17 05:20:36 +07:00
|
|
|
wake_up_interruptible(&hiddev->wait);
|
|
|
|
} else {
|
2011-05-24 16:43:18 +07:00
|
|
|
mutex_unlock(&hiddev->existancelock);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(hiddev);
|
|
|
|
}
|
|
|
|
}
|