Commit 3d5afd32 authored by Jiri Slaby's avatar Jiri Slaby Committed by Jiri Kosina

HID: fix oops during suspend of unbound HID devices

Usbhid structure is allocated on start invoked only from probe
of some driver. When there is no driver, the structure is null
and causes null-dereference oopses.

Fix it by allocating the structure on probe and disconnect of
the device itself. Also make sure we won't race between start
and resume or stop and suspend respectively.

References: http://bugzilla.kernel.org/show_bug.cgi?id=11827Signed-off-by: default avatarJiri Slaby <jirislaby@gmail.com>
Cc: Johannes Berg <johannes@sipsolutions.net>
Cc: Andreas Schwab <schwab@suse.de>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent f8d56f17
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
...@@ -776,21 +777,10 @@ static int usbhid_start(struct hid_device *hid) ...@@ -776,21 +777,10 @@ static int usbhid_start(struct hid_device *hid)
struct usb_interface *intf = to_usb_interface(hid->dev.parent); struct usb_interface *intf = to_usb_interface(hid->dev.parent);
struct usb_host_interface *interface = intf->cur_altsetting; struct usb_host_interface *interface = intf->cur_altsetting;
struct usb_device *dev = interface_to_usbdev(intf); struct usb_device *dev = interface_to_usbdev(intf);
struct usbhid_device *usbhid; struct usbhid_device *usbhid = hid->driver_data;
unsigned int n, insize = 0; unsigned int n, insize = 0;
int ret; int ret;
WARN_ON(hid->driver_data);
usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL);
if (usbhid == NULL) {
ret = -ENOMEM;
goto err;
}
hid->driver_data = usbhid;
usbhid->hid = hid;
usbhid->bufsize = HID_MIN_BUFFER_SIZE; usbhid->bufsize = HID_MIN_BUFFER_SIZE;
hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize); hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);
hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize); hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);
...@@ -804,6 +794,7 @@ static int usbhid_start(struct hid_device *hid) ...@@ -804,6 +794,7 @@ static int usbhid_start(struct hid_device *hid)
if (insize > HID_MAX_BUFFER_SIZE) if (insize > HID_MAX_BUFFER_SIZE)
insize = HID_MAX_BUFFER_SIZE; insize = HID_MAX_BUFFER_SIZE;
mutex_lock(&usbhid->setup);
if (hid_alloc_buffers(dev, hid)) { if (hid_alloc_buffers(dev, hid)) {
ret = -ENOMEM; ret = -ENOMEM;
goto fail; goto fail;
...@@ -888,6 +879,9 @@ static int usbhid_start(struct hid_device *hid) ...@@ -888,6 +879,9 @@ static int usbhid_start(struct hid_device *hid)
usbhid_init_reports(hid); usbhid_init_reports(hid);
hid_dump_device(hid); hid_dump_device(hid);
set_bit(HID_STARTED, &usbhid->iofl);
mutex_unlock(&usbhid->setup);
return 0; return 0;
fail: fail:
...@@ -895,8 +889,7 @@ static int usbhid_start(struct hid_device *hid) ...@@ -895,8 +889,7 @@ static int usbhid_start(struct hid_device *hid)
usb_free_urb(usbhid->urbout); usb_free_urb(usbhid->urbout);
usb_free_urb(usbhid->urbctrl); usb_free_urb(usbhid->urbctrl);
hid_free_buffers(dev, hid); hid_free_buffers(dev, hid);
kfree(usbhid); mutex_unlock(&usbhid->setup);
err:
return ret; return ret;
} }
...@@ -907,6 +900,8 @@ static void usbhid_stop(struct hid_device *hid) ...@@ -907,6 +900,8 @@ static void usbhid_stop(struct hid_device *hid)
if (WARN_ON(!usbhid)) if (WARN_ON(!usbhid))
return; return;
mutex_lock(&usbhid->setup);
clear_bit(HID_STARTED, &usbhid->iofl);
spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ spin_lock_irq(&usbhid->inlock); /* 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->inlock);
...@@ -931,8 +926,7 @@ static void usbhid_stop(struct hid_device *hid) ...@@ -931,8 +926,7 @@ static void usbhid_stop(struct hid_device *hid)
usb_free_urb(usbhid->urbout); usb_free_urb(usbhid->urbout);
hid_free_buffers(hid_to_usb_dev(hid), hid); hid_free_buffers(hid_to_usb_dev(hid), hid);
kfree(usbhid); mutex_unlock(&usbhid->setup);
hid->driver_data = NULL;
} }
static struct hid_ll_driver usb_hid_driver = { static struct hid_ll_driver usb_hid_driver = {
...@@ -947,6 +941,7 @@ static struct hid_ll_driver usb_hid_driver = { ...@@ -947,6 +941,7 @@ static struct hid_ll_driver usb_hid_driver = {
static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{ {
struct usb_device *dev = interface_to_usbdev(intf); struct usb_device *dev = interface_to_usbdev(intf);
struct usbhid_device *usbhid;
struct hid_device *hid; struct hid_device *hid;
size_t len; size_t len;
int ret; int ret;
...@@ -1000,14 +995,26 @@ static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1000,14 +995,26 @@ static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0) if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)
hid->uniq[0] = 0; hid->uniq[0] = 0;
usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL);
if (usbhid == NULL) {
ret = -ENOMEM;
goto err;
}
hid->driver_data = usbhid;
usbhid->hid = hid;
mutex_init(&usbhid->setup); /* needed on suspend/resume */
ret = hid_add_device(hid); ret = hid_add_device(hid);
if (ret) { if (ret) {
if (ret != -ENODEV) if (ret != -ENODEV)
dev_err(&intf->dev, "can't add hid device: %d\n", ret); dev_err(&intf->dev, "can't add hid device: %d\n", ret);
goto err; goto err_free;
} }
return 0; return 0;
err_free:
kfree(usbhid);
err: err:
hid_destroy_device(hid); hid_destroy_device(hid);
return ret; return ret;
...@@ -1016,11 +1023,14 @@ static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id) ...@@ -1016,11 +1023,14 @@ static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
static void hid_disconnect(struct usb_interface *intf) static void hid_disconnect(struct usb_interface *intf)
{ {
struct hid_device *hid = usb_get_intfdata(intf); struct hid_device *hid = usb_get_intfdata(intf);
struct usbhid_device *usbhid;
if (WARN_ON(!hid)) if (WARN_ON(!hid))
return; return;
usbhid = hid->driver_data;
hid_destroy_device(hid); hid_destroy_device(hid);
kfree(usbhid);
} }
static int hid_suspend(struct usb_interface *intf, pm_message_t message) static int hid_suspend(struct usb_interface *intf, pm_message_t message)
...@@ -1028,11 +1038,18 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) ...@@ -1028,11 +1038,18 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message)
struct hid_device *hid = usb_get_intfdata (intf); struct hid_device *hid = usb_get_intfdata (intf);
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
mutex_lock(&usbhid->setup);
if (!test_bit(HID_STARTED, &usbhid->iofl)) {
mutex_unlock(&usbhid->setup);
return 0;
}
spin_lock_irq(&usbhid->inlock); /* Sync with error handler */ spin_lock_irq(&usbhid->inlock); /* Sync with error handler */
set_bit(HID_SUSPENDED, &usbhid->iofl); set_bit(HID_SUSPENDED, &usbhid->iofl);
spin_unlock_irq(&usbhid->inlock); spin_unlock_irq(&usbhid->inlock);
del_timer(&usbhid->io_retry); del_timer(&usbhid->io_retry);
usb_kill_urb(usbhid->urbin); usb_kill_urb(usbhid->urbin);
mutex_unlock(&usbhid->setup);
dev_dbg(&intf->dev, "suspend\n"); dev_dbg(&intf->dev, "suspend\n");
return 0; return 0;
} }
...@@ -1043,9 +1060,16 @@ static int hid_resume(struct usb_interface *intf) ...@@ -1043,9 +1060,16 @@ static int hid_resume(struct usb_interface *intf)
struct usbhid_device *usbhid = hid->driver_data; struct usbhid_device *usbhid = hid->driver_data;
int status; int status;
mutex_lock(&usbhid->setup);
if (!test_bit(HID_STARTED, &usbhid->iofl)) {
mutex_unlock(&usbhid->setup);
return 0;
}
clear_bit(HID_SUSPENDED, &usbhid->iofl); clear_bit(HID_SUSPENDED, &usbhid->iofl);
usbhid->retry_delay = 0; usbhid->retry_delay = 0;
status = hid_start_in(hid); status = hid_start_in(hid);
mutex_unlock(&usbhid->setup);
dev_dbg(&intf->dev, "resume status %d\n", status); dev_dbg(&intf->dev, "resume status %d\n", status);
return status; return status;
} }
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/mutex.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/wait.h> #include <linux/wait.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
...@@ -73,6 +74,7 @@ struct usbhid_device { ...@@ -73,6 +74,7 @@ struct usbhid_device {
dma_addr_t outbuf_dma; /* Output buffer dma */ dma_addr_t outbuf_dma; /* Output buffer dma */
spinlock_t outlock; /* Output fifo spinlock */ spinlock_t outlock; /* Output fifo spinlock */
struct mutex setup;
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 */
......
...@@ -410,6 +410,7 @@ struct hid_output_fifo { ...@@ -410,6 +410,7 @@ struct hid_output_fifo {
#define HID_SUSPENDED 5 #define HID_SUSPENDED 5
#define HID_CLEAR_HALT 6 #define HID_CLEAR_HALT 6
#define HID_DISCONNECTED 7 #define HID_DISCONNECTED 7
#define HID_STARTED 8
struct hid_input { struct hid_input {
struct list_head list; struct list_head list;
......
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