Bluetooth: Handle PM_SUSPEND_PREPARE and PM_POST_SUSPEND
authorAbhishek Pandit-Subedi <abhishekpandit@chromium.org>
Wed, 11 Mar 2020 15:54:00 +0000 (08:54 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Wed, 11 Mar 2020 17:00:48 +0000 (18:00 +0100)
Register for PM_SUSPEND_PREPARE and PM_POST_SUSPEND to make sure the
Bluetooth controller is prepared correctly for suspend/resume. Implement
the registration, scheduling and task handling portions only in this
patch.

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

index c498ac1..d6f694b 100644 (file)
@@ -88,6 +88,20 @@ struct discovery_state {
        unsigned long           scan_duration;
 };
 
+#define SUSPEND_NOTIFIER_TIMEOUT       msecs_to_jiffies(2000) /* 2 seconds */
+
+enum suspend_tasks {
+       SUSPEND_POWERING_DOWN,
+
+       SUSPEND_PREPARE_NOTIFIER,
+       __SUSPEND_NUM_TASKS
+};
+
+enum suspended_state {
+       BT_RUNNING = 0,
+       BT_SUSPENDED,
+};
+
 struct hci_conn_hash {
        struct list_head list;
        unsigned int     acl_num;
@@ -390,6 +404,15 @@ struct hci_dev {
        void                    *smp_bredr_data;
 
        struct discovery_state  discovery;
+
+       struct notifier_block   suspend_notifier;
+       struct work_struct      suspend_prepare;
+       enum suspended_state    suspend_state_next;
+       enum suspended_state    suspend_state;
+
+       wait_queue_head_t       suspend_wait_q;
+       DECLARE_BITMAP(suspend_tasks, __SUSPEND_NUM_TASKS);
+
        struct hci_conn_hash    conn_hash;
 
        struct list_head        mgmt_pending;
index 196edc0..39aa21a 100644 (file)
@@ -31,6 +31,8 @@
 #include <linux/debugfs.h>
 #include <linux/crypto.h>
 #include <linux/property.h>
+#include <linux/suspend.h>
+#include <linux/wait.h>
 #include <asm/unaligned.h>
 
 #include <net/bluetooth/bluetooth.h>
@@ -1787,6 +1789,9 @@ int hci_dev_do_close(struct hci_dev *hdev)
        clear_bit(HCI_RUNNING, &hdev->flags);
        hci_sock_dev_event(hdev, HCI_DEV_CLOSE);
 
+       if (test_and_clear_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks))
+               wake_up(&hdev->suspend_wait_q);
+
        /* After this point our queues are empty
         * and no tasks are scheduled. */
        hdev->close(hdev);
@@ -3264,6 +3269,78 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
        }
 }
 
+static int hci_suspend_wait_event(struct hci_dev *hdev)
+{
+#define WAKE_COND                                                              \
+       (find_first_bit(hdev->suspend_tasks, __SUSPEND_NUM_TASKS) ==           \
+        __SUSPEND_NUM_TASKS)
+
+       int i;
+       int ret = wait_event_timeout(hdev->suspend_wait_q,
+                                    WAKE_COND, SUSPEND_NOTIFIER_TIMEOUT);
+
+       if (ret == 0) {
+               bt_dev_dbg(hdev, "Timed out waiting for suspend");
+               for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) {
+                       if (test_bit(i, hdev->suspend_tasks))
+                               bt_dev_dbg(hdev, "Bit %d is set", i);
+                       clear_bit(i, hdev->suspend_tasks);
+               }
+
+               ret = -ETIMEDOUT;
+       } else {
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static void hci_prepare_suspend(struct work_struct *work)
+{
+       struct hci_dev *hdev =
+               container_of(work, struct hci_dev, suspend_prepare);
+
+       hci_dev_lock(hdev);
+       hci_req_prepare_suspend(hdev, hdev->suspend_state_next);
+       hci_dev_unlock(hdev);
+}
+
+static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
+                               void *data)
+{
+       struct hci_dev *hdev =
+               container_of(nb, struct hci_dev, suspend_notifier);
+       int ret = 0;
+
+       /* If powering down, wait for completion. */
+       if (mgmt_powering_down(hdev)) {
+               set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
+               ret = hci_suspend_wait_event(hdev);
+               if (ret)
+                       goto done;
+       }
+
+       /* Suspend notifier should only act on events when powered. */
+       if (!hdev_is_powered(hdev))
+               goto done;
+
+       if (action == PM_SUSPEND_PREPARE) {
+               hdev->suspend_state_next = BT_SUSPENDED;
+               set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
+               queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
+
+               ret = hci_suspend_wait_event(hdev);
+       } 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);
+       }
+
+done:
+       return ret ? notifier_from_errno(-EBUSY) : NOTIFY_STOP;
+}
 /* Alloc HCI device */
 struct hci_dev *hci_alloc_dev(void)
 {
@@ -3341,6 +3418,7 @@ struct hci_dev *hci_alloc_dev(void)
        INIT_WORK(&hdev->tx_work, hci_tx_work);
        INIT_WORK(&hdev->power_on, hci_power_on);
        INIT_WORK(&hdev->error_reset, hci_error_reset);
+       INIT_WORK(&hdev->suspend_prepare, hci_prepare_suspend);
 
        INIT_DELAYED_WORK(&hdev->power_off, hci_power_off);
 
@@ -3349,6 +3427,7 @@ struct hci_dev *hci_alloc_dev(void)
        skb_queue_head_init(&hdev->raw_q);
 
        init_waitqueue_head(&hdev->req_wait_q);
+       init_waitqueue_head(&hdev->suspend_wait_q);
 
        INIT_DELAYED_WORK(&hdev->cmd_timer, hci_cmd_timeout);
 
@@ -3460,6 +3539,11 @@ int hci_register_dev(struct hci_dev *hdev)
        hci_sock_dev_event(hdev, HCI_DEV_REG);
        hci_dev_hold(hdev);
 
+       hdev->suspend_notifier.notifier_call = hci_suspend_notifier;
+       error = register_pm_notifier(&hdev->suspend_notifier);
+       if (error)
+               goto err_wqueue;
+
        queue_work(hdev->req_workqueue, &hdev->power_on);
 
        return id;
@@ -3493,6 +3577,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
 
        hci_dev_do_close(hdev);
 
+       unregister_pm_notifier(&hdev->suspend_notifier);
+
        if (!test_bit(HCI_INIT, &hdev->flags) &&
            !hci_dev_test_flag(hdev, HCI_SETUP) &&
            !hci_dev_test_flag(hdev, HCI_CONFIG)) {
index 53179ae..2343166 100644 (file)
@@ -918,6 +918,21 @@ static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
        return adv_instance->scan_rsp_len;
 }
 
+/* Call with hci_dev_lock */
+void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
+{
+       if (next == hdev->suspend_state) {
+               bt_dev_dbg(hdev, "Same state before and after: %d", next);
+               goto done;
+       }
+
+       hdev->suspend_state = next;
+
+done:
+       clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
+       wake_up(&hdev->suspend_wait_q);
+}
+
 static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev)
 {
        u8 instance = hdev->cur_adv_instance;
index a7019fb..0e81614 100644 (file)
@@ -68,6 +68,8 @@ void __hci_req_update_eir(struct hci_request *req);
 void hci_req_add_le_scan_disable(struct hci_request *req);
 void hci_req_add_le_passive_scan(struct hci_request *req);
 
+void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next);
+
 void hci_req_reenable_advertising(struct hci_dev *hdev);
 void __hci_req_enable_advertising(struct hci_request *req);
 void __hci_req_disable_advertising(struct hci_request *req);