Commit 0361a28d authored by Oliver Neukum's avatar Oliver Neukum Committed by Jiri Kosina

HID: autosuspend support for USB HID

This uses the USB busy mechanism for aggessive autosuspend of USB
HID devices. It autosuspends all opened devices supporting remote wakeup
after a timeout unless

- output is being done to the device
- a key is being held down (remote wakeup isn't triggered upon key release)
- LED(s) are lit
- hiddev is opened

As in the current driver closed devices will be autosuspended even if they
don't support remote wakeup.

The patch is quite large because output to devices is done in hard interrupt
context meaning a lot a queuing and locking had to be touched. The LED stuff
has been solved by means of a simple counter. Additions to the generic HID code
could be avoided. In addition it now covers hidraw. It contains an embryonic
version of an API to let the generic HID code tell the lower levels which
capabilities with respect to power management are needed.
Signed-off-by: default avatarOliver Neukum <oneukum@suse.de>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 8e0ee43b
...@@ -1822,6 +1822,22 @@ static DECLARE_WORK(hid_compat_work, hid_compat_load); ...@@ -1822,6 +1822,22 @@ static DECLARE_WORK(hid_compat_work, hid_compat_load);
static struct workqueue_struct *hid_compat_wq; static struct workqueue_struct *hid_compat_wq;
#endif #endif
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,9 +181,17 @@ static int hidraw_open(struct inode *inode, struct file *file) ...@@ -181,9 +181,17 @@ static int hidraw_open(struct inode *inode, struct file *file)
dev = hidraw_table[minor]; dev = hidraw_table[minor];
if (!dev->open++) { if (!dev->open++) {
if (dev->hid->ll_driver->power) {
err = dev->hid->ll_driver->power(dev->hid, PM_HINT_FULLON);
if (err < 0)
goto out_unlock;
}
err = dev->hid->ll_driver->open(dev->hid); err = dev->hid->ll_driver->open(dev->hid);
if (err < 0) 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:
...@@ -209,10 +217,13 @@ static int hidraw_release(struct inode * inode, struct file * file) ...@@ -209,10 +217,13 @@ 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);
......
This diff is collapsed.
...@@ -249,10 +249,12 @@ static int hiddev_release(struct inode * inode, struct file * file) ...@@ -249,10 +249,12 @@ 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);
...@@ -303,6 +305,17 @@ static int hiddev_open(struct inode *inode, struct file *file) ...@@ -303,6 +305,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) \
......
...@@ -603,12 +603,17 @@ struct hid_ll_driver { ...@@ -603,12 +603,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))
...@@ -641,6 +646,7 @@ int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int ...@@ -641,6 +646,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