Bluetooth: hci_sync: Rework hci_suspend_notifier
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 27 Oct 2021 23:59:00 +0000 (16:59 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Fri, 29 Oct 2021 14:52:00 +0000 (16:52 +0200)
This makes hci_suspend_notifier use the hci_*_sync which can be
executed synchronously which is allowed in the suspend_notifier and
simplifies a lot of the handling since the status of each command can
be checked inline so no other work need to be scheduled thus can be
performed without using of a state machine.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
include/net/bluetooth/hci_core.h
include/net/bluetooth/hci_sync.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_core.c
net/bluetooth/hci_event.c
net/bluetooth/hci_request.c
net/bluetooth/hci_sync.c
net/bluetooth/mgmt.c
net/bluetooth/msft.c
net/bluetooth/msft.h

index 3e53c84..53a8c7d 100644 (file)
@@ -523,7 +523,6 @@ struct hci_dev {
        bool                    advertising_paused;
 
        struct notifier_block   suspend_notifier;
-       struct work_struct      suspend_prepare;
        enum suspended_state    suspend_state_next;
        enum suspended_state    suspend_state;
        bool                    scanning_paused;
@@ -532,9 +531,6 @@ struct hci_dev {
        bdaddr_t                wake_addr;
        u8                      wake_addr_type;
 
-       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 487e498..00b13e8 100644 (file)
@@ -92,3 +92,6 @@ int hci_set_powered_sync(struct hci_dev *hdev, u8 val);
 
 int hci_start_discovery_sync(struct hci_dev *hdev);
 int hci_stop_discovery_sync(struct hci_dev *hdev);
+
+int hci_suspend_sync(struct hci_dev *hdev);
+int hci_resume_sync(struct hci_dev *hdev);
index dbd737b..cd6e1cf 100644 (file)
@@ -900,16 +900,6 @@ void hci_le_conn_failed(struct hci_conn *conn, u8 status)
 
        hci_conn_del(conn);
 
-       /* The suspend notifier is waiting for all devices to disconnect and an
-        * LE connect cancel will result in an hci_le_conn_failed. Once the last
-        * connection is deleted, we should also wake the suspend queue to
-        * complete suspend operations.
-        */
-       if (list_empty(&hdev->conn_hash.list) &&
-           test_and_clear_bit(SUSPEND_DISCONNECTING, hdev->suspend_tasks)) {
-               wake_up(&hdev->suspend_wait_q);
-       }
-
        /* Since we may have temporarily stopped the background scanning in
         * favor of connection establishment, we should restart it.
         */
index e0c0aa7..fdc0dcf 100644 (file)
@@ -2374,61 +2374,6 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
        }
 }
 
-static void hci_suspend_clear_tasks(struct hci_dev *hdev)
-{
-       int i;
-
-       for (i = 0; i < __SUSPEND_NUM_TASKS; i++)
-               clear_bit(i, hdev->suspend_tasks);
-
-       wake_up(&hdev->suspend_wait_q);
-}
-
-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_err(hdev, "Timed out waiting for suspend events");
-               for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) {
-                       if (test_bit(i, hdev->suspend_tasks))
-                               bt_dev_err(hdev, "Suspend timeout bit: %d", 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_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 void hci_clear_wake_reason(struct hci_dev *hdev)
 {
        hci_dev_lock(hdev);
@@ -2565,7 +2510,6 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
        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);
 
        hci_cmd_sync_init(hdev);
 
@@ -2576,7 +2520,6 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
        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);
        INIT_DELAYED_WORK(&hdev->ncmd_timer, hci_ncmd_timeout);
@@ -2729,11 +2672,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
 
        hci_cmd_sync_clear(hdev);
 
-       if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks)) {
-               hci_suspend_clear_tasks(hdev);
+       if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks))
                unregister_pm_notifier(&hdev->suspend_notifier);
-               cancel_work_sync(&hdev->suspend_prepare);
-       }
 
        msft_unregister(hdev);
 
@@ -2800,7 +2740,6 @@ EXPORT_SYMBOL(hci_release_dev);
 int hci_suspend_dev(struct hci_dev *hdev)
 {
        int ret;
-       u8 state = BT_RUNNING;
 
        bt_dev_dbg(hdev, "");
 
@@ -2809,40 +2748,17 @@ int hci_suspend_dev(struct hci_dev *hdev)
            hci_dev_test_flag(hdev, HCI_UNREGISTER))
                return 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 consists of two actions:
-        *  - First, disconnect everything and make the controller not
-        *    connectable (disabling scanning)
-        *  - Second, program event filter/accept list and enable scan
-        */
-       ret = hci_change_suspend_state(hdev, BT_SUSPEND_DISCONNECT);
-       if (ret)
-               goto clear;
-
-       state = BT_SUSPEND_DISCONNECT;
+       /* If powering down don't attempt to suspend */
+       if (mgmt_powering_down(hdev))
+               return 0;
 
-       /* Only configure accept list if device may wakeup. */
-       if (hdev->wakeup && hdev->wakeup(hdev)) {
-               ret = hci_change_suspend_state(hdev, BT_SUSPEND_CONFIGURE_WAKE);
-               if (!ret)
-                       state = BT_SUSPEND_CONFIGURE_WAKE;
-       }
+       hci_req_sync_lock(hdev);
+       ret = hci_suspend_sync(hdev);
+       hci_req_sync_unlock(hdev);
 
-clear:
        hci_clear_wake_reason(hdev);
-       mgmt_suspending(hdev, state);
+       mgmt_suspending(hdev, hdev->suspend_state);
 
-done:
-       /* We always allow suspend even if suspend preparation failed and
-        * attempt to recover in resume.
-        */
        hci_sock_dev_event(hdev, HCI_DEV_SUSPEND);
        return ret;
 }
@@ -2864,10 +2780,12 @@ int hci_resume_dev(struct hci_dev *hdev)
        if (mgmt_powering_down(hdev))
                return 0;
 
-       ret = hci_change_suspend_state(hdev, BT_RUNNING);
+       hci_req_sync_lock(hdev);
+       ret = hci_resume_sync(hdev);
+       hci_req_sync_unlock(hdev);
 
        mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr,
-                             hdev->wake_addr_type);
+                     hdev->wake_addr_type);
 
        hci_sock_dev_event(hdev, HCI_DEV_RESUME);
        return ret;
index 0f5761b..d4b75a6 100644 (file)
@@ -2414,9 +2414,14 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
 static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
 {
        struct hci_cp_disconnect *cp;
+       struct hci_conn_params *params;
        struct hci_conn *conn;
+       bool mgmt_conn;
 
-       if (!status)
+       /* Wait for HCI_EV_DISCONN_COMPLETE if status 0x00 and not suspended
+        * otherwise cleanup the connection immediately.
+        */
+       if (!status && !hdev->suspended)
                return;
 
        cp = hci_sent_cmd_data(hdev, HCI_OP_DISCONNECT);
@@ -2426,7 +2431,10 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
        hci_dev_lock(hdev);
 
        conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
-       if (conn) {
+       if (!conn)
+               goto unlock;
+
+       if (status) {
                mgmt_disconnect_failed(hdev, &conn->dst, conn->type,
                                       conn->dst_type, status);
 
@@ -2435,14 +2443,48 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
                        hci_enable_advertising(hdev);
                }
 
-               /* If the disconnection failed for any reason, the upper layer
-                * does not retry to disconnect in current implementation.
-                * Hence, we need to do some basic cleanup here and re-enable
-                * advertising if necessary.
-                */
-               hci_conn_del(conn);
+               goto done;
        }
 
+       mgmt_conn = test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags);
+
+       if (conn->type == ACL_LINK) {
+               if (test_bit(HCI_CONN_FLUSH_KEY, &conn->flags))
+                       hci_remove_link_key(hdev, &conn->dst);
+       }
+
+       params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type);
+       if (params) {
+               switch (params->auto_connect) {
+               case HCI_AUTO_CONN_LINK_LOSS:
+                       if (cp->reason != HCI_ERROR_CONNECTION_TIMEOUT)
+                               break;
+                       fallthrough;
+
+               case HCI_AUTO_CONN_DIRECT:
+               case HCI_AUTO_CONN_ALWAYS:
+                       list_del_init(&params->action);
+                       list_add(&params->action, &hdev->pend_le_conns);
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       mgmt_device_disconnected(hdev, &conn->dst, conn->type, conn->dst_type,
+                                cp->reason, mgmt_conn);
+
+       hci_disconn_cfm(conn, cp->reason);
+
+done:
+       /* If the disconnection failed for any reason, the upper layer
+        * does not retry to disconnect in current implementation.
+        * Hence, we need to do some basic cleanup here and re-enable
+        * advertising if necessary.
+        */
+       hci_conn_del(conn);
+unlock:
        hci_dev_unlock(hdev);
 }
 
@@ -3047,14 +3089,6 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
 
        hci_conn_del(conn);
 
-       /* The suspend notifier is waiting for all devices to disconnect so
-        * clear the bit from pending tasks and inform the wait queue.
-        */
-       if (list_empty(&hdev->conn_hash.list) &&
-           test_and_clear_bit(SUSPEND_DISCONNECTING, hdev->suspend_tasks)) {
-               wake_up(&hdev->suspend_wait_q);
-       }
-
 unlock:
        hci_dev_unlock(hdev);
 }
@@ -5575,8 +5609,9 @@ static struct hci_conn *check_pending_le_conn(struct hci_dev *hdev,
        if (adv_type != LE_ADV_IND && adv_type != LE_ADV_DIRECT_IND)
                return NULL;
 
-       /* Ignore if the device is blocked */
-       if (hci_bdaddr_list_lookup(&hdev->reject_list, addr, addr_type))
+       /* Ignore if the device is blocked or hdev is suspended */
+       if (hci_bdaddr_list_lookup(&hdev->reject_list, addr, addr_type) ||
+           hdev->suspended)
                return NULL;
 
        /* Most controller will fail if we try to create new connections
index 46fa9c3..8aa6e18 100644 (file)
@@ -492,9 +492,6 @@ void hci_req_add_le_scan_disable(struct hci_request *req, bool rpa_le_conn)
                return;
        }
 
-       if (hdev->suspended)
-               set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
-
        if (use_ext_scan(hdev)) {
                struct hci_cp_le_set_ext_scan_enable cp;
 
@@ -868,8 +865,6 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
        if (hdev->suspended) {
                window = hdev->le_scan_window_suspend;
                interval = hdev->le_scan_int_suspend;
-
-               set_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks);
        } else if (hci_is_le_conn_scanning(hdev)) {
                window = hdev->le_scan_window_connect;
                interval = hdev->le_scan_int_connect;
@@ -902,59 +897,6 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
                           addr_resolv);
 }
 
-static void hci_req_clear_event_filter(struct hci_request *req)
-{
-       struct hci_cp_set_event_filter f;
-
-       if (!hci_dev_test_flag(req->hdev, HCI_BREDR_ENABLED))
-               return;
-
-       if (hci_dev_test_flag(req->hdev, HCI_EVENT_FILTER_CONFIGURED)) {
-               memset(&f, 0, sizeof(f));
-               f.flt_type = HCI_FLT_CLEAR_ALL;
-               hci_req_add(req, HCI_OP_SET_EVENT_FLT, 1, &f);
-       }
-}
-
-static void hci_req_set_event_filter(struct hci_request *req)
-{
-       struct bdaddr_list_with_flags *b;
-       struct hci_cp_set_event_filter f;
-       struct hci_dev *hdev = req->hdev;
-       u8 scan = SCAN_DISABLED;
-       bool scanning = test_bit(HCI_PSCAN, &hdev->flags);
-
-       if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
-               return;
-
-       /* Always clear event filter when starting */
-       hci_req_clear_event_filter(req);
-
-       list_for_each_entry(b, &hdev->accept_list, list) {
-               if (!hci_conn_test_flag(HCI_CONN_FLAG_REMOTE_WAKEUP,
-                                       b->current_flags))
-                       continue;
-
-               memset(&f, 0, sizeof(f));
-               bacpy(&f.addr_conn_flt.bdaddr, &b->bdaddr);
-               f.flt_type = HCI_FLT_CONN_SETUP;
-               f.cond_type = HCI_CONN_SETUP_ALLOW_BDADDR;
-               f.addr_conn_flt.auto_accept = HCI_CONN_SETUP_AUTO_ON;
-
-               bt_dev_dbg(hdev, "Adding event filters for %pMR", &b->bdaddr);
-               hci_req_add(req, HCI_OP_SET_EVENT_FLT, sizeof(f), &f);
-               scan = SCAN_PAGE;
-       }
-
-       if (scan && !scanning) {
-               set_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks);
-               hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
-       } else if (!scan && scanning) {
-               set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
-               hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
-       }
-}
-
 static void cancel_adv_timeout(struct hci_dev *hdev)
 {
        if (hdev->adv_instance_timeout) {
@@ -1013,185 +955,6 @@ int hci_req_resume_adv_instances(struct hci_dev *hdev)
        return hci_req_run(&req, NULL);
 }
 
-static void suspend_req_complete(struct hci_dev *hdev, u8 status, u16 opcode)
-{
-       bt_dev_dbg(hdev, "Request complete opcode=0x%x, status=0x%x", opcode,
-                  status);
-       if (test_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks) ||
-           test_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks)) {
-               clear_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks);
-               clear_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
-               wake_up(&hdev->suspend_wait_q);
-       }
-
-       if (test_bit(SUSPEND_SET_ADV_FILTER, hdev->suspend_tasks)) {
-               clear_bit(SUSPEND_SET_ADV_FILTER, hdev->suspend_tasks);
-               wake_up(&hdev->suspend_wait_q);
-       }
-}
-
-static void hci_req_prepare_adv_monitor_suspend(struct hci_request *req,
-                                               bool suspending)
-{
-       struct hci_dev *hdev = req->hdev;
-
-       switch (hci_get_adv_monitor_offload_ext(hdev)) {
-       case HCI_ADV_MONITOR_EXT_MSFT:
-               if (suspending)
-                       msft_suspend(hdev);
-               else
-                       msft_resume(hdev);
-               break;
-       default:
-               return;
-       }
-
-       /* No need to block when enabling since it's on resume path */
-       if (hdev->suspended && suspending)
-               set_bit(SUSPEND_SET_ADV_FILTER, hdev->suspend_tasks);
-}
-
-/* Call with hci_dev_lock */
-void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
-{
-       int old_state;
-       struct hci_conn *conn;
-       struct hci_request req;
-       u8 page_scan;
-       int disconnect_counter;
-
-       if (next == hdev->suspend_state) {
-               bt_dev_dbg(hdev, "Same state before and after: %d", next);
-               goto done;
-       }
-
-       hdev->suspend_state = next;
-       hci_req_init(&req, hdev);
-
-       if (next == BT_SUSPEND_DISCONNECT) {
-               /* Mark device as suspended */
-               hdev->suspended = true;
-
-               /* Pause discovery if not already stopped */
-               old_state = hdev->discovery.state;
-               if (old_state != DISCOVERY_STOPPED) {
-                       set_bit(SUSPEND_PAUSE_DISCOVERY, hdev->suspend_tasks);
-                       hci_discovery_set_state(hdev, DISCOVERY_STOPPING);
-                       queue_work(hdev->req_workqueue, &hdev->discov_update);
-               }
-
-               hdev->discovery_paused = true;
-               hdev->discovery_old_state = old_state;
-
-               /* Stop directed advertising */
-               old_state = hci_dev_test_flag(hdev, HCI_ADVERTISING);
-               if (old_state) {
-                       set_bit(SUSPEND_PAUSE_ADVERTISING, hdev->suspend_tasks);
-                       cancel_delayed_work(&hdev->discov_off);
-                       queue_delayed_work(hdev->req_workqueue,
-                                          &hdev->discov_off, 0);
-               }
-
-               /* Pause other advertisements */
-               if (hdev->adv_instance_cnt)
-                       __hci_req_pause_adv_instances(&req);
-
-               hdev->advertising_paused = true;
-               hdev->advertising_old_state = old_state;
-
-               /* Disable page scan if enabled */
-               if (test_bit(HCI_PSCAN, &hdev->flags)) {
-                       page_scan = SCAN_DISABLED;
-                       hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1,
-                                   &page_scan);
-                       set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
-               }
-
-               /* Disable LE passive scan if enabled */
-               if (hci_dev_test_flag(hdev, HCI_LE_SCAN)) {
-                       cancel_interleave_scan(hdev);
-                       hci_req_add_le_scan_disable(&req, false);
-               }
-
-               /* Disable advertisement filters */
-               hci_req_prepare_adv_monitor_suspend(&req, true);
-
-               /* Prevent disconnects from causing scanning to be re-enabled */
-               hdev->scanning_paused = true;
-
-               /* Run commands before disconnecting */
-               hci_req_run(&req, suspend_req_complete);
-
-               disconnect_counter = 0;
-               /* Soft disconnect everything (power off) */
-               list_for_each_entry(conn, &hdev->conn_hash.list, list) {
-                       hci_disconnect(conn, HCI_ERROR_REMOTE_POWER_OFF);
-                       disconnect_counter++;
-               }
-
-               if (disconnect_counter > 0) {
-                       bt_dev_dbg(hdev,
-                                  "Had %d disconnects. Will wait on them",
-                                  disconnect_counter);
-                       set_bit(SUSPEND_DISCONNECTING, hdev->suspend_tasks);
-               }
-       } else if (next == BT_SUSPEND_CONFIGURE_WAKE) {
-               /* Unpause to take care of updating scanning params */
-               hdev->scanning_paused = false;
-               /* Enable event filter for paired devices */
-               hci_req_set_event_filter(&req);
-               /* Enable passive scan at lower duty cycle */
-               __hci_update_background_scan(&req);
-               /* Pause scan changes again. */
-               hdev->scanning_paused = true;
-               hci_req_run(&req, suspend_req_complete);
-       } else {
-               hdev->suspended = false;
-               hdev->scanning_paused = false;
-
-               /* Clear any event filters and restore scan state */
-               hci_req_clear_event_filter(&req);
-               __hci_req_update_scan(&req);
-
-               /* Reset passive/background scanning to normal */
-               __hci_update_background_scan(&req);
-               /* Enable all of the advertisement filters */
-               hci_req_prepare_adv_monitor_suspend(&req, false);
-
-               /* Unpause directed advertising */
-               hdev->advertising_paused = false;
-               if (hdev->advertising_old_state) {
-                       set_bit(SUSPEND_UNPAUSE_ADVERTISING,
-                               hdev->suspend_tasks);
-                       hci_dev_set_flag(hdev, HCI_ADVERTISING);
-                       queue_work(hdev->req_workqueue,
-                                  &hdev->discoverable_update);
-                       hdev->advertising_old_state = 0;
-               }
-
-               /* Resume other advertisements */
-               if (hdev->adv_instance_cnt)
-                       __hci_req_resume_adv_instances(&req);
-
-               /* Unpause discovery */
-               hdev->discovery_paused = false;
-               if (hdev->discovery_old_state != DISCOVERY_STOPPED &&
-                   hdev->discovery_old_state != DISCOVERY_STOPPING) {
-                       set_bit(SUSPEND_UNPAUSE_DISCOVERY, hdev->suspend_tasks);
-                       hci_discovery_set_state(hdev, DISCOVERY_STARTING);
-                       queue_work(hdev->req_workqueue, &hdev->discov_update);
-               }
-
-               hci_req_run(&req, suspend_req_complete);
-       }
-
-       hdev->suspend_state = next;
-
-done:
-       clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
-       wake_up(&hdev->suspend_wait_q);
-}
-
 static bool adv_cur_instance_is_scannable(struct hci_dev *hdev)
 {
        return hci_adv_instance_is_scannable(hdev, hdev->cur_adv_instance);
index 4e0a771..e3f44e6 100644 (file)
@@ -1410,9 +1410,6 @@ int hci_scan_disable_sync(struct hci_dev *hdev)
                return 0;
        }
 
-       if (hdev->suspended)
-               set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
-
        err = hci_le_set_scan_enable_sync(hdev, LE_SCAN_DISABLE, 0x00);
        if (err) {
                bt_dev_err(hdev, "Unable to disable scanning: %d", err);
@@ -1642,10 +1639,11 @@ static int hci_le_add_accept_list_sync(struct hci_dev *hdev,
        return 0;
 }
 
-/* This function disables all advertising instances (including 0x00) */
+/* This function disables/pause all advertising instances */
 static int hci_pause_advertising_sync(struct hci_dev *hdev)
 {
        int err;
+       int old_state;
 
        /* If there are no instances or advertising has already been paused
         * there is nothing to do.
@@ -1653,6 +1651,21 @@ static int hci_pause_advertising_sync(struct hci_dev *hdev)
        if (!hdev->adv_instance_cnt || hdev->advertising_paused)
                return 0;
 
+       bt_dev_dbg(hdev, "Pausing directed advertising");
+
+       /* Stop directed advertising */
+       old_state = hci_dev_test_flag(hdev, HCI_ADVERTISING);
+       if (old_state) {
+               /* When discoverable timeout triggers, then just make sure
+                * the limited discoverable flag is cleared. Even in the case
+                * of a timeout triggered from general discoverable, it is
+                * safe to unconditionally clear the flag.
+                */
+               hci_dev_clear_flag(hdev, HCI_LIMITED_DISCOVERABLE);
+               hci_dev_clear_flag(hdev, HCI_DISCOVERABLE);
+               hdev->discov_timeout = 0;
+       }
+
        bt_dev_dbg(hdev, "Pausing advertising instances");
 
        /* Call to disable any advertisements active on the controller.
@@ -1667,11 +1680,12 @@ static int hci_pause_advertising_sync(struct hci_dev *hdev)
                cancel_adv_timeout(hdev);
 
        hdev->advertising_paused = true;
+       hdev->advertising_old_state = old_state;
 
        return 0;
 }
 
-/* This function enables all user advertising instances (excluding 0x00) */
+/* This function enables all user advertising instances */
 static int hci_resume_advertising_sync(struct hci_dev *hdev)
 {
        struct adv_info *adv, *tmp;
@@ -1681,6 +1695,14 @@ static int hci_resume_advertising_sync(struct hci_dev *hdev)
        if (!hdev->advertising_paused)
                return 0;
 
+       /* Resume directed advertising */
+       hdev->advertising_paused = false;
+       if (hdev->advertising_old_state) {
+               hci_dev_set_flag(hdev, HCI_ADVERTISING);
+               queue_work(hdev->req_workqueue, &hdev->discoverable_update);
+               hdev->advertising_old_state = 0;
+       }
+
        bt_dev_dbg(hdev, "Resuming advertising instances");
 
        if (ext_adv_capable(hdev)) {
@@ -2002,8 +2024,6 @@ int hci_passive_scan_sync(struct hci_dev *hdev)
        if (hdev->suspended) {
                window = hdev->le_scan_window_suspend;
                interval = hdev->le_scan_int_suspend;
-
-               set_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks);
        } else if (hci_is_le_conn_scanning(hdev)) {
                window = hdev->le_scan_window_connect;
                interval = hdev->le_scan_int_connect;
@@ -2937,6 +2957,13 @@ static int hci_set_event_mask_sync(struct hci_dev *hdev)
 
        if (lmp_bredr_capable(hdev)) {
                events[4] |= 0x01; /* Flow Specification Complete */
+
+               /* Don't set Disconnect Complete when suspended as that
+                * would wakeup the host when disconnecting due to
+                * suspend.
+                */
+               if (hdev->suspended)
+                       events[0] &= 0xef;
        } else {
                /* Use a different default for LE-only devices */
                memset(events, 0, sizeof(events));
@@ -2949,7 +2976,12 @@ static int hci_set_event_mask_sync(struct hci_dev *hdev)
                 * control related events.
                 */
                if (hdev->commands[0] & 0x20) {
-                       events[0] |= 0x10; /* Disconnection Complete */
+                       /* Don't set Disconnect Complete when suspended as that
+                        * would wakeup the host when disconnecting due to
+                        * suspend.
+                        */
+                       if (!hdev->suspended)
+                               events[0] |= 0x10; /* Disconnection Complete */
                        events[2] |= 0x04; /* Number of Completed Packets */
                        events[3] |= 0x02; /* Data Buffer Overflow */
                }
@@ -4033,9 +4065,6 @@ int hci_dev_close_sync(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);
 
@@ -4299,6 +4328,20 @@ static int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn,
        return 0;
 }
 
+static int hci_disconnect_all_sync(struct hci_dev *hdev, u8 reason)
+{
+       struct hci_conn *conn, *tmp;
+       int err;
+
+       list_for_each_entry_safe(conn, tmp, &hdev->conn_hash.list, list) {
+               err = hci_abort_conn_sync(hdev, conn, reason);
+               if (err)
+                       return err;
+       }
+
+       return err;
+}
+
 /* This function perform power off HCI command sequence as follows:
  *
  * Clear Advertising
@@ -4308,7 +4351,6 @@ static int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn,
  */
 static int hci_power_off_sync(struct hci_dev *hdev)
 {
-       struct hci_conn *conn;
        int err;
 
        /* If controller is already down there is nothing to do */
@@ -4330,10 +4372,10 @@ static int hci_power_off_sync(struct hci_dev *hdev)
        if (err)
                return err;
 
-       list_for_each_entry(conn, &hdev->conn_hash.list, list) {
-               /* 0x15 == Terminated due to Power Off */
-               hci_abort_conn_sync(hdev, conn, 0x15);
-       }
+       /* Terminated due to Power Off */
+       err = hci_disconnect_all_sync(hdev, HCI_ERROR_REMOTE_POWER_OFF);
+       if (err)
+               return err;
 
        return hci_dev_close_sync(hdev);
 }
@@ -4535,3 +4577,223 @@ int hci_start_discovery_sync(struct hci_dev *hdev)
                           timeout);
        return 0;
 }
+
+static void hci_suspend_monitor_sync(struct hci_dev *hdev)
+{
+       switch (hci_get_adv_monitor_offload_ext(hdev)) {
+       case HCI_ADV_MONITOR_EXT_MSFT:
+               msft_suspend_sync(hdev);
+               break;
+       default:
+               return;
+       }
+}
+
+/* This function disables discovery and mark it as paused */
+static int hci_pause_discovery_sync(struct hci_dev *hdev)
+{
+       int old_state = hdev->discovery.state;
+       int err;
+
+       /* If discovery already stopped/stopping/paused there nothing to do */
+       if (old_state == DISCOVERY_STOPPED || old_state == DISCOVERY_STOPPING ||
+           hdev->discovery_paused)
+               return 0;
+
+       hci_discovery_set_state(hdev, DISCOVERY_STOPPING);
+       err = hci_stop_discovery_sync(hdev);
+       if (err)
+               return err;
+
+       hdev->discovery_paused = true;
+       hdev->discovery_old_state = old_state;
+       hci_discovery_set_state(hdev, DISCOVERY_STOPPED);
+
+       return 0;
+}
+
+static int hci_update_event_filter_sync(struct hci_dev *hdev)
+{
+       struct bdaddr_list_with_flags *b;
+       u8 scan = SCAN_DISABLED;
+       bool scanning = test_bit(HCI_PSCAN, &hdev->flags);
+       int err;
+
+       if (!hci_dev_test_flag(hdev, HCI_BREDR_ENABLED))
+               return 0;
+
+       /* Always clear event filter when starting */
+       hci_clear_event_filter_sync(hdev);
+
+       list_for_each_entry(b, &hdev->accept_list, list) {
+               if (!hci_conn_test_flag(HCI_CONN_FLAG_REMOTE_WAKEUP,
+                                       b->current_flags))
+                       continue;
+
+               bt_dev_dbg(hdev, "Adding event filters for %pMR", &b->bdaddr);
+
+               err =  hci_set_event_filter_sync(hdev, HCI_FLT_CONN_SETUP,
+                                                HCI_CONN_SETUP_ALLOW_BDADDR,
+                                                &b->bdaddr,
+                                                HCI_CONN_SETUP_AUTO_ON);
+               if (err)
+                       bt_dev_dbg(hdev, "Failed to set event filter for %pMR",
+                                  &b->bdaddr);
+               else
+                       scan = SCAN_PAGE;
+       }
+
+       if (scan && !scanning)
+               hci_write_scan_enable_sync(hdev, scan);
+       else if (!scan && scanning)
+               hci_write_scan_enable_sync(hdev, scan);
+
+       return 0;
+}
+
+/* This function performs the HCI suspend procedures in the follow order:
+ *
+ * Pause discovery (active scanning/inquiry)
+ * Pause Directed Advertising/Advertising
+ * Disconnect all connections
+ * Set suspend_status to BT_SUSPEND_DISCONNECT if hdev cannot wakeup
+ * otherwise:
+ * Update event mask (only set events that are allowed to wake up the host)
+ * Update event filter (with devices marked with HCI_CONN_FLAG_REMOTE_WAKEUP)
+ * Update passive scanning (lower duty cycle)
+ * Set suspend_status to BT_SUSPEND_CONFIGURE_WAKE
+ */
+int hci_suspend_sync(struct hci_dev *hdev)
+{
+       int err;
+
+       /* If marked as suspended there nothing to do */
+       if (hdev->suspended)
+               return 0;
+
+       /* Mark device as suspended */
+       hdev->suspended = true;
+
+       /* Pause discovery if not already stopped */
+       hci_pause_discovery_sync(hdev);
+
+       /* Pause other advertisements */
+       hci_pause_advertising_sync(hdev);
+
+       /* Disable page scan if enabled */
+       if (test_bit(HCI_PSCAN, &hdev->flags))
+               hci_write_scan_enable_sync(hdev, SCAN_DISABLED);
+
+       /* Suspend monitor filters */
+       hci_suspend_monitor_sync(hdev);
+
+       /* Prevent disconnects from causing scanning to be re-enabled */
+       hdev->scanning_paused = true;
+
+       /* Soft disconnect everything (power off) */
+       err = hci_disconnect_all_sync(hdev, HCI_ERROR_REMOTE_POWER_OFF);
+       if (err) {
+               /* Set state to BT_RUNNING so resume doesn't notify */
+               hdev->suspend_state = BT_RUNNING;
+               hci_resume_sync(hdev);
+               return err;
+       }
+
+       /* Only configure accept list if disconnect succeeded and wake
+        * isn't being prevented.
+        */
+       if (!hdev->wakeup || !hdev->wakeup(hdev)) {
+               hdev->suspend_state = BT_SUSPEND_DISCONNECT;
+               return 0;
+       }
+
+       /* Unpause to take care of updating scanning params */
+       hdev->scanning_paused = false;
+
+       /* Update event mask so only the allowed event can wakeup the host */
+       hci_set_event_mask_sync(hdev);
+
+       /* Enable event filter for paired devices */
+       hci_update_event_filter_sync(hdev);
+
+       /* Update LE passive scan if enabled */
+       hci_update_passive_scan_sync(hdev);
+
+       /* Pause scan changes again. */
+       hdev->scanning_paused = true;
+
+       hdev->suspend_state = BT_SUSPEND_CONFIGURE_WAKE;
+
+       return 0;
+}
+
+/* This function resumes discovery */
+static int hci_resume_discovery_sync(struct hci_dev *hdev)
+{
+       int err;
+
+       /* If discovery not paused there nothing to do */
+       if (!hdev->discovery_paused)
+               return 0;
+
+       hdev->discovery_paused = false;
+
+       hci_discovery_set_state(hdev, DISCOVERY_STARTING);
+
+       err = hci_start_discovery_sync(hdev);
+
+       hci_discovery_set_state(hdev, err ? DISCOVERY_STOPPED :
+                               DISCOVERY_FINDING);
+
+       return err;
+}
+
+static void hci_resume_monitor_sync(struct hci_dev *hdev)
+{
+       switch (hci_get_adv_monitor_offload_ext(hdev)) {
+       case HCI_ADV_MONITOR_EXT_MSFT:
+               msft_resume_sync(hdev);
+               break;
+       default:
+               return;
+       }
+}
+
+/* This function performs the HCI suspend procedures in the follow order:
+ *
+ * Restore event mask
+ * Clear event filter
+ * Update passive scanning (normal duty cycle)
+ * Resume Directed Advertising/Advertising
+ * Resume discovery (active scanning/inquiry)
+ */
+int hci_resume_sync(struct hci_dev *hdev)
+{
+       /* If not marked as suspended there nothing to do */
+       if (!hdev->suspended)
+               return 0;
+
+       hdev->suspended = false;
+       hdev->scanning_paused = false;
+
+       /* Restore event mask */
+       hci_set_event_mask_sync(hdev);
+
+       /* Clear any event filters and restore scan state */
+       hci_clear_event_filter_sync(hdev);
+       hci_update_scan_sync(hdev);
+
+       /* Reset passive scanning to normal */
+       hci_update_passive_scan_sync(hdev);
+
+       /* Resume monitor filters */
+       hci_resume_monitor_sync(hdev);
+
+       /* Resume other advertisements */
+       hci_resume_advertising_sync(hdev);
+
+       /* Resume discovery */
+       hci_resume_discovery_sync(hdev);
+
+       return 0;
+}
index bfa08eb..a7d35c1 100644 (file)
@@ -5171,13 +5171,6 @@ void mgmt_start_discovery_complete(struct hci_dev *hdev, u8 status)
        }
 
        hci_dev_unlock(hdev);
-
-       /* Handle suspend notifier */
-       if (test_and_clear_bit(SUSPEND_UNPAUSE_DISCOVERY,
-                              hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Unpaused discovery");
-               wake_up(&hdev->suspend_wait_q);
-       }
 }
 
 static bool discovery_type_is_valid(struct hci_dev *hdev, uint8_t type,
@@ -5217,14 +5210,7 @@ static void start_discovery_complete(struct hci_dev *hdev, void *data, int err)
                          cmd->param, 1);
        mgmt_pending_free(cmd);
 
-       /* Handle suspend notifier */
-       if (test_and_clear_bit(SUSPEND_UNPAUSE_DISCOVERY,
-                              hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Unpaused discovery");
-               wake_up(&hdev->suspend_wait_q);
-       }
-
-       hci_discovery_set_state(hdev, err ? DISCOVERY_STOPPED :
+       hci_discovery_set_state(hdev, err ? DISCOVERY_STOPPED:
                                DISCOVERY_FINDING);
 }
 
@@ -5446,12 +5432,6 @@ void mgmt_stop_discovery_complete(struct hci_dev *hdev, u8 status)
        }
 
        hci_dev_unlock(hdev);
-
-       /* Handle suspend notifier */
-       if (test_and_clear_bit(SUSPEND_PAUSE_DISCOVERY, hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Paused discovery");
-               wake_up(&hdev->suspend_wait_q);
-       }
 }
 
 static void stop_discovery_complete(struct hci_dev *hdev, void *data, int err)
@@ -5464,12 +5444,6 @@ static void stop_discovery_complete(struct hci_dev *hdev, void *data, int err)
                          cmd->param, 1);
        mgmt_pending_free(cmd);
 
-       /* Handle suspend notifier */
-       if (test_and_clear_bit(SUSPEND_PAUSE_DISCOVERY, hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Paused discovery");
-               wake_up(&hdev->suspend_wait_q);
-       }
-
        if (!err)
                hci_discovery_set_state(hdev, DISCOVERY_STOPPED);
 }
@@ -5709,17 +5683,6 @@ static void set_advertising_complete(struct hci_dev *hdev, void *data, int err)
        if (match.sk)
                sock_put(match.sk);
 
-       /* Handle suspend notifier */
-       if (test_and_clear_bit(SUSPEND_PAUSE_ADVERTISING,
-                              hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Paused advertising");
-               wake_up(&hdev->suspend_wait_q);
-       } else if (test_and_clear_bit(SUSPEND_UNPAUSE_ADVERTISING,
-                                     hdev->suspend_tasks)) {
-               bt_dev_dbg(hdev, "Unpaused advertising");
-               wake_up(&hdev->suspend_wait_q);
-       }
-
        /* If "Set Advertising" was just disabled and instance advertising was
         * set up earlier, then re-enable multi-instance advertising.
         */
index 5205d94..1122097 100644 (file)
@@ -93,7 +93,7 @@ struct msft_data {
        struct list_head handle_map;
        __u16 pending_add_handle;
        __u16 pending_remove_handle;
-       __u8 reregistering;
+       __u8 resuming;
        __u8 suspending;
        __u8 filter_enabled;
 };
@@ -156,7 +156,6 @@ failed:
        return false;
 }
 
-/* This function requires the caller holds hdev->lock */
 static void reregister_monitor(struct hci_dev *hdev, int handle)
 {
        struct adv_monitor *monitor;
@@ -166,8 +165,8 @@ static void reregister_monitor(struct hci_dev *hdev, int handle)
        while (1) {
                monitor = idr_get_next(&hdev->adv_monitors_idr, &handle);
                if (!monitor) {
-                       /* All monitors have been reregistered */
-                       msft->reregistering = false;
+                       /* All monitors have been resumed */
+                       msft->resuming = false;
                        hci_update_passive_scan(hdev);
                        return;
                }
@@ -185,67 +184,317 @@ static void reregister_monitor(struct hci_dev *hdev, int handle)
        }
 }
 
-/* This function requires the caller holds hdev->lock */
-static void remove_monitor_on_suspend(struct hci_dev *hdev, int handle)
+/* is_mgmt = true matches the handle exposed to userspace via mgmt.
+ * is_mgmt = false matches the handle used by the msft controller.
+ * This function requires the caller holds hdev->lock
+ */
+static struct msft_monitor_advertisement_handle_data *msft_find_handle_data
+                               (struct hci_dev *hdev, u16 handle, bool is_mgmt)
+{
+       struct msft_monitor_advertisement_handle_data *entry;
+       struct msft_data *msft = hdev->msft_data;
+
+       list_for_each_entry(entry, &msft->handle_map, list) {
+               if (is_mgmt && entry->mgmt_handle == handle)
+                       return entry;
+               if (!is_mgmt && entry->msft_handle == handle)
+                       return entry;
+       }
+
+       return NULL;
+}
+
+static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev,
+                                            u8 status, u16 opcode,
+                                            struct sk_buff *skb)
+{
+       struct msft_rp_le_monitor_advertisement *rp;
+       struct adv_monitor *monitor;
+       struct msft_monitor_advertisement_handle_data *handle_data;
+       struct msft_data *msft = hdev->msft_data;
+
+       hci_dev_lock(hdev);
+
+       monitor = idr_find(&hdev->adv_monitors_idr, msft->pending_add_handle);
+       if (!monitor) {
+               bt_dev_err(hdev, "msft add advmon: monitor %u is not found!",
+                          msft->pending_add_handle);
+               status = HCI_ERROR_UNSPECIFIED;
+               goto unlock;
+       }
+
+       if (status)
+               goto unlock;
+
+       rp = (struct msft_rp_le_monitor_advertisement *)skb->data;
+       if (skb->len < sizeof(*rp)) {
+               status = HCI_ERROR_UNSPECIFIED;
+               goto unlock;
+       }
+
+       handle_data = kmalloc(sizeof(*handle_data), GFP_KERNEL);
+       if (!handle_data) {
+               status = HCI_ERROR_UNSPECIFIED;
+               goto unlock;
+       }
+
+       handle_data->mgmt_handle = monitor->handle;
+       handle_data->msft_handle = rp->handle;
+       INIT_LIST_HEAD(&handle_data->list);
+       list_add(&handle_data->list, &msft->handle_map);
+
+       monitor->state = ADV_MONITOR_STATE_OFFLOADED;
+
+unlock:
+       if (status && monitor)
+               hci_free_adv_monitor(hdev, monitor);
+
+       hci_dev_unlock(hdev);
+
+       if (!msft->resuming)
+               hci_add_adv_patterns_monitor_complete(hdev, status);
+}
+
+static void msft_le_cancel_monitor_advertisement_cb(struct hci_dev *hdev,
+                                                   u8 status, u16 opcode,
+                                                   struct sk_buff *skb)
 {
+       struct msft_cp_le_cancel_monitor_advertisement *cp;
+       struct msft_rp_le_cancel_monitor_advertisement *rp;
        struct adv_monitor *monitor;
+       struct msft_monitor_advertisement_handle_data *handle_data;
        struct msft_data *msft = hdev->msft_data;
        int err;
+       bool pending;
 
-       while (1) {
-               monitor = idr_get_next(&hdev->adv_monitors_idr, &handle);
-               if (!monitor) {
-                       /* All monitors have been removed */
-                       msft->suspending = false;
-                       hci_update_background_scan(hdev);
+       if (status)
+               goto done;
+
+       rp = (struct msft_rp_le_cancel_monitor_advertisement *)skb->data;
+       if (skb->len < sizeof(*rp)) {
+               status = HCI_ERROR_UNSPECIFIED;
+               goto done;
+       }
+
+       hci_dev_lock(hdev);
+
+       cp = hci_sent_cmd_data(hdev, hdev->msft_opcode);
+       handle_data = msft_find_handle_data(hdev, cp->handle, false);
+
+       if (handle_data) {
+               monitor = idr_find(&hdev->adv_monitors_idr,
+                                  handle_data->mgmt_handle);
+
+               if (monitor && monitor->state == ADV_MONITOR_STATE_OFFLOADED)
+                       monitor->state = ADV_MONITOR_STATE_REGISTERED;
+
+               /* Do not free the monitor if it is being removed due to
+                * suspend. It will be re-monitored on resume.
+                */
+               if (monitor && !msft->suspending)
+                       hci_free_adv_monitor(hdev, monitor);
+
+               list_del(&handle_data->list);
+               kfree(handle_data);
+       }
+
+       /* If remove all monitors is required, we need to continue the process
+        * here because the earlier it was paused when waiting for the
+        * response from controller.
+        */
+       if (msft->pending_remove_handle == 0) {
+               pending = hci_remove_all_adv_monitor(hdev, &err);
+               if (pending) {
+                       hci_dev_unlock(hdev);
                        return;
                }
 
-               msft->pending_remove_handle = (u16)handle;
-               err = __msft_remove_monitor(hdev, monitor, handle);
+               if (err)
+                       status = HCI_ERROR_UNSPECIFIED;
+       }
 
-               /* If success, return and wait for monitor removed callback */
-               if (!err)
-                       return;
+       hci_dev_unlock(hdev);
+
+done:
+       if (!msft->suspending)
+               hci_remove_adv_monitor_complete(hdev, status);
+}
+
+static int msft_remove_monitor_sync(struct hci_dev *hdev,
+                                   struct adv_monitor *monitor)
+{
+       struct msft_cp_le_cancel_monitor_advertisement cp;
+       struct msft_monitor_advertisement_handle_data *handle_data;
+       struct sk_buff *skb;
+       u8 status;
+
+       handle_data = msft_find_handle_data(hdev, monitor->handle, true);
+
+       /* If no matched handle, just remove without telling controller */
+       if (!handle_data)
+               return -ENOENT;
+
+       cp.sub_opcode = MSFT_OP_LE_CANCEL_MONITOR_ADVERTISEMENT;
+       cp.handle = handle_data->msft_handle;
+
+       skb = __hci_cmd_sync(hdev, hdev->msft_opcode, sizeof(cp), &cp,
+                            HCI_CMD_TIMEOUT);
+       if (IS_ERR(skb))
+               return PTR_ERR(skb);
+
+       status = skb->data[0];
+       skb_pull(skb, 1);
+
+       msft_le_cancel_monitor_advertisement_cb(hdev, status, hdev->msft_opcode,
+                                               skb);
+
+       return status;
+}
+
+/* This function requires the caller holds hci_req_sync_lock */
+int msft_suspend_sync(struct hci_dev *hdev)
+{
+       struct msft_data *msft = hdev->msft_data;
+       struct adv_monitor *monitor;
+       int handle = 0;
+
+       if (!msft || !msft_monitor_supported(hdev))
+               return 0;
+
+       msft->suspending = true;
+
+       while (1) {
+               monitor = idr_get_next(&hdev->adv_monitors_idr, &handle);
+               if (!monitor)
+                       break;
+
+               msft_remove_monitor_sync(hdev, monitor);
 
-               /* Otherwise free the monitor and keep removing */
-               hci_free_adv_monitor(hdev, monitor);
                handle++;
        }
+
+       /* All monitors have been removed */
+       msft->suspending = false;
+
+       return 0;
 }
 
-/* This function requires the caller holds hdev->lock */
-void msft_suspend(struct hci_dev *hdev)
+static bool msft_monitor_rssi_valid(struct adv_monitor *monitor)
 {
-       struct msft_data *msft = hdev->msft_data;
+       struct adv_rssi_thresholds *r = &monitor->rssi;
 
-       if (!msft)
-               return;
+       if (r->high_threshold < MSFT_RSSI_THRESHOLD_VALUE_MIN ||
+           r->high_threshold > MSFT_RSSI_THRESHOLD_VALUE_MAX ||
+           r->low_threshold < MSFT_RSSI_THRESHOLD_VALUE_MIN ||
+           r->low_threshold > MSFT_RSSI_THRESHOLD_VALUE_MAX)
+               return false;
 
-       if (msft_monitor_supported(hdev)) {
-               msft->suspending = true;
-               /* Quitely remove all monitors on suspend to avoid waking up
-                * the system.
-                */
-               remove_monitor_on_suspend(hdev, 0);
+       /* High_threshold_timeout is not supported,
+        * once high_threshold is reached, events are immediately reported.
+        */
+       if (r->high_threshold_timeout != 0)
+               return false;
+
+       if (r->low_threshold_timeout > MSFT_RSSI_LOW_TIMEOUT_MAX)
+               return false;
+
+       /* Sampling period from 0x00 to 0xFF are all allowed */
+       return true;
+}
+
+static bool msft_monitor_pattern_valid(struct adv_monitor *monitor)
+{
+       return msft_monitor_rssi_valid(monitor);
+       /* No additional check needed for pattern-based monitor */
+}
+
+static int msft_add_monitor_sync(struct hci_dev *hdev,
+                                struct adv_monitor *monitor)
+{
+       struct msft_cp_le_monitor_advertisement *cp;
+       struct msft_le_monitor_advertisement_pattern_data *pattern_data;
+       struct msft_le_monitor_advertisement_pattern *pattern;
+       struct adv_pattern *entry;
+       size_t total_size = sizeof(*cp) + sizeof(*pattern_data);
+       ptrdiff_t offset = 0;
+       u8 pattern_count = 0;
+       struct sk_buff *skb;
+       u8 status;
+
+       if (!msft_monitor_pattern_valid(monitor))
+               return -EINVAL;
+
+       list_for_each_entry(entry, &monitor->patterns, list) {
+               pattern_count++;
+               total_size += sizeof(*pattern) + entry->length;
        }
+
+       cp = kmalloc(total_size, GFP_KERNEL);
+       if (!cp)
+               return -ENOMEM;
+
+       cp->sub_opcode = MSFT_OP_LE_MONITOR_ADVERTISEMENT;
+       cp->rssi_high = monitor->rssi.high_threshold;
+       cp->rssi_low = monitor->rssi.low_threshold;
+       cp->rssi_low_interval = (u8)monitor->rssi.low_threshold_timeout;
+       cp->rssi_sampling_period = monitor->rssi.sampling_period;
+
+       cp->cond_type = MSFT_MONITOR_ADVERTISEMENT_TYPE_PATTERN;
+
+       pattern_data = (void *)cp->data;
+       pattern_data->count = pattern_count;
+
+       list_for_each_entry(entry, &monitor->patterns, list) {
+               pattern = (void *)(pattern_data->data + offset);
+               /* the length also includes data_type and offset */
+               pattern->length = entry->length + 2;
+               pattern->data_type = entry->ad_type;
+               pattern->start_byte = entry->offset;
+               memcpy(pattern->pattern, entry->value, entry->length);
+               offset += sizeof(*pattern) + entry->length;
+       }
+
+       skb = __hci_cmd_sync(hdev, hdev->msft_opcode, total_size, cp,
+                            HCI_CMD_TIMEOUT);
+       kfree(cp);
+
+       if (IS_ERR(skb))
+               return PTR_ERR(skb);
+
+       status = skb->data[0];
+       skb_pull(skb, 1);
+
+       msft_le_monitor_advertisement_cb(hdev, status, hdev->msft_opcode, skb);
+
+       return status;
 }
 
-/* This function requires the caller holds hdev->lock */
-void msft_resume(struct hci_dev *hdev)
+/* This function requires the caller holds hci_req_sync_lock */
+int msft_resume_sync(struct hci_dev *hdev)
 {
        struct msft_data *msft = hdev->msft_data;
+       struct adv_monitor *monitor;
+       int handle = 0;
 
-       if (!msft)
-               return;
+       if (!msft || !msft_monitor_supported(hdev))
+               return 0;
 
-       if (msft_monitor_supported(hdev)) {
-               msft->reregistering = true;
-               /* Monitors are removed on suspend, so we need to add all
-                * monitors on resume.
-                */
-               reregister_monitor(hdev, 0);
+       msft->resuming = true;
+
+       while (1) {
+               monitor = idr_get_next(&hdev->adv_monitors_idr, &handle);
+               if (!monitor)
+                       break;
+
+               msft_add_monitor_sync(hdev, monitor);
+
+               handle++;
        }
+
+       /* All monitors have been resumed */
+       msft->resuming = false;
+
+       return 0;
 }
 
 void msft_do_open(struct hci_dev *hdev)
@@ -275,7 +524,7 @@ void msft_do_open(struct hci_dev *hdev)
        }
 
        if (msft_monitor_supported(hdev)) {
-               msft->reregistering = true;
+               msft->resuming = true;
                msft_set_filter_enable(hdev, true);
                /* Monitors get removed on power off, so we need to explicitly
                 * tell the controller to re-monitor.
@@ -381,151 +630,6 @@ __u64 msft_get_features(struct hci_dev *hdev)
        return msft ? msft->features : 0;
 }
 
-/* is_mgmt = true matches the handle exposed to userspace via mgmt.
- * is_mgmt = false matches the handle used by the msft controller.
- * This function requires the caller holds hdev->lock
- */
-static struct msft_monitor_advertisement_handle_data *msft_find_handle_data
-                               (struct hci_dev *hdev, u16 handle, bool is_mgmt)
-{
-       struct msft_monitor_advertisement_handle_data *entry;
-       struct msft_data *msft = hdev->msft_data;
-
-       list_for_each_entry(entry, &msft->handle_map, list) {
-               if (is_mgmt && entry->mgmt_handle == handle)
-                       return entry;
-               if (!is_mgmt && entry->msft_handle == handle)
-                       return entry;
-       }
-
-       return NULL;
-}
-
-static void msft_le_monitor_advertisement_cb(struct hci_dev *hdev,
-                                            u8 status, u16 opcode,
-                                            struct sk_buff *skb)
-{
-       struct msft_rp_le_monitor_advertisement *rp;
-       struct adv_monitor *monitor;
-       struct msft_monitor_advertisement_handle_data *handle_data;
-       struct msft_data *msft = hdev->msft_data;
-
-       hci_dev_lock(hdev);
-
-       monitor = idr_find(&hdev->adv_monitors_idr, msft->pending_add_handle);
-       if (!monitor) {
-               bt_dev_err(hdev, "msft add advmon: monitor %u is not found!",
-                          msft->pending_add_handle);
-               status = HCI_ERROR_UNSPECIFIED;
-               goto unlock;
-       }
-
-       if (status)
-               goto unlock;
-
-       rp = (struct msft_rp_le_monitor_advertisement *)skb->data;
-       if (skb->len < sizeof(*rp)) {
-               status = HCI_ERROR_UNSPECIFIED;
-               goto unlock;
-       }
-
-       handle_data = kmalloc(sizeof(*handle_data), GFP_KERNEL);
-       if (!handle_data) {
-               status = HCI_ERROR_UNSPECIFIED;
-               goto unlock;
-       }
-
-       handle_data->mgmt_handle = monitor->handle;
-       handle_data->msft_handle = rp->handle;
-       INIT_LIST_HEAD(&handle_data->list);
-       list_add(&handle_data->list, &msft->handle_map);
-
-       monitor->state = ADV_MONITOR_STATE_OFFLOADED;
-
-unlock:
-       if (status && monitor)
-               hci_free_adv_monitor(hdev, monitor);
-
-       /* If in restart/reregister sequence, keep registering. */
-       if (msft->reregistering)
-               reregister_monitor(hdev, msft->pending_add_handle + 1);
-
-       hci_dev_unlock(hdev);
-
-       if (!msft->reregistering)
-               hci_add_adv_patterns_monitor_complete(hdev, status);
-}
-
-static void msft_le_cancel_monitor_advertisement_cb(struct hci_dev *hdev,
-                                                   u8 status, u16 opcode,
-                                                   struct sk_buff *skb)
-{
-       struct msft_cp_le_cancel_monitor_advertisement *cp;
-       struct msft_rp_le_cancel_monitor_advertisement *rp;
-       struct adv_monitor *monitor;
-       struct msft_monitor_advertisement_handle_data *handle_data;
-       struct msft_data *msft = hdev->msft_data;
-       int err;
-       bool pending;
-
-       if (status)
-               goto done;
-
-       rp = (struct msft_rp_le_cancel_monitor_advertisement *)skb->data;
-       if (skb->len < sizeof(*rp)) {
-               status = HCI_ERROR_UNSPECIFIED;
-               goto done;
-       }
-
-       hci_dev_lock(hdev);
-
-       cp = hci_sent_cmd_data(hdev, hdev->msft_opcode);
-       handle_data = msft_find_handle_data(hdev, cp->handle, false);
-
-       if (handle_data) {
-               monitor = idr_find(&hdev->adv_monitors_idr,
-                                  handle_data->mgmt_handle);
-
-               if (monitor && monitor->state == ADV_MONITOR_STATE_OFFLOADED)
-                       monitor->state = ADV_MONITOR_STATE_REGISTERED;
-
-               /* Do not free the monitor if it is being removed due to
-                * suspend. It will be re-monitored on resume.
-                */
-               if (monitor && !msft->suspending)
-                       hci_free_adv_monitor(hdev, monitor);
-
-               list_del(&handle_data->list);
-               kfree(handle_data);
-       }
-
-       /* If in suspend/remove sequence, keep removing. */
-       if (msft->suspending)
-               remove_monitor_on_suspend(hdev,
-                                         msft->pending_remove_handle + 1);
-
-       /* If remove all monitors is required, we need to continue the process
-        * here because the earlier it was paused when waiting for the
-        * response from controller.
-        */
-       if (msft->pending_remove_handle == 0) {
-               pending = hci_remove_all_adv_monitor(hdev, &err);
-               if (pending) {
-                       hci_dev_unlock(hdev);
-                       return;
-               }
-
-               if (err)
-                       status = HCI_ERROR_UNSPECIFIED;
-       }
-
-       hci_dev_unlock(hdev);
-
-done:
-       if (!msft->suspending)
-               hci_remove_adv_monitor_complete(hdev, status);
-}
-
 static void msft_le_set_advertisement_filter_enable_cb(struct hci_dev *hdev,
                                                       u8 status, u16 opcode,
                                                       struct sk_buff *skb)
@@ -560,35 +664,6 @@ static void msft_le_set_advertisement_filter_enable_cb(struct hci_dev *hdev,
        hci_dev_unlock(hdev);
 }
 
-static bool msft_monitor_rssi_valid(struct adv_monitor *monitor)
-{
-       struct adv_rssi_thresholds *r = &monitor->rssi;
-
-       if (r->high_threshold < MSFT_RSSI_THRESHOLD_VALUE_MIN ||
-           r->high_threshold > MSFT_RSSI_THRESHOLD_VALUE_MAX ||
-           r->low_threshold < MSFT_RSSI_THRESHOLD_VALUE_MIN ||
-           r->low_threshold > MSFT_RSSI_THRESHOLD_VALUE_MAX)
-               return false;
-
-       /* High_threshold_timeout is not supported,
-        * once high_threshold is reached, events are immediately reported.
-        */
-       if (r->high_threshold_timeout != 0)
-               return false;
-
-       if (r->low_threshold_timeout > MSFT_RSSI_LOW_TIMEOUT_MAX)
-               return false;
-
-       /* Sampling period from 0x00 to 0xFF are all allowed */
-       return true;
-}
-
-static bool msft_monitor_pattern_valid(struct adv_monitor *monitor)
-{
-       return msft_monitor_rssi_valid(monitor);
-       /* No additional check needed for pattern-based monitor */
-}
-
 /* This function requires the caller holds hdev->lock */
 static int __msft_add_monitor_pattern(struct hci_dev *hdev,
                                      struct adv_monitor *monitor)
@@ -656,7 +731,7 @@ int msft_add_monitor_pattern(struct hci_dev *hdev, struct adv_monitor *monitor)
        if (!msft)
                return -EOPNOTSUPP;
 
-       if (msft->reregistering || msft->suspending)
+       if (msft->resuming || msft->suspending)
                return -EBUSY;
 
        return __msft_add_monitor_pattern(hdev, monitor);
@@ -700,7 +775,7 @@ int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
        if (!msft)
                return -EOPNOTSUPP;
 
-       if (msft->reregistering || msft->suspending)
+       if (msft->resuming || msft->suspending)
                return -EBUSY;
 
        return __msft_remove_monitor(hdev, monitor, handle);
index 59c6e08..b59b63d 100644 (file)
@@ -24,8 +24,8 @@ int msft_remove_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
                        u16 handle);
 void msft_req_add_set_filter_enable(struct hci_request *req, bool enable);
 int msft_set_filter_enable(struct hci_dev *hdev, bool enable);
-void msft_suspend(struct hci_dev *hdev);
-void msft_resume(struct hci_dev *hdev);
+int msft_suspend_sync(struct hci_dev *hdev);
+int msft_resume_sync(struct hci_dev *hdev);
 bool msft_curve_validity(struct hci_dev *hdev);
 
 #else
@@ -61,8 +61,15 @@ static inline int msft_set_filter_enable(struct hci_dev *hdev, bool enable)
        return -EOPNOTSUPP;
 }
 
-static inline void msft_suspend(struct hci_dev *hdev) {}
-static inline void msft_resume(struct hci_dev *hdev) {}
+static inline int msft_suspend_sync(struct hci_dev *hdev)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline int msft_resume_sync(struct hci_dev *hdev)
+{
+       return -EOPNOTSUPP;
+}
 
 static inline bool msft_curve_validity(struct hci_dev *hdev)
 {