From 1caea61eda5c4d446147aa0e712ba395bb6b81c3 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 18 Mar 2010 12:09:53 +0100 Subject: [PATCH 1/7] HID: output event in debugfs even if hid_get_report() fails if hid_get_report() fails for whatever reason, the raw output of the report doesn't make it into 'events' file in debugfs at all, because we leave hid_input_report() too soon. We want the report to be always present for debugging reasons. Move the code around, so that the event makes it to 'events' file all the time, even if we are going to discard the report. Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 368fbb0c4ca6..c49aaa21e1a7 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1096,20 +1096,11 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i goto nomem; } - snprintf(buf, HID_DEBUG_BUFSIZE - 1, - "\nreport (size %u) (%snumbered)\n", size, report_enum->numbered ? "" : "un"); - hid_debug_event(hid, buf); - - report = hid_get_report(report_enum, data); - if (!report) { - kfree(buf); - return -1; - } - /* dump the report */ snprintf(buf, HID_DEBUG_BUFSIZE - 1, - "report %d (size %u) = ", report->id, size); + "\nreport (size %u) (%snumbered) = ", size, report_enum->numbered ? "" : "un"); hid_debug_event(hid, buf); + for (i = 0; i < size; i++) { snprintf(buf, HID_DEBUG_BUFSIZE - 1, " %02x", data[i]); @@ -1117,6 +1108,13 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i } hid_debug_event(hid, "\n"); + report = hid_get_report(report_enum, data); + + if (!report) { + kfree(buf); + return -1; + } + kfree(buf); nomem: From f77e347bd44e3640bdc56003b7402c63ddb1241d Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 18 Mar 2010 14:11:53 +0100 Subject: [PATCH 2/7] HID: simplify error handling in hid_input_report() The handling of failed debugging buffer allocation got overly complicated. We simply want to skip the debugging code if allocation fails and go on with event processing. Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index c49aaa21e1a7..86cb2c47e9ea 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1091,10 +1091,8 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); - if (!buf) { - report = hid_get_report(report_enum, data); + if (!buf) goto nomem; - } /* dump the report */ snprintf(buf, HID_DEBUG_BUFSIZE - 1, @@ -1107,17 +1105,14 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i hid_debug_event(hid, buf); } hid_debug_event(hid, "\n"); - - report = hid_get_report(report_enum, data); - - if (!report) { - kfree(buf); - return -1; - } - kfree(buf); nomem: + report = hid_get_report(report_enum, data); + + if (!report) + return -1; + if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) { ret = hdrv->raw_event(hid, report, data, size); if (ret != 0) From 2e57480b2a717916510b0c23b2851398a4cbd958 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 14:15:11 +0100 Subject: [PATCH 3/7] HID: remove BKL from hidraw Remove BKL from hidraw, which is possible through fixing the locking of minors_lock mutex properly -- it is now used to guard all accessess to hidraw_table[], preventing it to becoming NULL unexpectedly by unregistering the device. Signed-off-by: Jiri Kosina --- drivers/hid/hidraw.c | 51 ++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index d04476700b7b..589dac5b5f56 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -105,38 +105,48 @@ out: static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { unsigned int minor = iminor(file->f_path.dentry->d_inode); - /* FIXME: What stops hidraw_table going NULL */ - struct hid_device *dev = hidraw_table[minor]->hid; + struct hid_device *dev; __u8 *buf; int ret = 0; - if (!dev->hid_output_raw_report) - return -ENODEV; + mutex_lock(&minors_lock); + dev = hidraw_table[minor]->hid; + + if (!dev->hid_output_raw_report) { + ret = -ENODEV; + goto out; + } if (count > HID_MAX_BUFFER_SIZE) { printk(KERN_WARNING "hidraw: pid %d passed too large report\n", task_pid_nr(current)); - return -EINVAL; + ret = -EINVAL; + goto out; } if (count < 2) { printk(KERN_WARNING "hidraw: pid %d passed too short report\n", task_pid_nr(current)); - return -EINVAL; - } - - buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (copy_from_user(buf, buffer, count)) { - ret = -EFAULT; + ret = -EINVAL; goto out; } + buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(buf, buffer, count)) { + ret = -EFAULT; + goto out_free; + } + ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); -out: +out_free: kfree(buf); +out: + mutex_unlock(&minors_lock); return ret; } @@ -164,7 +174,6 @@ static int hidraw_open(struct inode *inode, struct file *file) goto out; } - lock_kernel(); mutex_lock(&minors_lock); if (!hidraw_table[minor]) { printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", @@ -196,7 +205,6 @@ static int hidraw_open(struct inode *inode, struct file *file) out_unlock: mutex_unlock(&minors_lock); - unlock_kernel(); out: return err; @@ -237,11 +245,12 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, struct inode *inode = file->f_path.dentry->d_inode; unsigned int minor = iminor(inode); long ret = 0; - /* FIXME: What stops hidraw_table going NULL */ - struct hidraw *dev = hidraw_table[minor]; + struct hidraw *dev; void __user *user_arg = (void __user*) arg; - lock_kernel(); + mutex_lock(&minors_lock); + dev = hidraw_table[minor]; + switch (cmd) { case HIDIOCGRDESCSIZE: if (put_user(dev->hid->rsize, (int __user *)arg)) @@ -314,7 +323,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, ret = -ENOTTY; } - unlock_kernel(); + mutex_unlock(&minors_lock); return ret; } From 0a504541b3ba593535d70f3124546e5e471a175e Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 15:20:01 +0100 Subject: [PATCH 4/7] HID: remove excessive _EMERG messages from hidraw We don't need to shout loudly when device gets disconnected while hidraw node has been open, as this is properly handled in disconnect() and protected by minors_lock already. Signed-off-by: Jiri Kosina --- drivers/hid/hidraw.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 589dac5b5f56..7919d3e843b6 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -176,8 +176,6 @@ static int hidraw_open(struct inode *inode, struct file *file) mutex_lock(&minors_lock); if (!hidraw_table[minor]) { - printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", - minor); kfree(list); err = -ENODEV; goto out_unlock; @@ -216,11 +214,8 @@ static int hidraw_release(struct inode * inode, struct file * file) struct hidraw *dev; struct hidraw_list *list = file->private_data; - if (!hidraw_table[minor]) { - printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", - minor); + if (!hidraw_table[minor]) return -ENODEV; - } list_del(&list->node); dev = hidraw_table[minor]; From da54a0ced4502dc2a25df034f218463a2a50488d Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 15:34:34 +0100 Subject: [PATCH 5/7] HID: update BKL comment in hiddev Update comment explaining BKL usage in legacy hiddev driver. Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hiddev.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 433602aed468..c24d2fa3e3b6 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -267,6 +267,7 @@ static int hiddev_open(struct inode *inode, struct file *file) struct hiddev_list *list; int res, i; + /* See comment in hiddev_connect() for BKL explanation */ lock_kernel(); i = iminor(inode) - HIDDEV_MINOR_BASE; @@ -894,8 +895,22 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) hiddev->hid = hid; hiddev->exist = 1; - /* when lock_kernel() usage is fixed in usb_open(), - * we could also fix it here */ + /* + * BKL here is used to avoid race after usb_register_dev(). + * Once the device node has been created, open() could happen on it. + * The code below will then fail, as hiddev_table hasn't been + * updated. + * + * The obvious fix -- introducing mutex to guard hiddev_table[] + * doesn't work, as usb_open() and usb_register_dev() both take + * minor_rwsem, thus we'll have ABBA deadlock. + * + * Before BKL pushdown, usb_open() had been acquiring it in right + * order, so _open() was safe to use it to protect from this race. + * Now the order is different, but AB-BA deadlock still doesn't occur + * as BKL is dropped on schedule() (i.e. while sleeping on + * minor_rwsem). Fugly. + */ lock_kernel(); retval = usb_register_dev(usbhid->intf, &hiddev_class); if (retval) { From 6a740aa4f47b9f29bad5292cf51f008f3edad9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pr=C3=A9mont?= Date: Sun, 25 Apr 2010 21:40:03 +0200 Subject: [PATCH 6/7] HID: add suspend/resume hooks for hid drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add suspend/resume hooks for HID drivers so these can do some additional state adjustment when device gets suspended/resumed. Signed-off-by: Bruno Prémont Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 24 +++++++++++++++++++++++- include/linux/hid.h | 8 ++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 56d06cd8075b..14a67fba590e 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1290,6 +1290,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) { set_bit(HID_REPORTED_IDLE, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } } else { usbhid_mark_busy(usbhid); spin_unlock_irq(&usbhid->lock); @@ -1297,6 +1302,11 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) } } else { + if (hid->driver && hid->driver->suspend) { + status = hid->driver->suspend(hid, message); + if (status < 0) + return status; + } spin_lock_irq(&usbhid->lock); set_bit(HID_REPORTED_IDLE, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); @@ -1351,6 +1361,11 @@ static int hid_resume(struct usb_interface *intf) hid_io_error(hid); usbhid_restart_queues(usbhid); + if (status >= 0 && hid->driver && hid->driver->resume) { + int ret = hid->driver->resume(hid); + if (ret < 0) + status = ret; + } dev_dbg(&intf->dev, "resume status %d\n", status); return 0; } @@ -1359,9 +1374,16 @@ static int hid_reset_resume(struct usb_interface *intf) { struct hid_device *hid = usb_get_intfdata(intf); struct usbhid_device *usbhid = hid->driver_data; + int status; clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); - return hid_post_reset(intf); + status = hid_post_reset(intf); + if (status >= 0 && hid->driver && hid->driver->reset_resume) { + int ret = hid->driver->reset_resume(hid); + if (ret < 0) + status = ret; + } + return status; } #endif /* CONFIG_PM */ diff --git a/include/linux/hid.h b/include/linux/hid.h index b1344ec4b7fc..069e587ae8e6 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -589,6 +589,9 @@ struct hid_usage_id { * @report_fixup: called before report descriptor parsing (NULL means nop) * @input_mapping: invoked on input registering before mapping an usage * @input_mapped: invoked on input registering after mapping an usage + * @suspend: invoked on suspend (NULL means nop) + * @resume: invoked on resume if device was not reset (NULL means nop) + * @reset_resume: invoked on resume if device was reset (NULL means nop) * * raw_event and event should return 0 on no action performed, 1 when no * further processing should be done and negative on error @@ -629,6 +632,11 @@ struct hid_driver { int (*input_mapped)(struct hid_device *hdev, struct hid_input *hidinput, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max); +#ifdef CONFIG_PM + int (*suspend)(struct hid_device *hdev, pm_message_t message); + int (*resume)(struct hid_device *hdev); + int (*reset_resume)(struct hid_device *hdev); +#endif /* private: */ struct device_driver driver; }; From 23d021167eebf0df5ccadf4f8de5ccb8d4ac2904 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 12 May 2010 16:01:26 +0200 Subject: [PATCH 7/7] HID: magicmouse: fix input registration When HIDRAW is not set, hid_hw_start() returns ENODEV as no subsystem has claimed the magicmouse device, and probe routine bails out. Which is not what we want. This happens because magicmouse driver is instantiating the connection to Input subsystem itself, and since commit 28918c211d86b ("HID: magicmouse: fix oops after device removal") the HID core is not registering input device itself. Fix this by letting HID core register the input device (so that hid_hw_start() succeeds, as the device is claimed by at least one subsystem) and de-register it again later before proceeding with proper input setup. Reported-by: Justin P. Mattock Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 0d471fc2ab82..f10d56a15f21 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -354,12 +354,15 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_free; } - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_HIDINPUT); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { dev_err(&hdev->dev, "magicmouse hw start failed\n"); goto err_free; } + /* we are handling the input ourselves */ + hidinput_disconnect(hdev); + report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); if (!report) { dev_err(&hdev->dev, "unable to register touch report\n");