Skip to content

Commit

Permalink
Bluetooth: Pause discovery and advertising during suspend
Browse files Browse the repository at this point in the history
To prevent spurious wake ups, we disable any discovery or advertising
when we enter suspend and restore it when we exit suspend. While paused,
we disable any management requests to modify discovery or advertising.

Signed-off-by: Abhishek Pandit-Subedi <[email protected]>
Signed-off-by: Marcel Holtmann <[email protected]>
  • Loading branch information
apandit authored and holtmann committed Mar 11, 2020
1 parent dd522a7 commit 4867bd0
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
11 changes: 11 additions & 0 deletions include/net/bluetooth/hci_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ struct discovery_state {
#define SUSPEND_NOTIFIER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */

enum suspend_tasks {
SUSPEND_PAUSE_DISCOVERY,
SUSPEND_UNPAUSE_DISCOVERY,

SUSPEND_PAUSE_ADVERTISING,
SUSPEND_UNPAUSE_ADVERTISING,

SUSPEND_SCAN_DISABLE,
SUSPEND_SCAN_ENABLE,
SUSPEND_DISCONNECTING,
Expand Down Expand Up @@ -410,6 +416,11 @@ struct hci_dev {

struct discovery_state discovery;

int discovery_old_state;
bool discovery_paused;
int advertising_old_state;
bool advertising_paused;

struct notifier_block suspend_notifier;
struct work_struct suspend_prepare;
enum suspended_state suspend_state_next;
Expand Down
44 changes: 44 additions & 0 deletions net/bluetooth/hci_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,7 @@ static void suspend_req_complete(struct hci_dev *hdev, u8 status, u16 opcode)
/* 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;
Expand All @@ -1038,6 +1039,28 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
/* 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 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);
}

hdev->advertising_paused = true;
hdev->advertising_old_state = old_state;
/* Disable page scan */
page_scan = SCAN_DISABLED;
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &page_scan);
Expand Down Expand Up @@ -1084,6 +1107,27 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
hci_req_clear_event_filter(&req);
/* Reset passive/background scanning to normal */
hci_req_config_le_suspend_scan(&req);

/* Unpause 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;
}

/* 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);
}

Expand Down
41 changes: 41 additions & 0 deletions net/bluetooth/mgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,12 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
goto failed;
}

if (hdev->advertising_paused) {
err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
MGMT_STATUS_BUSY);
goto failed;
}

if (!hdev_is_powered(hdev)) {
bool changed = false;

Expand Down Expand Up @@ -3929,6 +3935,13 @@ 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,
Expand Down Expand Up @@ -3990,6 +4003,13 @@ static int start_discovery_internal(struct sock *sk, struct hci_dev *hdev,
goto failed;
}

/* Can't start discovery when it is paused */
if (hdev->discovery_paused) {
err = mgmt_cmd_complete(sk, hdev->id, op, MGMT_STATUS_BUSY,
&cp->type, sizeof(cp->type));
goto failed;
}

/* Clear the discovery filter first to free any previously
* allocated memory for the UUID list.
*/
Expand Down Expand Up @@ -4157,6 +4177,12 @@ 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 int stop_discovery(struct sock *sk, struct hci_dev *hdev, void *data,
Expand Down Expand Up @@ -4388,6 +4414,17 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status,
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.
*/
Expand Down Expand Up @@ -4439,6 +4476,10 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data,
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
MGMT_STATUS_INVALID_PARAMS);

if (hdev->advertising_paused)
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
MGMT_STATUS_BUSY);

hci_dev_lock(hdev);

val = !!cp->val;
Expand Down

0 comments on commit 4867bd0

Please sign in to comment.