From 1caea61eda5c4d446147aa0e712ba395bb6b81c3 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 18 Mar 2010 12:09:53 +0100 Subject: 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 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 368fbb0..c49aaa2 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: -- cgit v0.10.2 From f77e347bd44e3640bdc56003b7402c63ddb1241d Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 18 Mar 2010 14:11:53 +0100 Subject: 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 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index c49aaa2..86cb2c4 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"); + kfree(buf); +nomem: report = hid_get_report(report_enum, data); - if (!report) { - kfree(buf); + if (!report) return -1; - } - - kfree(buf); -nomem: if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) { ret = hdrv->raw_event(hid, report, data, size); if (ret != 0) -- cgit v0.10.2 From 2e57480b2a717916510b0c23b2851398a4cbd958 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 14:15:11 +0100 Subject: 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 diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index d044767..589dac5 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; + ret = -EINVAL; + goto out; } buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); - if (!buf) - return -ENOMEM; + if (!buf) { + ret = -ENOMEM; + goto out; + } if (copy_from_user(buf, buffer, count)) { ret = -EFAULT; - goto out; + 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; } -- cgit v0.10.2 From 0a504541b3ba593535d70f3124546e5e471a175e Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 15:20:01 +0100 Subject: 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 diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 589dac5..7919d3e 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]; -- cgit v0.10.2 From da54a0ced4502dc2a25df034f218463a2a50488d Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 25 Mar 2010 15:34:34 +0100 Subject: HID: update BKL comment in hiddev Update comment explaining BKL usage in legacy hiddev driver. Signed-off-by: Jiri Kosina diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 433602a..c24d2fa 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) { -- cgit v0.10.2 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: 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 diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 56d06cd..14a67fb 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 b1344ec..069e587 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; }; -- cgit v0.10.2 From 23d021167eebf0df5ccadf4f8de5ccb8d4ac2904 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 12 May 2010 16:01:26 +0200 Subject: 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 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 0d471fc2..f10d56a 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"); -- cgit v0.10.2