Bluetooth: Restore running state if suspend fails
authorAbhishek Pandit-Subedi <abhishekpandit@chromium.org>
Fri, 20 Mar 2020 00:07:12 +0000 (17:07 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Mon, 23 Mar 2020 17:48:50 +0000 (18:48 +0100)
If Bluetooth fails to enter the suspended state correctly, restore the
state to running (re-enabling scans). PM_POST_SUSPEND is only sent to
notifiers that successfully return from PM_PREPARE_SUSPEND notification
so we should recover gracefully if it fails.

Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
net/bluetooth/hci_core.c

index dbd2ad3a26eda5a2379940759c877a91aec03fae..2e7bc2da8371d68511ce2a46757e4e2b658050be 100644 (file)
@@ -3305,6 +3305,15 @@ static void hci_prepare_suspend(struct work_struct *work)
        hci_dev_unlock(hdev);
 }
 
+static int hci_change_suspend_state(struct hci_dev *hdev,
+                                   enum suspended_state next)
+{
+       hdev->suspend_state_next = next;
+       set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
+       queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
+       return hci_suspend_wait_event(hdev);
+}
+
 static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
                                void *data)
 {
@@ -3330,32 +3339,24 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
                 *    connectable (disabling scanning)
                 *  - Second, program event filter/whitelist and enable scan
                 */
-               hdev->suspend_state_next = BT_SUSPEND_DISCONNECT;
-               set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
-               queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
-               ret = hci_suspend_wait_event(hdev);
+               ret = hci_change_suspend_state(hdev, BT_SUSPEND_DISCONNECT);
 
-               /* If the disconnect portion failed, don't attempt to complete
-                * by configuring the whitelist. The suspend notifier will
-                * follow a cancelled suspend with a PM_POST_SUSPEND
-                * notification.
-                */
-               if (!ret) {
-                       hdev->suspend_state_next = BT_SUSPEND_COMPLETE;
-                       set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
-                       queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
-                       ret = hci_suspend_wait_event(hdev);
-               }
+               /* Only configure whitelist if disconnect succeeded */
+               if (!ret)
+                       ret = hci_change_suspend_state(hdev,
+                                                      BT_SUSPEND_COMPLETE);
        } else if (action == PM_POST_SUSPEND) {
-               hdev->suspend_state_next = BT_RUNNING;
-               set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
-               queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
-               ret = hci_suspend_wait_event(hdev);
+               ret = hci_change_suspend_state(hdev, BT_RUNNING);
        }
 
+       /* If suspend failed, restore it to running */
+       if (ret && action == PM_SUSPEND_PREPARE)
+               hci_change_suspend_state(hdev, BT_RUNNING);
+
 done:
        return ret ? notifier_from_errno(-EBUSY) : NOTIFY_STOP;
 }
+
 /* Alloc HCI device */
 struct hci_dev *hci_alloc_dev(void)
 {