From a5f04b9df1113e0c16271afe5e43028f0d763f13 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 17 Apr 2013 19:38:13 +0200 Subject: HID: debug: break out hid_dump_report() into hid-debug No semantic changes, but hid_dump_report should be in hid-debug.c, not in hid-core.c Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-debug.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'drivers/hid/hid-debug.c') diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 933fff0fff1..094cbcfe1e1 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -591,6 +591,36 @@ void hid_debug_event(struct hid_device *hdev, char *buf) } EXPORT_SYMBOL_GPL(hid_debug_event); +void hid_dump_report(struct hid_device *hid, int type, u8 *data, + int size) +{ + struct hid_report_enum *report_enum; + char *buf; + unsigned int i; + + buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); + + if (!buf) + return; + + report_enum = hid->report_enum + type; + + /* dump the report */ + snprintf(buf, HID_DEBUG_BUFSIZE - 1, + "\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]); + hid_debug_event(hid, buf); + } + hid_debug_event(hid, "\n"); + kfree(buf); +} +EXPORT_SYMBOL_GPL(hid_dump_report); + void hid_dump_input(struct hid_device *hdev, struct hid_usage *usage, __s32 value) { char *buf; -- cgit v1.2.3-70-g09d2 From 2353f2bea307390e015493118e425152b8a5a431 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Tue, 16 Apr 2013 15:40:09 -0700 Subject: HID: protect hid_debug_list Accesses to hid_device->hid_debug_list are not serialized properly, which could result in SMP concurrency issues when HID debugfs events are accessesed by multiple userspace processess. Serialize all the list operations by a mutex. Spotted by Al Viro. Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-debug.c | 6 ++++++ include/linux/hid.h | 1 + 3 files changed, 8 insertions(+) (limited to 'drivers/hid/hid-debug.c') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f86dd9708ca..e7765ede339 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2319,6 +2319,7 @@ struct hid_device *hid_allocate_device(void) init_waitqueue_head(&hdev->debug_wait); INIT_LIST_HEAD(&hdev->debug_list); + mutex_init(&hdev->debug_list_lock); sema_init(&hdev->driver_lock, 1); return hdev; diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 094cbcfe1e1..7e56cb3855e 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -580,12 +580,14 @@ void hid_debug_event(struct hid_device *hdev, char *buf) int i; struct hid_debug_list *list; + mutex_lock(&hdev->debug_list_lock); list_for_each_entry(list, &hdev->debug_list, node) { for (i = 0; i < strlen(buf); i++) list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] = buf[i]; list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE; } + mutex_unlock(&hdev->debug_list_lock); wake_up_interruptible(&hdev->debug_wait); } @@ -990,7 +992,9 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) file->private_data = list; mutex_init(&list->read_mutex); + mutex_lock(&list->hdev->debug_list_lock); list_add_tail(&list->node, &list->hdev->debug_list); + mutex_unlock(&list->hdev->debug_list_lock); out: return err; @@ -1085,7 +1089,9 @@ static int hid_debug_events_release(struct inode *inode, struct file *file) { struct hid_debug_list *list = file->private_data; + mutex_lock(&list->hdev->debug_list_lock); list_del(&list->node); + mutex_unlock(&list->hdev->debug_list_lock); kfree(list->hid_debug_buf); kfree(list); diff --git a/include/linux/hid.h b/include/linux/hid.h index e14b465b114..06579c72d19 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -512,6 +512,7 @@ struct hid_device { /* device report descriptor */ struct dentry *debug_rdesc; struct dentry *debug_events; struct list_head debug_list; + struct mutex debug_list_lock; wait_queue_head_t debug_wait; }; -- cgit v1.2.3-70-g09d2 From 1deb9d341d475ff84262e927d6c0e36fecb9942e Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 6 May 2013 13:05:50 +0200 Subject: HID: debug: fix RCU preemption issue Commit 2353f2bea ("HID: protect hid_debug_list") introduced mutex locking around debug_list access to prevent SMP races when debugfs nodes are being operated upon by multiple userspace processess. mutex is not a proper synchronization primitive though, as the hid-debug callbacks are being called from atomic contexts. We also have to be careful about disabling IRQs when taking the lock to prevent deadlock against IRQ handlers. Benjamin reports this has also been reported in RH bugzilla as bug #958935. =============================== [ INFO: suspicious RCU usage. ] 3.9.0+ #94 Not tainted ------------------------------- include/linux/rcupdate.h:476 Illegal context switch in RCU read-side critical section! other info that might help us debug this: rcu_scheduler_active = 1, debug_locks = 0 4 locks held by Xorg/5502: #0: (&evdev->mutex){+.+...}, at: [] evdev_write+0x6d/0x160 #1: (&(&dev->event_lock)->rlock#2){-.-...}, at: [] input_inject_event+0x5b/0x230 #2: (rcu_read_lock){.+.+..}, at: [] input_inject_event+0x42/0x230 #3: (&(&usbhid->lock)->rlock){-.....}, at: [] usb_hidinput_input_event+0x89/0x120 stack backtrace: CPU: 0 PID: 5502 Comm: Xorg Not tainted 3.9.0+ #94 Hardware name: Dell Inc. OptiPlex 390/0M5DCD, BIOS A09 07/24/2012 0000000000000001 ffff8800689c7c38 ffffffff816f249f ffff8800689c7c68 ffffffff810acb1d 0000000000000000 ffffffff81a03ac7 000000000000019d 0000000000000000 ffff8800689c7c90 ffffffff8107cda7 0000000000000000 Call Trace: [] dump_stack+0x19/0x1b [] lockdep_rcu_suspicious+0xfd/0x130 [] __might_sleep+0xc7/0x230 [] mutex_lock_nested+0x40/0x3a0 [] ? vsnprintf+0x354/0x640 [] hid_debug_event+0x34/0x100 [] hid_dump_input+0x67/0xa0 [] hid_set_field+0x50/0x120 [] usb_hidinput_input_event+0x9a/0x120 [] input_handle_event+0x8e/0x530 [] input_inject_event+0x1d0/0x230 [] ? input_inject_event+0x42/0x230 [] evdev_write+0xde/0x160 [] vfs_write+0xc8/0x1f0 [] SyS_write+0x55/0xa0 [] system_call_fastpath+0x16/0x1b BUG: sleeping function called from invalid context at kernel/mutex.c:413 in_atomic(): 1, irqs_disabled(): 1, pid: 5502, name: Xorg INFO: lockdep is turned off. irq event stamp: 1098574 hardirqs last enabled at (1098573): [] _raw_spin_unlock_irqrestore+0x3f/0x70 hardirqs last disabled at (1098574): [] _raw_spin_lock_irqsave+0x25/0xa0 softirqs last enabled at (1098306): [] __do_softirq+0x18f/0x3c0 softirqs last disabled at (1097867): [] irq_exit+0xa5/0xb0 CPU: 0 PID: 5502 Comm: Xorg Not tainted 3.9.0+ #94 Hardware name: Dell Inc. OptiPlex 390/0M5DCD, BIOS A09 07/24/2012 ffffffff81a03ac7 ffff8800689c7c68 ffffffff816f249f ffff8800689c7c90 ffffffff8107ce60 0000000000000000 ffff8800689c7fd8 ffff88006a62c800 ffff8800689c7d10 ffffffff816f7770 ffff8800689c7d00 ffffffff81312ac4 Call Trace: [] dump_stack+0x19/0x1b [] __might_sleep+0x180/0x230 [] mutex_lock_nested+0x40/0x3a0 [] ? vsnprintf+0x354/0x640 [] hid_debug_event+0x34/0x100 [] hid_dump_input+0x67/0xa0 [] hid_set_field+0x50/0x120 [] usb_hidinput_input_event+0x9a/0x120 [] input_handle_event+0x8e/0x530 [] input_inject_event+0x1d0/0x230 [] ? input_inject_event+0x42/0x230 [] evdev_write+0xde/0x160 [] vfs_write+0xc8/0x1f0 [] SyS_write+0x55/0xa0 [] system_call_fastpath+0x16/0x1b Reported-by: majianpeng Reported-by: Benjamin Tissoires Reviewed-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 2 +- drivers/hid/hid-debug.c | 15 +++++++++------ include/linux/hid.h | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/hid/hid-debug.c') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 1e51159c310..264f5509994 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2342,7 +2342,7 @@ struct hid_device *hid_allocate_device(void) init_waitqueue_head(&hdev->debug_wait); INIT_LIST_HEAD(&hdev->debug_list); - mutex_init(&hdev->debug_list_lock); + spin_lock_init(&hdev->debug_list_lock); sema_init(&hdev->driver_lock, 1); sema_init(&hdev->driver_input_lock, 1); diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 7e56cb3855e..8453214ec37 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -579,15 +579,16 @@ void hid_debug_event(struct hid_device *hdev, char *buf) { int i; struct hid_debug_list *list; + unsigned long flags; - mutex_lock(&hdev->debug_list_lock); + spin_lock_irqsave(&hdev->debug_list_lock, flags); list_for_each_entry(list, &hdev->debug_list, node) { for (i = 0; i < strlen(buf); i++) list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] = buf[i]; list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE; } - mutex_unlock(&hdev->debug_list_lock); + spin_unlock_irqrestore(&hdev->debug_list_lock, flags); wake_up_interruptible(&hdev->debug_wait); } @@ -977,6 +978,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) { int err = 0; struct hid_debug_list *list; + unsigned long flags; if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) { err = -ENOMEM; @@ -992,9 +994,9 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) file->private_data = list; mutex_init(&list->read_mutex); - mutex_lock(&list->hdev->debug_list_lock); + spin_lock_irqsave(&list->hdev->debug_list_lock, flags); list_add_tail(&list->node, &list->hdev->debug_list); - mutex_unlock(&list->hdev->debug_list_lock); + spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags); out: return err; @@ -1088,10 +1090,11 @@ static unsigned int hid_debug_events_poll(struct file *file, poll_table *wait) static int hid_debug_events_release(struct inode *inode, struct file *file) { struct hid_debug_list *list = file->private_data; + unsigned long flags; - mutex_lock(&list->hdev->debug_list_lock); + spin_lock_irqsave(&list->hdev->debug_list_lock, flags); list_del(&list->node); - mutex_unlock(&list->hdev->debug_list_lock); + spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags); kfree(list->hid_debug_buf); kfree(list); diff --git a/include/linux/hid.h b/include/linux/hid.h index af1b86d46f6..0c48991b040 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -515,7 +515,7 @@ struct hid_device { /* device report descriptor */ struct dentry *debug_rdesc; struct dentry *debug_events; struct list_head debug_list; - struct mutex debug_list_lock; + spinlock_t debug_list_lock; wait_queue_head_t debug_wait; }; -- cgit v1.2.3-70-g09d2