Commit 4f40afc6 authored by Abhishek Pandit-Subedi's avatar Abhishek Pandit-Subedi Committed by Marcel Holtmann
Browse files

Bluetooth: Handle BR/EDR devices during suspend



To handle BR/EDR devices, we first disable page scan and disconnect all
connected devices. Once that is complete, we add event filters (for
devices that can wake the system) and re-enable page scan.

Signed-off-by: default avatarAbhishek Pandit-Subedi <abhishekpandit@chromium.org>
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent 9952d90e
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -942,10 +942,14 @@ struct hci_cp_sniff_subrate {
#define HCI_OP_RESET			0x0c03

#define HCI_OP_SET_EVENT_FLT		0x0c05
struct hci_cp_set_event_flt {
#define HCI_SET_EVENT_FLT_SIZE		9
struct hci_cp_set_event_filter {
	__u8		flt_type;
	__u8		cond_type;
	__u8     condition[];
	struct {
		bdaddr_t bdaddr;
		__u8 auto_accept;
	} __packed	addr_conn_flt;
} __packed;

/* Filter types */
@@ -961,6 +965,7 @@ struct hci_cp_set_event_flt {
/* CONN_SETUP Conditions */
#define HCI_CONN_SETUP_AUTO_OFF		0x01
#define HCI_CONN_SETUP_AUTO_ON		0x02
#define HCI_CONN_SETUP_AUTO_ON_WITH_RS	0x03

#define HCI_OP_READ_STORED_LINK_KEY	0x0c0d
struct hci_cp_read_stored_link_key {
+9 −1
Original line number Diff line number Diff line
@@ -91,6 +91,10 @@ struct discovery_state {
#define SUSPEND_NOTIFIER_TIMEOUT	msecs_to_jiffies(2000) /* 2 seconds */

enum suspend_tasks {
	SUSPEND_SCAN_DISABLE,
	SUSPEND_SCAN_ENABLE,
	SUSPEND_DISCONNECTING,

	SUSPEND_POWERING_DOWN,

	SUSPEND_PREPARE_NOTIFIER,
@@ -99,7 +103,8 @@ enum suspend_tasks {

enum suspended_state {
	BT_RUNNING = 0,
	BT_SUSPENDED,
	BT_SUSPEND_DISCONNECT,
	BT_SUSPEND_COMPLETE,
};

struct hci_conn_hash {
@@ -409,6 +414,8 @@ struct hci_dev {
	struct work_struct	suspend_prepare;
	enum suspended_state	suspend_state_next;
	enum suspended_state	suspend_state;
	bool			scanning_paused;
	bool			suspended;

	wait_queue_head_t	suspend_wait_q;
	DECLARE_BITMAP(suspend_tasks, __SUSPEND_NUM_TASKS);
@@ -418,6 +425,7 @@ struct hci_dev {
	struct list_head	mgmt_pending;
	struct list_head	blacklist;
	struct list_head	whitelist;
	struct list_head	wakeable;
	struct list_head	uuids;
	struct list_head	link_keys;
	struct list_head	long_term_keys;
+19 −3
Original line number Diff line number Diff line
@@ -3325,16 +3325,31 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
		goto done;

	if (action == PM_SUSPEND_PREPARE) {
		hdev->suspend_state_next = BT_SUSPENDED;
		/* Suspend consists of two actions:
		 *  - First, disconnect everything and make the controller not
		 *    connectable (disabling scanning)
		 *  - Second, program event filter/whitelist and enable scan
		 */
		hdev->suspend_state_next = BT_SUSPEND_DISCONNECT;
		set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
		queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
		ret = hci_suspend_wait_event(hdev);

		/* If the disconnect portion failed, don't attempt to complete
		 * by configuring the whitelist. The suspend notifier will
		 * follow a cancelled suspend with a PM_POST_SUSPEND
		 * notification.
		 */
		if (!ret) {
			hdev->suspend_state_next = BT_SUSPEND_COMPLETE;
			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);
	}

@@ -3399,6 +3414,7 @@ struct hci_dev *hci_alloc_dev(void)
	INIT_LIST_HEAD(&hdev->mgmt_pending);
	INIT_LIST_HEAD(&hdev->blacklist);
	INIT_LIST_HEAD(&hdev->whitelist);
	INIT_LIST_HEAD(&hdev->wakeable);
	INIT_LIST_HEAD(&hdev->uuids);
	INIT_LIST_HEAD(&hdev->link_keys);
	INIT_LIST_HEAD(&hdev->long_term_keys);
+24 −0
Original line number Diff line number Diff line
@@ -2505,6 +2505,7 @@ static void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb)
static void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
	struct hci_ev_conn_complete *ev = (void *) skb->data;
	struct inquiry_entry *ie;
	struct hci_conn *conn;

	BT_DBG("%s", hdev->name);
@@ -2513,6 +2514,21 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)

	conn = hci_conn_hash_lookup_ba(hdev, ev->link_type, &ev->bdaddr);
	if (!conn) {
		/* Connection may not exist if auto-connected. Check the inquiry
		 * cache to see if we've already discovered this bdaddr before.
		 * If found and link is an ACL type, create a connection class
		 * automatically.
		 */
		ie = hci_inquiry_cache_lookup(hdev, &ev->bdaddr);
		if (ie && ev->link_type == ACL_LINK) {
			conn = hci_conn_add(hdev, ev->link_type, &ev->bdaddr,
					    HCI_ROLE_SLAVE);
			if (!conn) {
				bt_dev_err(hdev, "no memory for new conn");
				goto unlock;
			}
		}

		if (ev->link_type != SCO_LINK)
			goto unlock;

@@ -2774,6 +2790,14 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
	hci_disconn_cfm(conn, ev->reason);
	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);
	}

	/* Re-enable advertising if necessary, since it might
	 * have been disabled by the connection. From the
	 * HCI_LE_Set_Advertise_Enable command description in
+106 −0
Original line number Diff line number Diff line
@@ -918,15 +918,118 @@ static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
	return adv_instance->scan_rsp_len;
}

static void hci_req_clear_event_filter(struct hci_request *req)
{
	struct hci_cp_set_event_filter f;

	memset(&f, 0, sizeof(f));
	f.flt_type = HCI_FLT_CLEAR_ALL;
	hci_req_add(req, HCI_OP_SET_EVENT_FLT, 1, &f);

	/* Update page scan state (since we may have modified it when setting
	 * the event filter).
	 */
	__hci_req_update_scan(req);
}

static void hci_req_set_event_filter(struct hci_request *req)
{
	struct bdaddr_list *b;
	struct hci_cp_set_event_filter f;
	struct hci_dev *hdev = req->hdev;
	u8 scan;

	/* Always clear event filter when starting */
	hci_req_clear_event_filter(req);

	list_for_each_entry(b, &hdev->wakeable, list) {
		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 = !list_empty(&hdev->wakeable) ? SCAN_PAGE : SCAN_DISABLED;
	hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
}

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_and_clear_bit(SUSPEND_SCAN_ENABLE, hdev->suspend_tasks) ||
	    test_and_clear_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks)) {
		wake_up(&hdev->suspend_wait_q);
	}
}

/* Call with hci_dev_lock */
void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
{
	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;

		/* Disable page scan */
		page_scan = SCAN_DISABLED;
		hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &page_scan);

		/* Mark task needing completion */
		set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);

		/* 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_COMPLETE) {
		/* Unpause to take care of updating scanning params */
		hdev->scanning_paused = false;
		/* Enable event filter for paired devices */
		hci_req_set_event_filter(&req);
		/* Pause scan changes again. */
		hdev->scanning_paused = true;
		hci_req_run(&req, suspend_req_complete);
	} else {
		hdev->suspended = false;
		hdev->scanning_paused = false;

		hci_req_clear_event_filter(&req);
		hci_req_run(&req, suspend_req_complete);
	}

	hdev->suspend_state = next;

done:
	clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
@@ -2030,6 +2133,9 @@ void __hci_req_update_scan(struct hci_request *req)
	if (mgmt_powering_down(hdev))
		return;

	if (hdev->scanning_paused)
		return;

	if (hci_dev_test_flag(hdev, HCI_CONNECTABLE) ||
	    disconnected_whitelist_entries(hdev))
		scan = SCAN_PAGE;