Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
[cascardo/linux.git] / net / bluetooth / hidp / core.c
index f13a8da..0c699cd 100644 (file)
@@ -844,6 +844,29 @@ static void hidp_session_dev_del(struct hidp_session *session)
                input_unregister_device(session->input);
 }
 
+/*
+ * Asynchronous device registration
+ * HID device drivers might want to perform I/O during initialization to
+ * detect device types. Therefore, call device registration in a separate
+ * worker so the HIDP thread can schedule I/O operations.
+ * Note that this must be called after the worker thread was initialized
+ * successfully. This will then add the devices and increase session state
+ * on success, otherwise it will terminate the session thread.
+ */
+static void hidp_session_dev_work(struct work_struct *work)
+{
+       struct hidp_session *session = container_of(work,
+                                                   struct hidp_session,
+                                                   dev_init);
+       int ret;
+
+       ret = hidp_session_dev_add(session);
+       if (!ret)
+               atomic_inc(&session->state);
+       else
+               hidp_session_terminate(session);
+}
+
 /*
  * Create new session object
  * Allocate session object, initialize static fields, copy input data into the
@@ -891,6 +914,7 @@ static int hidp_session_new(struct hidp_session **out, const bdaddr_t *bdaddr,
        session->idle_to = req->idle_to;
 
        /* device management */
+       INIT_WORK(&session->dev_init, hidp_session_dev_work);
        setup_timer(&session->timer, hidp_idle_timeout,
                    (unsigned long)session);
 
@@ -1029,8 +1053,8 @@ static void hidp_session_terminate(struct hidp_session *session)
  * Probe HIDP session
  * This is called from the l2cap_conn core when our l2cap_user object is bound
  * to the hci-connection. We get the session via the \user object and can now
- * start the session thread, register the HID/input devices and link it into
- * the global session list.
+ * start the session thread, link it into the global session list and
+ * schedule HID/input device registration.
  * The global session-list owns its own reference to the session object so you
  * can drop your own reference after registering the l2cap_user object.
  */
@@ -1052,21 +1076,30 @@ static int hidp_session_probe(struct l2cap_conn *conn,
                goto out_unlock;
        }
 
+       if (session->input) {
+               ret = hidp_session_dev_add(session);
+               if (ret)
+                       goto out_unlock;
+       }
+
        ret = hidp_session_start_sync(session);
        if (ret)
-               goto out_unlock;
+               goto out_del;
 
-       ret = hidp_session_dev_add(session);
-       if (ret)
-               goto out_stop;
+       /* HID device registration is async to allow I/O during probe */
+       if (session->input)
+               atomic_inc(&session->state);
+       else
+               schedule_work(&session->dev_init);
 
        hidp_session_get(session);
        list_add(&session->list, &hidp_session_list);
        ret = 0;
        goto out_unlock;
 
-out_stop:
-       hidp_session_terminate(session);
+out_del:
+       if (session->input)
+               hidp_session_dev_del(session);
 out_unlock:
        up_write(&hidp_session_sem);
        return ret;
@@ -1096,7 +1129,12 @@ static void hidp_session_remove(struct l2cap_conn *conn,
        down_write(&hidp_session_sem);
 
        hidp_session_terminate(session);
-       hidp_session_dev_del(session);
+
+       cancel_work_sync(&session->dev_init);
+       if (session->input ||
+           atomic_read(&session->state) > HIDP_SESSION_PREPARING)
+               hidp_session_dev_del(session);
+
        list_del(&session->list);
 
        up_write(&hidp_session_sem);