diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 377e916ccacc..a52574003e4f 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1819,6 +1819,8 @@ static void store_pending_adv_report(struct hci_dev *hdev, bdaddr_t *bdaddr, static void le_set_scan_enable_complete(struct hci_dev *hdev, u8 enable) { + bool timeout_pending; + hci_dev_lock(hdev); switch (enable) { @@ -1848,10 +1850,18 @@ static void le_set_scan_enable_complete(struct hci_dev *hdev, u8 enable) /* Cancel this timer so that we don't try to disable scanning * when it's already disabled. */ - cancel_delayed_work(&hdev->le_scan_disable); + timeout_pending = cancel_delayed_work(&hdev->le_scan_disable); hci_dev_clear_flag(hdev, HCI_LE_SCAN); + /* The scan of a discovery was disabled before its timeout, + * for example by a Stop Discovery that timed out and still + * completed, so nothing else would end the discovery. + */ + if (timeout_pending && + hdev->discovery.state == DISCOVERY_FINDING) + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED && hci_test_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY) && !test_bit(HCI_INQUIRY, &hdev->flags) && diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 9d829c20c316..9213e3ef9f26 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -376,8 +376,18 @@ static void le_scan_disable(struct work_struct *work) bt_dev_dbg(hdev, ""); hci_dev_lock(hdev); - if (!hci_dev_test_flag(hdev, HCI_LE_SCAN)) + if (!hci_dev_test_flag(hdev, HCI_LE_SCAN)) { + /* The scan of a discovery can be disabled while this work + * waits for the lock, for example by a Stop Discovery that + * timed out and still completed, so end the discovery as + * le_set_scan_enable_complete() does when it cancels this + * work. + */ + if (hdev->discovery.state == DISCOVERY_FINDING) + goto discov_stopped; + goto _return; + } status = hci_cmd_sync_queue(hdev, scan_disable_sync, NULL, NULL); if (status) { @@ -5920,11 +5930,15 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) } if (hci_dev_test_flag(hdev, HCI_LE_SCAN)) { - cancel_delayed_work(&hdev->le_scan_disable); - err = hci_scan_disable_sync(hdev); if (err) return err; + + /* Only cancel the LE scan timeout once the scan is + * disabled, so that the discovery still ends if + * stopping it failed. + */ + cancel_delayed_work(&hdev->le_scan_disable); } } else { @@ -6559,6 +6573,12 @@ static int hci_pause_discovery_sync(struct hci_dev *hdev) return 0; hci_discovery_set_state(hdev, DISCOVERY_STOPPING); + + /* Cancel the LE scan timeout first, so that it does not run while + * suspended if the scan cannot be disabled. + */ + cancel_delayed_work(&hdev->le_scan_disable); + err = hci_stop_discovery_sync(hdev); if (err) return err; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index cf5256f4a89e..260ef2a45719 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -6318,12 +6318,29 @@ static void stop_discovery_complete(struct hci_dev *hdev, void *data, int err) bt_dev_dbg(hdev, "err %d", err); + hci_dev_lock(hdev); + + /* Change the state before replying, so that a client acting on the + * reply sees it, and only while the discovery is still stopping, as + * Inquiry Cancel may already have stopped it and a new Start Discovery + * may have followed. Go back to DISCOVERY_FINDING when stopping failed + * while the inquiry or the active LE scan of the discovery is still + * running, so that it can be stopped again. + */ + if (hdev->discovery.state == DISCOVERY_STOPPING) { + if (err && (test_bit(HCI_INQUIRY, &hdev->flags) || + (hci_dev_test_flag(hdev, HCI_LE_SCAN) && + hdev->le_scan_type == LE_SCAN_ACTIVE))) + hci_discovery_set_state(hdev, DISCOVERY_FINDING); + else + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + } + + hci_dev_unlock(hdev); + mgmt_cmd_complete(cmd->sk, cmd->hdev->id, cmd->opcode, mgmt_status(err), cmd->param, 1); mgmt_pending_free(cmd); - - if (!err) - hci_discovery_set_state(hdev, DISCOVERY_STOPPED); } static int stop_discovery_sync(struct hci_dev *hdev, void *data)