Commit 621de593 authored by Jiri Kosina's avatar Jiri Kosina

Merge branch 'autosuspend' into for-next

Conflicts:

	drivers/hid/hid-core.c
parents afa5eb7c 6d779768
...@@ -1819,6 +1819,22 @@ void hid_unregister_driver(struct hid_driver *hdrv) ...@@ -1819,6 +1819,22 @@ void hid_unregister_driver(struct hid_driver *hdrv)
} }
EXPORT_SYMBOL_GPL(hid_unregister_driver); EXPORT_SYMBOL_GPL(hid_unregister_driver);
int hid_check_keys_pressed(struct hid_device *hid)
{
struct hid_input *hidinput;
int i;
list_for_each_entry(hidinput, &hid->inputs, list) {
for (i = 0; i < BITS_TO_LONGS(KEY_MAX); i++)
if (hidinput->input->key[i])
return 1;
}
return 0;
}
EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
static int __init hid_init(void) static int __init hid_init(void)
{ {
int ret; int ret;
......
...@@ -181,10 +181,18 @@ static int hidraw_open(struct inode *inode, struct file *file) ...@@ -181,10 +181,18 @@ static int hidraw_open(struct inode *inode, struct file *file)
dev = hidraw_table[minor]; dev = hidraw_table[minor];
if (!dev->open++) { if (!dev->open++) {
err = dev->hid->ll_driver->open(dev->hid); if (dev->hid->ll_driver->power) {
err = dev->hid->ll_driver->power(dev->hid, PM_HINT_FULLON);
if (err < 0) if (err < 0)
goto out_unlock;
}
err = dev->hid->ll_driver->open(dev->hid);
if (err < 0) {
if (dev->hid->ll_driver->power)
dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL);
dev->open--; dev->open--;
} }
}
out_unlock: out_unlock:
mutex_unlock(&minors_lock); mutex_unlock(&minors_lock);
...@@ -209,11 +217,14 @@ static int hidraw_release(struct inode * inode, struct file * file) ...@@ -209,11 +217,14 @@ static int hidraw_release(struct inode * inode, struct file * file)
list_del(&list->node); list_del(&list->node);
dev = hidraw_table[minor]; dev = hidraw_table[minor];
if (!--dev->open) { if (!--dev->open) {
if (list->hidraw->exist) if (list->hidraw->exist) {
if (dev->hid->ll_driver->power)
dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL);
dev->hid->ll_driver->close(dev->hid); dev->hid->ll_driver->close(dev->hid);
else } else {
kfree(list->hidraw); kfree(list->hidraw);
} }
}
kfree(list); kfree(list);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz> * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
* Copyright (c) 2006-2008 Jiri Kosina * Copyright (c) 2006-2008 Jiri Kosina
* Copyright (c) 2007-2008 Oliver Neukum
*/ */
/* /*
...@@ -27,6 +28,7 @@ ...@@ -27,6 +28,7 @@
#include <asm/byteorder.h> #include <asm/byteorder.h>
#include <linux/input.h> #include <linux/input.h>
#include <linux/wait.h> #include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/usb.h> #include <linux/usb.h>
...@@ -53,6 +55,10 @@ static unsigned int hid_mousepoll_interval; ...@@ -53,6 +55,10 @@ static unsigned int hid_mousepoll_interval;
module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644); module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
MODULE_PARM_DESC(mousepoll, "Polling interval of mice"); MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
static unsigned int ignoreled;
module_param_named(ignoreled, ignoreled, uint, 0644);
MODULE_PARM_DESC(ignoreled, "Autosuspend with active leds");
/* Quirks specified at module load time */ /* Quirks specified at module load time */
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL }; static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
module_param_array_named(quirks, quirks_param, charp, NULL, 0444); module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
...@@ -63,8 +69,13 @@ MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying " ...@@ -63,8 +69,13 @@ MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
/* /*
* Input submission and I/O error handler. * Input submission and I/O error handler.
*/ */
static DEFINE_MUTEX(hid_open_mut);
static struct workqueue_struct *resumption_waker;
static void hid_io_error(struct hid_device *hid); static void hid_io_error(struct hid_device *hid);
static int hid_submit_out(struct hid_device *hid);
static int hid_submit_ctrl(struct hid_device *hid);
static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid);
/* Start up the input URB */ /* Start up the input URB */
static int hid_start_in(struct hid_device *hid) static int hid_start_in(struct hid_device *hid)
...@@ -73,15 +84,16 @@ static int hid_start_in(struct hid_device *hid) ...@@ -73,15 +84,16 @@ static int hid_start_in(struct hid_device *hid)
int rc = 0; int rc = 0;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
spin_lock_irqsave(&usbhid->inlock, flags); spin_lock_irqsave(&usbhid->lock, flags);
if (hid->open > 0 && !test_bit(HID_SUSPENDED, &usbhid->iofl) && if (hid->open > 0 &&
!test_bit(HID_DISCONNECTED, &usbhid->iofl) && !test_bit(HID_DISCONNECTED, &usbhid->iofl) &&
!test_bit(HID_REPORTED_IDLE, &usbhid->iofl) &&
!test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) { !test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) {
rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC); rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC);
if (rc != 0) if (rc != 0)
clear_bit(HID_IN_RUNNING, &usbhid->iofl); clear_bit(HID_IN_RUNNING, &usbhid->iofl);
} }
spin_unlock_irqrestore(&usbhid->inlock, flags); spin_unlock_irqrestore(&usbhid->lock, flags);
return rc; return rc;
} }
...@@ -145,7 +157,7 @@ static void hid_io_error(struct hid_device *hid) ...@@ -145,7 +157,7 @@ static void hid_io_error(struct hid_device *hid)
unsigned long flags; unsigned long flags;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
spin_lock_irqsave(&usbhid->inlock, flags); spin_lock_irqsave(&usbhid->lock, flags);
/* Stop when disconnected */ /* Stop when disconnected */
if (test_bit(HID_DISCONNECTED, &usbhid->iofl)) if (test_bit(HID_DISCONNECTED, &usbhid->iofl))
...@@ -175,7 +187,51 @@ static void hid_io_error(struct hid_device *hid) ...@@ -175,7 +187,51 @@ static void hid_io_error(struct hid_device *hid)
mod_timer(&usbhid->io_retry, mod_timer(&usbhid->io_retry,
jiffies + msecs_to_jiffies(usbhid->retry_delay)); jiffies + msecs_to_jiffies(usbhid->retry_delay));
done: done:
spin_unlock_irqrestore(&usbhid->inlock, flags); spin_unlock_irqrestore(&usbhid->lock, flags);
}
static void usbhid_mark_busy(struct usbhid_device *usbhid)
{
struct usb_interface *intf = usbhid->intf;
usb_mark_last_busy(interface_to_usbdev(intf));
}
static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
{
struct hid_device *hid = usb_get_intfdata(usbhid->intf);
int kicked;
if (!hid)
return 0;
if ((kicked = (usbhid->outhead != usbhid->outtail))) {
dbg("Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
if (hid_submit_out(hid)) {
clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
wake_up(&usbhid->wait);
}
}
return kicked;
}
static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
{
struct hid_device *hid = usb_get_intfdata(usbhid->intf);
int kicked;
WARN_ON(hid == NULL);
if (!hid)
return 0;
if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
dbg("Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
if (hid_submit_ctrl(hid)) {
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
wake_up(&usbhid->wait);
}
}
return kicked;
} }
/* /*
...@@ -190,12 +246,23 @@ static void hid_irq_in(struct urb *urb) ...@@ -190,12 +246,23 @@ static void hid_irq_in(struct urb *urb)
switch (urb->status) { switch (urb->status) {
case 0: /* success */ case 0: /* success */
usbhid_mark_busy(usbhid);
usbhid->retry_delay = 0; usbhid->retry_delay = 0;
hid_input_report(urb->context, HID_INPUT_REPORT, hid_input_report(urb->context, HID_INPUT_REPORT,
urb->transfer_buffer, urb->transfer_buffer,
urb->actual_length, 1); urb->actual_length, 1);
/*
* autosuspend refused while keys are pressed
* because most keyboards don't wake up when
* a key is released
*/
if (hid_check_keys_pressed(hid))
set_bit(HID_KEYS_PRESSED, &usbhid->iofl);
else
clear_bit(HID_KEYS_PRESSED, &usbhid->iofl);
break; break;
case -EPIPE: /* stall */ case -EPIPE: /* stall */
usbhid_mark_busy(usbhid);
clear_bit(HID_IN_RUNNING, &usbhid->iofl); clear_bit(HID_IN_RUNNING, &usbhid->iofl);
set_bit(HID_CLEAR_HALT, &usbhid->iofl); set_bit(HID_CLEAR_HALT, &usbhid->iofl);
schedule_work(&usbhid->reset_work); schedule_work(&usbhid->reset_work);
...@@ -209,6 +276,7 @@ static void hid_irq_in(struct urb *urb) ...@@ -209,6 +276,7 @@ static void hid_irq_in(struct urb *urb)
case -EPROTO: /* protocol error or unplug */ case -EPROTO: /* protocol error or unplug */
case -ETIME: /* protocol error or unplug */ case -ETIME: /* protocol error or unplug */
case -ETIMEDOUT: /* Should never happen, but... */ case -ETIMEDOUT: /* Should never happen, but... */
usbhid_mark_busy(usbhid);
clear_bit(HID_IN_RUNNING, &usbhid->iofl); clear_bit(HID_IN_RUNNING, &usbhid->iofl);
hid_io_error(hid); hid_io_error(hid);
return; return;
...@@ -239,6 +307,7 @@ static int hid_submit_out(struct hid_device *hid) ...@@ -239,6 +307,7 @@ static int hid_submit_out(struct hid_device *hid)
report = usbhid->out[usbhid->outtail].report; report = usbhid->out[usbhid->outtail].report;
raw_report = usbhid->out[usbhid->outtail].raw_report; raw_report = usbhid->out[usbhid->outtail].raw_report;
if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0);
usbhid->urbout->dev = hid_to_usb_dev(hid); usbhid->urbout->dev = hid_to_usb_dev(hid);
memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length); memcpy(usbhid->outbuf, raw_report, usbhid->urbout->transfer_buffer_length);
...@@ -250,6 +319,14 @@ static int hid_submit_out(struct hid_device *hid) ...@@ -250,6 +319,14 @@ static int hid_submit_out(struct hid_device *hid)
err_hid("usb_submit_urb(out) failed"); err_hid("usb_submit_urb(out) failed");
return -1; return -1;
} }
} else {
/*
* queue work to wake up the device.
* as the work queue is freezeable, this is safe
* with respect to STD and STR
*/
queue_work(resumption_waker, &usbhid->restart_work);
}
return 0; return 0;
} }
...@@ -266,6 +343,7 @@ static int hid_submit_ctrl(struct hid_device *hid) ...@@ -266,6 +343,7 @@ static int hid_submit_ctrl(struct hid_device *hid)
raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report; raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report;
dir = usbhid->ctrl[usbhid->ctrltail].dir; dir = usbhid->ctrl[usbhid->ctrltail].dir;
if (!test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) {
len = ((report->size - 1) >> 3) + 1 + (report->id > 0); len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
if (dir == USB_DIR_OUT) { if (dir == USB_DIR_OUT) {
usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
...@@ -302,6 +380,14 @@ static int hid_submit_ctrl(struct hid_device *hid) ...@@ -302,6 +380,14 @@ static int hid_submit_ctrl(struct hid_device *hid)
err_hid("usb_submit_urb(ctrl) failed"); err_hid("usb_submit_urb(ctrl) failed");
return -1; return -1;
} }
} else {
/*
* queue work to wake up the device.
* as the work queue is freezeable, this is safe
* with respect to STD and STR
*/
queue_work(resumption_waker, &usbhid->restart_work);
}
return 0; return 0;
} }
...@@ -332,7 +418,7 @@ static void hid_irq_out(struct urb *urb) ...@@ -332,7 +418,7 @@ static void hid_irq_out(struct urb *urb)
"received\n", urb->status); "received\n", urb->status);
} }
spin_lock_irqsave(&usbhid->outlock, flags); spin_lock_irqsave(&usbhid->lock, flags);
if (unplug) if (unplug)
usbhid->outtail = usbhid->outhead; usbhid->outtail = usbhid->outhead;
...@@ -344,12 +430,12 @@ static void hid_irq_out(struct urb *urb) ...@@ -344,12 +430,12 @@ static void hid_irq_out(struct urb *urb)
clear_bit(HID_OUT_RUNNING, &usbhid->iofl); clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
wake_up(&usbhid->wait); wake_up(&usbhid->wait);
} }
spin_unlock_irqrestore(&usbhid->outlock, flags); spin_unlock_irqrestore(&usbhid->lock, flags);
return; return;
} }
clear_bit(HID_OUT_RUNNING, &usbhid->iofl); clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
spin_unlock_irqrestore(&usbhid->outlock, flags); spin_unlock_irqrestore(&usbhid->lock, flags);
wake_up(&usbhid->wait); wake_up(&usbhid->wait);
} }
...@@ -361,12 +447,11 @@ static void hid_ctrl(struct urb *urb) ...@@ -361,12 +447,11 @@ static void hid_ctrl(struct urb *urb)
{ {
struct hid_device *hid = urb->context; struct hid_device *hid = urb->context;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
unsigned long flags; int unplug = 0, status = urb->status;
int unplug = 0;
spin_lock_irqsave(&usbhid->ctrllock, flags); spin_lock(&usbhid->lock);
switch (urb->status) { switch (status) {
case 0: /* success */ case 0: /* success */
if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN) if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN)
hid_input_report(urb->context, hid_input_report(urb->context,
...@@ -383,7 +468,7 @@ static void hid_ctrl(struct urb *urb) ...@@ -383,7 +468,7 @@ static void hid_ctrl(struct urb *urb)
break; break;
default: /* error */ default: /* error */
dev_warn(&urb->dev->dev, "ctrl urb status %d " dev_warn(&urb->dev->dev, "ctrl urb status %d "
"received\n", urb->status); "received\n", status);
} }
if (unplug) if (unplug)
...@@ -396,19 +481,18 @@ static void hid_ctrl(struct urb *urb) ...@@ -396,19 +481,18 @@ static void hid_ctrl(struct urb *urb)
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
wake_up(&usbhid->wait); wake_up(&usbhid->wait);
} }
spin_unlock_irqrestore(&usbhid->ctrllock, flags); spin_unlock(&usbhid->lock);
return; return;
} }
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
spin_unlock_irqrestore(&usbhid->ctrllock, flags); spin_unlock(&usbhid->lock);
wake_up(&usbhid->wait); wake_up(&usbhid->wait);
} }
void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir) void __usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
{ {
int head; int head;
unsigned long flags;
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
int len = ((report->size - 1) >> 3) + 1 + (report->id > 0); int len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
...@@ -416,18 +500,13 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -416,18 +500,13 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
return; return;
if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) { if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
spin_lock_irqsave(&usbhid->outlock, flags);
if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) { if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
spin_unlock_irqrestore(&usbhid->outlock, flags);
dev_warn(&hid->dev, "output queue full\n"); dev_warn(&hid->dev, "output queue full\n");
return; return;
} }
usbhid->out[usbhid->outhead].raw_report = kmalloc(len, GFP_ATOMIC); usbhid->out[usbhid->outhead].raw_report = kmalloc(len, GFP_ATOMIC);
if (!usbhid->out[usbhid->outhead].raw_report) { if (!usbhid->out[usbhid->outhead].raw_report) {
spin_unlock_irqrestore(&usbhid->outlock, flags);
dev_warn(&hid->dev, "output queueing failed\n"); dev_warn(&hid->dev, "output queueing failed\n");
return; return;
} }
...@@ -438,15 +517,10 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -438,15 +517,10 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl))
if (hid_submit_out(hid)) if (hid_submit_out(hid))
clear_bit(HID_OUT_RUNNING, &usbhid->iofl); clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
spin_unlock_irqrestore(&usbhid->outlock, flags);
return; return;
} }
spin_lock_irqsave(&usbhid->ctrllock, flags);
if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) { if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
dev_warn(&hid->dev, "control queue full\n"); dev_warn(&hid->dev, "control queue full\n");
return; return;
} }
...@@ -454,7 +528,6 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -454,7 +528,6 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if (dir == USB_DIR_OUT) { if (dir == USB_DIR_OUT) {
usbhid->ctrl[usbhid->ctrlhead].raw_report = kmalloc(len, GFP_ATOMIC); usbhid->ctrl[usbhid->ctrlhead].raw_report = kmalloc(len, GFP_ATOMIC);
if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) { if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) {
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
dev_warn(&hid->dev, "control queueing failed\n"); dev_warn(&hid->dev, "control queueing failed\n");
return; return;
} }
...@@ -467,15 +540,25 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -467,15 +540,25 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
if (hid_submit_ctrl(hid)) if (hid_submit_ctrl(hid))
clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
}
spin_unlock_irqrestore(&usbhid->ctrllock, flags); void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
{
struct usbhid_device *usbhid = hid->driver_data;
unsigned long flags;
spin_lock_irqsave(&usbhid->lock, flags);
__usbhid_submit_report(hid, report, dir);
spin_unlock_irqrestore(&usbhid->lock, flags);
} }
EXPORT_SYMBOL_GPL(usbhid_submit_report); EXPORT_SYMBOL_GPL(usbhid_submit_report);
static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{ {
struct hid_device *hid = input_get_drvdata(dev); struct hid_device *hid = input_get_drvdata(dev);
struct usbhid_device *usbhid = hid->driver_data;
struct hid_field *field; struct hid_field *field;
unsigned long flags;
int offset; int offset;
if (type == EV_FF) if (type == EV_FF)
...@@ -490,6 +573,15 @@ static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, un ...@@ -490,6 +573,15 @@ static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, un
} }
hid_set_field(field, offset, value); hid_set_field(field, offset, value);
if (value) {
spin_lock_irqsave(&usbhid->lock, flags);
usbhid->ledcount++;
spin_unlock_irqrestore(&usbhid->lock, flags);
} else {
spin_lock_irqsave(&usbhid->lock, flags);
usbhid->ledcount--;
spin_unlock_irqrestore(&usbhid->lock, flags);
}
usbhid_submit_report(hid, field->report, USB_DIR_OUT); usbhid_submit_report(hid, field->report, USB_DIR_OUT);
return 0; return 0;
...@@ -538,15 +630,22 @@ int usbhid_open(struct hid_device *hid) ...@@ -538,15 +630,22 @@ int usbhid_open(struct hid_device *hid)
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
int res; int res;
mutex_lock(&hid_open_mut);
if (!hid->open++) { if (!hid->open++) {
res = usb_autopm_get_interface(usbhid->intf); res = usb_autopm_get_interface(usbhid->intf);
/* the device must be awake to reliable request remote wakeup */
if (res < 0) { if (res < 0) {
hid->open--; hid->open--;
mutex_unlock(&hid_open_mut);
return -EIO; return -EIO;
} }
} usbhid->intf->needs_remote_wakeup = 1;
if (hid_start_in(hid)) if (hid_start_in(hid))
hid_io_error(hid); hid_io_error(hid);
usb_autopm_put_interface(usbhid->intf);
}
mutex_unlock(&hid_open_mut);
return 0; return 0;
} }
...@@ -554,10 +653,22 @@ void usbhid_close(struct hid_device *hid) ...@@ -554,10 +653,22 @@ void usbhid_close(struct hid_device *hid)
{ {
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
mutex_lock(&hid_open_mut);
/* protecting hid->open to make sure we don't restart
* data acquistion due to a resumption we no longer
* care about
*/
spin_lock_irq(&usbhid->lock);
if (!--hid->open) { if (!--hid->open) {
spin_unlock_irq(&usbhid->lock);
usb_kill_urb(usbhid->urbin); usb_kill_urb(usbhid->urbin);
usb_autopm_put_interface(usbhid->intf); flush_scheduled_work();
usbhid->intf->needs_remote_wakeup = 0;
} else {
spin_unlock_irq(&usbhid->lock);
} }
mutex_unlock(&hid_open_mut);
} }
/* /*
...@@ -687,6 +798,25 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co ...@@ -687,6 +798,25 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co
return ret; return ret;
} }
static void usbhid_restart_queues(struct usbhid_device *usbhid)
{
if (usbhid->urbout)
usbhid_restart_out_queue(usbhid);
usbhid_restart_ctrl_queue(usbhid);
}
static void __usbhid_restart_queues(struct work_struct *work)
{
struct usbhid_device *usbhid =
container_of(work, struct usbhid_device, restart_work);
int r;
r = usb_autopm_get_interface(usbhid->intf);
if (r < 0)
return;
usb_autopm_put_interface(usbhid->intf);
}
static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid) static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
{ {
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
...@@ -853,11 +983,11 @@ static int usbhid_start(struct hid_device *hid) ...@@ -853,11 +983,11 @@ static int usbhid_start(struct hid_device *hid)
init_waitqueue_head(&usbhid->wait); init_waitqueue_head(&usbhid->wait);
INIT_WORK(&usbhid->reset_work, hid_reset); INIT_WORK(&usbhid->reset_work, hid_reset);
INIT_WORK(&usbhid->restart_work, __usbhid_restart_queues);
setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid); setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
spin_lock_init(&usbhid->inlock); spin_lock_init(&usbhid->lock);
spin_lock_init(&usbhid->outlock); spin_lock_init(&usbhid->lock);
spin_lock_init(&usbhid->ctrllock);
usbhid->intf = intf; usbhid->intf = intf;
usbhid->ifnum = interface->desc.bInterfaceNumber; usbhid->ifnum = interface->desc.bInterfaceNumber;
...@@ -909,15 +1039,14 @@ static void usbhid_stop(struct hid_device *hid) ...@@ -909,15 +1039,14 @@ static void usbhid_stop(struct hid_device *hid)
return; return;
clear_bit(HID_STARTED, &usbhid->iofl); clear_bit(HID_STARTED, &usbhid->iofl);
spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ spin_lock_irq(&usbhid->lock); /* Sync with error handler */
set_bit(HID_DISCONNECTED, &usbhid->iofl); set_bit(HID_DISCONNECTED, &usbhid->iofl);
spin_unlock_irq(&usbhid->inlock); spin_unlock_irq(&usbhid->lock);
usb_kill_urb(usbhid->urbin); usb_kill_urb(usbhid->urbin);
usb_kill_urb(usbhid->urbout); usb_kill_urb(usbhid->urbout);
usb_kill_urb(usbhid->urbctrl); usb_kill_urb(usbhid->urbctrl);
del_timer_sync(&usbhid->io_retry); hid_cancel_delayed_stuff(usbhid);
cancel_work_sync(&usbhid->reset_work);
if (hid->claimed & HID_CLAIMED_INPUT) if (hid->claimed & HID_CLAIMED_INPUT)
hidinput_disconnect(hid); hidinput_disconnect(hid);
...@@ -938,12 +1067,28 @@ static void usbhid_stop(struct hid_device *hid) ...@@ -938,12 +1067,28 @@ static void usbhid_stop(struct hid_device *hid)
hid_free_buffers(hid_to_usb_dev(hid), hid); hid_free_buffers(hid_to_usb_dev(hid), hid);
} }
static int usbhid_power(struct hid_device *hid, int lvl)
{
int r = 0;
switch (lvl) {
case PM_HINT_FULLON:
r = usbhid_get_power(hid);
break;
case PM_HINT_NORMAL:
usbhid_put_power(hid);
break;
}
return r;
}
static struct hid_ll_driver usb_hid_driver = { static struct hid_ll_driver usb_hid_driver = {
.parse = usbhid_parse, .parse = usbhid_parse,
.start = usbhid_start, .start = usbhid_start,
.stop = usbhid_stop, .stop = usbhid_stop,
.open = usbhid_open, .open = usbhid_open,
.close = usbhid_close, .close = usbhid_close,
.power = usbhid_power,
.hidinput_input_event = usb_hidinput_input_event, .hidinput_input_event = usb_hidinput_input_event,
}; };
...@@ -1052,19 +1197,126 @@ static void hid_disconnect(struct usb_interface *intf) ...@@ -1052,19 +1197,126 @@ static void hid_disconnect(struct usb_interface *intf)
kfree(usbhid); kfree(usbhid);
} }
static int hid_suspend(struct usb_interface *intf, pm_message_t message) static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid)
{ {
struct hid_device *hid = usb_get_intfdata (intf); del_timer_sync(&usbhid->io_retry);
cancel_work_sync(&usbhid->restart_work);
cancel_work_sync(&usbhid->reset_work);
}
static void hid_cease_io(struct usbhid_device *usbhid)
{
del_timer(&usbhid->io_retry);
usb_kill_urb(usbhid->urbin);
usb_kill_urb(usbhid->urbctrl);
usb_kill_urb(usbhid->urbout);
}
/* Treat USB reset pretty much the same as suspend/resume */
static int hid_pre_reset(struct usb_interface *intf)
{
struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
if (!test_bit(HID_STARTED, &usbhid->iofl)) spin_lock_irq(&usbhid->lock);
set_bit(HID_RESET_PENDING, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock);
cancel_work_sync(&usbhid->restart_work);
hid_cease_io(usbhid);
return 0; return 0;
}
spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ /* Same routine used for post_reset and reset_resume */
set_bit(HID_SUSPENDED, &usbhid->iofl); static int hid_post_reset(struct usb_interface *intf)
spin_unlock_irq(&usbhid->inlock); {
del_timer_sync(&usbhid->io_retry); struct usb_device *dev = interface_to_usbdev (intf);
usb_kill_urb(usbhid->urbin); struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid = hid->driver_data;
int status;
spin_lock_irq(&usbhid->lock);
clear_bit(HID_RESET_PENDING, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock);
hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0);
/* FIXME: Any more reinitialization needed? */
status = hid_start_in(hid);
if (status < 0)
hid_io_error(hid);
usbhid_restart_queues(usbhid);
return 0;
}
int usbhid_get_power(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
return usb_autopm_get_interface(usbhid->intf);
}
void usbhid_put_power(struct hid_device *hid)
{
struct usbhid_device *usbhid = hid->driver_data;
usb_autopm_put_interface(usbhid->intf);
}
#ifdef CONFIG_PM
static int hid_suspend(struct usb_interface *intf, pm_message_t message)
{
struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid = hid->driver_data;
struct usb_device *udev = interface_to_usbdev(intf);
int status;
if (udev->auto_pm) {
spin_lock_irq(&usbhid->lock); /* Sync with error handler */
if (!test_bit(HID_RESET_PENDING, &usbhid->iofl)
&& !test_bit(HID_CLEAR_HALT, &usbhid->iofl)
&& !test_bit(HID_OUT_RUNNING, &usbhid->iofl)
&& !test_bit(HID_CTRL_RUNNING, &usbhid->iofl)
&& !test_bit(HID_KEYS_PRESSED, &usbhid->iofl)
&& (!usbhid->ledcount || ignoreled))
{
set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock);
} else {
usbhid_mark_busy(usbhid);
spin_unlock_irq(&usbhid->lock);
return -EBUSY;
}
} else {
spin_lock_irq(&usbhid->lock);
set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
spin_unlock_irq(&usbhid->lock);
if (usbhid_wait_io(hid) < 0)
return -EIO;
}
if (!ignoreled && udev->auto_pm) {
spin_lock_irq(&usbhid->lock);
if (test_bit(HID_LED_ON, &usbhid->iofl)) {
spin_unlock_irq(&usbhid->lock);
usbhid_mark_busy(usbhid);
return -EBUSY;
}
spin_unlock_irq(&usbhid->lock);
}
hid_cancel_delayed_stuff(usbhid);
hid_cease_io(usbhid);
if (udev->auto_pm && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) {
/* lost race against keypresses */
status = hid_start_in(hid);
if (status < 0)
hid_io_error(hid);
usbhid_mark_busy(usbhid);
return -EBUSY;
}
dev_dbg(&intf->dev, "suspend\n"); dev_dbg(&intf->dev, "suspend\n");
return 0; return 0;
} }
...@@ -1078,32 +1330,33 @@ static int hid_resume(struct usb_interface *intf) ...@@ -1078,32 +1330,33 @@ static int hid_resume(struct usb_interface *intf)
if (!test_bit(HID_STARTED, &usbhid->iofl)) if (!test_bit(HID_STARTED, &usbhid->iofl))
return 0; return 0;
clear_bit(HID_SUSPENDED, &usbhid->iofl); clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
usbhid_mark_busy(usbhid);
if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
test_bit(HID_RESET_PENDING, &usbhid->iofl))
schedule_work(&usbhid->reset_work);
usbhid->retry_delay = 0; usbhid->retry_delay = 0;
status = hid_start_in(hid); status = hid_start_in(hid);
dev_dbg(&intf->dev, "resume status %d\n", status); if (status < 0)
return status; hid_io_error(hid);
} usbhid_restart_queues(usbhid);
/* Treat USB reset pretty much the same as suspend/resume */ dev_dbg(&intf->dev, "resume status %d\n", status);
static int hid_pre_reset(struct usb_interface *intf)
{
/* FIXME: What if the interface is already suspended? */
hid_suspend(intf, PMSG_ON);
return 0; return 0;
} }
/* Same routine used for post_reset and reset_resume */ static int hid_reset_resume(struct usb_interface *intf)
static int hid_post_reset(struct usb_interface *intf)
{ {
struct usb_device *dev = interface_to_usbdev (intf); struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid = hid->driver_data;
hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0);
/* FIXME: Any more reinitialization needed? */
return hid_resume(intf); clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
return hid_post_reset(intf);
} }
#endif /* CONFIG_PM */
static struct usb_device_id hid_usb_ids [] = { static struct usb_device_id hid_usb_ids [] = {
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_INTERFACE_CLASS_HID }, .bInterfaceClass = USB_INTERFACE_CLASS_HID },
...@@ -1116,9 +1369,11 @@ static struct usb_driver hid_driver = { ...@@ -1116,9 +1369,11 @@ static struct usb_driver hid_driver = {
.name = "usbhid", .name = "usbhid",
.probe = hid_probe, .probe = hid_probe,
.disconnect = hid_disconnect, .disconnect = hid_disconnect,
#ifdef CONFIG_PM
.suspend = hid_suspend, .suspend = hid_suspend,
.resume = hid_resume, .resume = hid_resume,
.reset_resume = hid_post_reset, .reset_resume = hid_reset_resume,
#endif
.pre_reset = hid_pre_reset, .pre_reset = hid_pre_reset,
.post_reset = hid_post_reset, .post_reset = hid_post_reset,
.id_table = hid_usb_ids, .id_table = hid_usb_ids,
...@@ -1137,7 +1392,11 @@ static struct hid_driver hid_usb_driver = { ...@@ -1137,7 +1392,11 @@ static struct hid_driver hid_usb_driver = {
static int __init hid_init(void) static int __init hid_init(void)
{ {
int retval; int retval = -ENOMEM;
resumption_waker = create_freezeable_workqueue("usbhid_resumer");
if (!resumption_waker)
goto no_queue;
retval = hid_register_driver(&hid_usb_driver); retval = hid_register_driver(&hid_usb_driver);
if (retval) if (retval)
goto hid_register_fail; goto hid_register_fail;
...@@ -1161,6 +1420,8 @@ static int __init hid_init(void) ...@@ -1161,6 +1420,8 @@ static int __init hid_init(void)
usbhid_quirks_init_fail: usbhid_quirks_init_fail:
hid_unregister_driver(&hid_usb_driver); hid_unregister_driver(&hid_usb_driver);
hid_register_fail: hid_register_fail:
destroy_workqueue(resumption_waker);
no_queue:
return retval; return retval;
} }
...@@ -1170,6 +1431,7 @@ static void __exit hid_exit(void) ...@@ -1170,6 +1431,7 @@ static void __exit hid_exit(void)
hiddev_exit(); hiddev_exit();
usbhid_quirks_exit(); usbhid_quirks_exit();
hid_unregister_driver(&hid_usb_driver); hid_unregister_driver(&hid_usb_driver);
destroy_workqueue(resumption_waker);
} }
module_init(hid_init); module_init(hid_init);
......
...@@ -246,11 +246,13 @@ static int hiddev_release(struct inode * inode, struct file * file) ...@@ -246,11 +246,13 @@ static int hiddev_release(struct inode * inode, struct file * file)
spin_unlock_irqrestore(&list->hiddev->list_lock, flags); spin_unlock_irqrestore(&list->hiddev->list_lock, flags);
if (!--list->hiddev->open) { if (!--list->hiddev->open) {
if (list->hiddev->exist) if (list->hiddev->exist) {
usbhid_close(list->hiddev->hid); usbhid_close(list->hiddev->hid);
else usbhid_put_power(list->hiddev->hid);
} else {
kfree(list->hiddev); kfree(list->hiddev);
} }
}
kfree(list); kfree(list);
...@@ -300,6 +302,17 @@ static int hiddev_open(struct inode *inode, struct file *file) ...@@ -300,6 +302,17 @@ static int hiddev_open(struct inode *inode, struct file *file)
list_add_tail(&list->node, &hiddev_table[i]->list); list_add_tail(&list->node, &hiddev_table[i]->list);
spin_unlock_irq(&list->hiddev->list_lock); spin_unlock_irq(&list->hiddev->list_lock);
if (!list->hiddev->open++)
if (list->hiddev->exist) {
struct hid_device *hid = hiddev_table[i]->hid;
res = usbhid_get_power(hid);
if (res < 0) {
res = -EIO;
goto bail;
}
usbhid_open(hid);
}
return 0; return 0;
bail: bail:
file->private_data = NULL; file->private_data = NULL;
......
...@@ -38,7 +38,10 @@ int usbhid_wait_io(struct hid_device* hid); ...@@ -38,7 +38,10 @@ int usbhid_wait_io(struct hid_device* hid);
void usbhid_close(struct hid_device *hid); void usbhid_close(struct hid_device *hid);
int usbhid_open(struct hid_device *hid); int usbhid_open(struct hid_device *hid);
void usbhid_init_reports(struct hid_device *hid); void usbhid_init_reports(struct hid_device *hid);
void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir); void usbhid_submit_report
(struct hid_device *hid, struct hid_report *report, unsigned char dir);
int usbhid_get_power(struct hid_device *hid);
void usbhid_put_power(struct hid_device *hid);
/* iofl flags */ /* iofl flags */
#define HID_CTRL_RUNNING 1 #define HID_CTRL_RUNNING 1
...@@ -49,6 +52,9 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns ...@@ -49,6 +52,9 @@ void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, uns
#define HID_CLEAR_HALT 6 #define HID_CLEAR_HALT 6
#define HID_DISCONNECTED 7 #define HID_DISCONNECTED 7
#define HID_STARTED 8 #define HID_STARTED 8
#define HID_REPORTED_IDLE 9
#define HID_KEYS_PRESSED 10
#define HID_LED_ON 11
/* /*
* USB-specific HID struct, to be pointed to * USB-specific HID struct, to be pointed to
...@@ -66,7 +72,6 @@ struct usbhid_device { ...@@ -66,7 +72,6 @@ struct usbhid_device {
struct urb *urbin; /* Input URB */ struct urb *urbin; /* Input URB */
char *inbuf; /* Input buffer */ char *inbuf; /* Input buffer */
dma_addr_t inbuf_dma; /* Input buffer dma */ dma_addr_t inbuf_dma; /* Input buffer dma */
spinlock_t inlock; /* Input fifo spinlock */
struct urb *urbctrl; /* Control URB */ struct urb *urbctrl; /* Control URB */
struct usb_ctrlrequest *cr; /* Control request struct */ struct usb_ctrlrequest *cr; /* Control request struct */
...@@ -75,21 +80,22 @@ struct usbhid_device { ...@@ -75,21 +80,22 @@ struct usbhid_device {
unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */ unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */
char *ctrlbuf; /* Control buffer */ char *ctrlbuf; /* Control buffer */
dma_addr_t ctrlbuf_dma; /* Control buffer dma */ dma_addr_t ctrlbuf_dma; /* Control buffer dma */
spinlock_t ctrllock; /* Control fifo spinlock */
struct urb *urbout; /* Output URB */ struct urb *urbout; /* Output URB */
struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */ struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */
unsigned char outhead, outtail; /* Output pipe fifo head & tail */ unsigned char outhead, outtail; /* Output pipe fifo head & tail */
char *outbuf; /* Output buffer */ char *outbuf; /* Output buffer */
dma_addr_t outbuf_dma; /* Output buffer dma */ dma_addr_t outbuf_dma; /* Output buffer dma */
spinlock_t outlock; /* Output fifo spinlock */
spinlock_t lock; /* fifo spinlock */
unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
struct timer_list io_retry; /* Retry timer */ struct timer_list io_retry; /* Retry timer */
unsigned long stop_retry; /* Time to give up, in jiffies */ unsigned long stop_retry; /* Time to give up, in jiffies */
unsigned int retry_delay; /* Delay length in ms */ unsigned int retry_delay; /* Delay length in ms */
struct work_struct reset_work; /* Task context for resets */ struct work_struct reset_work; /* Task context for resets */
struct work_struct restart_work; /* waking up for output to be done in a task */
wait_queue_head_t wait; /* For sleeping */ wait_queue_head_t wait; /* For sleeping */
int ledcount; /* counting the number of active leds */
}; };
#define hid_to_usb_dev(hid_dev) \ #define hid_to_usb_dev(hid_dev) \
......
...@@ -604,12 +604,17 @@ struct hid_ll_driver { ...@@ -604,12 +604,17 @@ struct hid_ll_driver {
int (*open)(struct hid_device *hdev); int (*open)(struct hid_device *hdev);
void (*close)(struct hid_device *hdev); void (*close)(struct hid_device *hdev);
int (*power)(struct hid_device *hdev, int level);
int (*hidinput_input_event) (struct input_dev *idev, unsigned int type, int (*hidinput_input_event) (struct input_dev *idev, unsigned int type,
unsigned int code, int value); unsigned int code, int value);
int (*parse)(struct hid_device *hdev); int (*parse)(struct hid_device *hdev);
}; };
#define PM_HINT_FULLON 1<<5
#define PM_HINT_NORMAL 1<<1
/* Applications from HID Usage Tables 4/8/99 Version 1.1 */ /* Applications from HID Usage Tables 4/8/99 Version 1.1 */
/* We ignore a few input applications that are not widely used */ /* We ignore a few input applications that are not widely used */
#define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || (a == 0x00010080) || (a == 0x000c0001) || (a == 0x000d0002)) #define IS_INPUT_APPLICATION(a) (((a >= 0x00010000) && (a <= 0x00010008)) || (a == 0x00010080) || (a == 0x000c0001) || (a == 0x000d0002))
...@@ -642,6 +647,7 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int ...@@ -642,6 +647,7 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int
void hid_output_report(struct hid_report *report, __u8 *data); void hid_output_report(struct hid_report *report, __u8 *data);
struct hid_device *hid_allocate_device(void); struct hid_device *hid_allocate_device(void);
int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size); int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size);
int hid_check_keys_pressed(struct hid_device *hid);
int hid_connect(struct hid_device *hid, unsigned int connect_mask); int hid_connect(struct hid_device *hid, unsigned int connect_mask);
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment