mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
Bluetooth: MGMT: Fix discovery stuck after a failed Stop Discovery
Stop Discovery moves the discovery to DISCOVERY_STOPPING, and
stop_discovery_complete() only moves it on to DISCOVERY_STOPPED when
stopping succeeded. When it fails, for example because LE Set Scan
Enable times out, nothing moves the discovery out of
DISCOVERY_STOPPING: Stop Discovery is rejected, as the discovery is not
active anymore, and the LE scan timeout was cancelled before the scan
was disabled. Until the controller is powered off, every Start
Discovery then fails with Busy, and hci_update_passive_scan_sync()
skips background scanning, so devices set to auto-connect are not
connected anymore.
Move the discovery back to DISCOVERY_FINDING when stopping it failed
while its inquiry or active LE scan is still running, so that it can be
stopped again. Only change the state while the discovery is still
stopping, as Inquiry Cancel may already have stopped it, for example
when only the passive scan could not be disabled, and a new Start
Discovery may have followed. Do this under hci_dev_lock(), like the
command completions that change these flags, and before replying, so
that a client acting on the reply sees the new state. Only cancel the
LE scan timeout once the scan is disabled, so that the discovery still
ends on its own, except when suspending, where it is still cancelled
first.
A timed out LE Set Scan Enable can still complete afterwards, which
cancels the timeout as well, so also stop the discovery when its scan
is disabled while the timeout is pending, or while the timeout work
waits for the lock.
Fixes: 0e05bba6f6 ("Bluetooth: Update stop_discovery to use HCI request")
Assisted-by: Claude:claude-opus-5.5
Signed-off-by: Kevin Dewald <kevin@simpleble.org>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
parent
ed4d302af7
commit
60b10121db
|
|
@ -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) &&
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user