Merge branches 'for-4.8/alps', 'for-4.8/apple', 'for-4.8/i2c-hid', 'for-4.8/uhid...
[sfrench/cifs-2.6.git] / drivers / hid / uhid.c
index 16b6f11a07001c7831a987e6bd891b7d00868f65..99ec3ff7563b3e75ce0c050ba7d28064d445c7af 100644 (file)
@@ -51,10 +51,26 @@ struct uhid_device {
        u32 report_id;
        u32 report_type;
        struct uhid_event report_buf;
+       struct work_struct worker;
 };
 
 static struct miscdevice uhid_misc;
 
+static void uhid_device_add_worker(struct work_struct *work)
+{
+       struct uhid_device *uhid = container_of(work, struct uhid_device, worker);
+       int ret;
+
+       ret = hid_add_device(uhid->hid);
+       if (ret) {
+               hid_err(uhid->hid, "Cannot register HID device: error %d\n", ret);
+
+               hid_destroy_device(uhid->hid);
+               uhid->hid = NULL;
+               uhid->running = false;
+       }
+}
+
 static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
 {
        __u8 newhead;
@@ -498,18 +514,14 @@ static int uhid_dev_create2(struct uhid_device *uhid,
        uhid->hid = hid;
        uhid->running = true;
 
-       ret = hid_add_device(hid);
-       if (ret) {
-               hid_err(hid, "Cannot register HID device\n");
-               goto err_hid;
-       }
+       /* Adding of a HID device is done through a worker, to allow HID drivers
+        * which use feature requests during .probe to work, without they would
+        * be blocked on devlock, which is held by uhid_char_write.
+        */
+       schedule_work(&uhid->worker);
 
        return 0;
 
-err_hid:
-       hid_destroy_device(hid);
-       uhid->hid = NULL;
-       uhid->running = false;
 err_free:
        kfree(uhid->rd_data);
        uhid->rd_data = NULL;
@@ -550,6 +562,8 @@ static int uhid_dev_destroy(struct uhid_device *uhid)
        uhid->running = false;
        wake_up_interruptible(&uhid->report_wait);
 
+       cancel_work_sync(&uhid->worker);
+
        hid_destroy_device(uhid->hid);
        kfree(uhid->rd_data);
 
@@ -612,6 +626,7 @@ static int uhid_char_open(struct inode *inode, struct file *file)
        init_waitqueue_head(&uhid->waitq);
        init_waitqueue_head(&uhid->report_wait);
        uhid->running = false;
+       INIT_WORK(&uhid->worker, uhid_device_add_worker);
 
        file->private_data = uhid;
        nonseekable_open(inode, file);