@@ -932,10 +932,31 @@ 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[0];
+ union {
+ union {
+ struct {
+ __u8 val[3];
+ __u8 mask[3];
+ } __packed dev_class;
+ bdaddr_t addr;
+ } inq;
+ union {
+ __u8 auto_accept_any;
+ struct {
+ __u8 val[3];
+ __u8 mask[3];
+ __u8 auto_accept;
+ } __packed dev_class;
+ struct {
+ bdaddr_t bdaddr;
+ __u8 auto_accept;
+ } __packed addr;
+ } conn;
+ } cond;
} __packed;
/* Filter types */
@@ -949,8 +970,9 @@ struct hci_cp_set_event_flt {
#define HCI_CONN_SETUP_ALLOW_BDADDR 0x02
/* CONN_SETUP Conditions */
-#define HCI_CONN_SETUP_AUTO_OFF 0x01
-#define HCI_CONN_SETUP_AUTO_ON 0x02
+#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 {
@@ -88,6 +88,49 @@ struct discovery_state {
unsigned long scan_duration;
};
+#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_LE_SET_SCAN_ENABLE,
+ SUSPEND_DISCONNECTING,
+
+ SUSPEND_PREPARE_NOTIFIER,
+ __SUSPEND_NUM_TASKS
+};
+
+enum suspended_state {
+ BT_RUNNING = 0,
+ BT_SUSPENDED,
+};
+
+struct suspend_state {
+ int discovery_old_state;
+ int advertising_old_state;
+
+ bool discovery_paused;
+ bool advertising_paused;
+
+ int disconnect_counter;
+
+ /* BREDR: Disallow changing event filters + page scan.
+ * LE: Disallow changing whitelist, scan params and scan enable.
+ */
+ bool freeze_filters;
+
+ DECLARE_BITMAP(tasks, __SUSPEND_NUM_TASKS);
+ wait_queue_head_t tasks_wait_q;
+
+ struct work_struct prepare;
+ enum suspended_state next_state;
+ enum suspended_state state;
+};
+
struct hci_conn_hash {
struct list_head list;
unsigned int acl_num;
@@ -389,6 +432,8 @@ struct hci_dev {
void *smp_bredr_data;
struct discovery_state discovery;
+ struct suspend_state suspend;
+ struct notifier_block suspend_notifier;
struct hci_conn_hash conn_hash;
struct list_head mgmt_pending;
@@ -31,6 +31,8 @@
#include <linux/debugfs.h>
#include <linux/crypto.h>
#include <linux/property.h>
+#include <linux/suspend.h>
+#include <linux/wait.h>
#include <asm/unaligned.h>
#include <net/bluetooth/bluetooth.h>
@@ -3241,6 +3243,65 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
}
}
+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.tasks_wait_q,
+ WAKE_COND, SUSPEND_NOTIFIER_TIMEOUT);
+
+ if (ret == 0) {
+ BT_DBG("Timed out waiting for suspend");
+ for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) {
+ if (test_bit(i, hdev->suspend.tasks))
+ BT_DBG("Bit %d is set", 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.next_state);
+ hci_dev_unlock(hdev);
+}
+
+static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct hci_dev *hdev =
+ container_of(nb, struct hci_dev, suspend_notifier);
+ int ret = 0;
+
+ if (action == PM_SUSPEND_PREPARE) {
+ hdev->suspend.next_state = BT_SUSPENDED;
+ 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.next_state = BT_RUNNING;
+ set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend.tasks);
+ queue_work(hdev->req_workqueue, &hdev->suspend.prepare);
+
+ ret = hci_suspend_wait_event(hdev);
+ }
+
+ return ret ? notifier_from_errno(-EBUSY) : NOTIFY_STOP;
+}
/* Alloc HCI device */
struct hci_dev *hci_alloc_dev(void)
{
@@ -3319,6 +3380,7 @@ struct hci_dev *hci_alloc_dev(void)
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);
INIT_DELAYED_WORK(&hdev->power_off, hci_power_off);
@@ -3327,6 +3389,7 @@ struct hci_dev *hci_alloc_dev(void)
skb_queue_head_init(&hdev->raw_q);
init_waitqueue_head(&hdev->req_wait_q);
+ init_waitqueue_head(&hdev->suspend.tasks_wait_q);
INIT_DELAYED_WORK(&hdev->cmd_timer, hci_cmd_timeout);
@@ -3438,6 +3501,11 @@ int hci_register_dev(struct hci_dev *hdev)
hci_sock_dev_event(hdev, HCI_DEV_REG);
hci_dev_hold(hdev);
+ hdev->suspend_notifier.notifier_call = hci_suspend_notifier;
+ error = register_pm_notifier(&hdev->suspend_notifier);
+ if (error)
+ goto err_wqueue;
+
queue_work(hdev->req_workqueue, &hdev->power_on);
return id;
@@ -3471,6 +3539,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
hci_dev_do_close(hdev);
+ unregister_pm_notifier(&hdev->suspend_notifier);
+
if (!test_bit(HCI_INIT, &hdev->flags) &&
!hci_dev_test_flag(hdev, HCI_SETUP) &&
!hci_dev_test_flag(hdev, HCI_CONFIG)) {
@@ -2474,6 +2474,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);
@@ -2482,14 +2483,25 @@ 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) {
- if (ev->link_type != SCO_LINK)
- goto unlock;
+ ie = hci_inquiry_cache_lookup(hdev, &ev->bdaddr);
+ if (ie) {
+ 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;
+ }
+ } else {
+ if (ev->link_type != SCO_LINK)
+ goto unlock;
- conn = hci_conn_hash_lookup_ba(hdev, ESCO_LINK, &ev->bdaddr);
- if (!conn)
- goto unlock;
+ conn = hci_conn_hash_lookup_ba(hdev, ESCO_LINK,
+ &ev->bdaddr);
+ if (!conn)
+ goto unlock;
- conn->type = SCO_LINK;
+ conn->type = SCO_LINK;
+ }
}
if (!ev->status) {
@@ -447,7 +447,7 @@ static void __hci_update_background_scan(struct hci_request *req)
if (hci_dev_test_flag(hdev, HCI_LE_SCAN))
hci_req_add_le_scan_disable(req);
- hci_req_add_le_passive_scan(req);
+ hci_req_add_le_passive_scan(req, false);
BT_DBG("%s starting background scanning", hdev->name);
}
@@ -654,6 +654,12 @@ void hci_req_add_le_scan_disable(struct hci_request *req)
{
struct hci_dev *hdev = req->hdev;
+ /* Early exit if we've frozen filters for suspend*/
+ if (hdev->suspend.freeze_filters) {
+ BT_DBG("Filters are frozen for suspend");
+ return;
+ }
+
if (use_ext_scan(hdev)) {
struct hci_cp_le_set_ext_scan_enable cp;
@@ -681,12 +687,25 @@ static void add_to_white_list(struct hci_request *req,
hci_req_add(req, HCI_OP_LE_ADD_TO_WHITE_LIST, sizeof(cp), &cp);
}
-static u8 update_white_list(struct hci_request *req)
+static void del_from_white_list(struct hci_request *req, bdaddr_t *bdaddr,
+ u8 bdaddr_type)
+{
+ struct hci_cp_le_del_from_white_list cp;
+
+ cp.bdaddr_type = bdaddr_type;
+ bacpy(&cp.bdaddr, bdaddr);
+
+ hci_req_add(req, HCI_OP_LE_DEL_FROM_WHITE_LIST, sizeof(cp), &cp);
+}
+
+static u8 update_white_list(struct hci_request *req, bool allow_rpa,
+ bool remove_nonwakeable)
{
struct hci_dev *hdev = req->hdev;
struct hci_conn_params *params;
struct bdaddr_list *b;
uint8_t white_list_entries = 0;
+ bool wakeable;
/* Go through the current white list programmed into the
* controller one by one and check if that address is still
@@ -695,24 +714,37 @@ static u8 update_white_list(struct hci_request *req)
* command to remove it from the controller.
*/
list_for_each_entry(b, &hdev->le_white_list, list) {
- /* If the device is neither in pend_le_conns nor
- * pend_le_reports then remove it from the whitelist.
+ wakeable = !!hci_bdaddr_list_lookup(&hdev->wakeable, &b->bdaddr,
+ b->bdaddr_type);
+
+ /* If the device is not likely to connect or report, remove it
+ * from the whitelist. Make an exception for wakeable devices if
+ * we're removing only non-wakeable devices (we want them to
+ * stay in whitelist).
*/
if (!hci_pend_le_action_lookup(&hdev->pend_le_conns,
&b->bdaddr, b->bdaddr_type) &&
!hci_pend_le_action_lookup(&hdev->pend_le_reports,
- &b->bdaddr, b->bdaddr_type)) {
- struct hci_cp_le_del_from_white_list cp;
-
- cp.bdaddr_type = b->bdaddr_type;
- bacpy(&cp.bdaddr, &b->bdaddr);
+ &b->bdaddr, b->bdaddr_type) &&
+ (!wakeable && remove_nonwakeable)) {
+ BT_DBG("Removing %pMR (0x%x) - not pending or wakeable",
+ &b->bdaddr, b->bdaddr_type);
+ del_from_white_list(req, &b->bdaddr, b->bdaddr_type);
+ continue;
+ }
- hci_req_add(req, HCI_OP_LE_DEL_FROM_WHITE_LIST,
- sizeof(cp), &cp);
+ /* If we're removing non wakeable devices and this whitelist
+ * entry is not wake capable, we remove it from the whitelist.
+ */
+ if (remove_nonwakeable && !wakeable) {
+ BT_DBG("Removing %pMR (0x%x) - not wake capable",
+ &b->bdaddr, b->bdaddr_type);
+ del_from_white_list(req, &b->bdaddr, b->bdaddr_type);
continue;
}
- if (hci_find_irk_by_addr(hdev, &b->bdaddr, b->bdaddr_type)) {
+ if (!allow_rpa &&
+ hci_find_irk_by_addr(hdev, &b->bdaddr, b->bdaddr_type)) {
/* White list can not be used with RPAs */
return 0x00;
}
@@ -740,14 +772,20 @@ static u8 update_white_list(struct hci_request *req)
return 0x00;
}
- if (hci_find_irk_by_addr(hdev, ¶ms->addr,
- params->addr_type)) {
+ if (!allow_rpa && hci_find_irk_by_addr(hdev, ¶ms->addr,
+ params->addr_type)) {
/* White list can not be used with RPAs */
return 0x00;
}
+ if (remove_nonwakeable &&
+ !hci_bdaddr_list_lookup(&hdev->wakeable, &b->bdaddr,
+ b->bdaddr_type))
+ continue;
+
white_list_entries++;
add_to_white_list(req, params);
+ BT_DBG("Adding %pMR to whitelist", ¶ms->addr);
}
/* After adding all new pending connections, walk through
@@ -764,14 +802,20 @@ static u8 update_white_list(struct hci_request *req)
return 0x00;
}
- if (hci_find_irk_by_addr(hdev, ¶ms->addr,
- params->addr_type)) {
+ if (!allow_rpa && hci_find_irk_by_addr(hdev, ¶ms->addr,
+ params->addr_type)) {
/* White list can not be used with RPAs */
return 0x00;
}
+ if (remove_nonwakeable &&
+ !hci_bdaddr_list_lookup(&hdev->wakeable, &b->bdaddr,
+ b->bdaddr_type))
+ continue;
+
white_list_entries++;
add_to_white_list(req, params);
+ BT_DBG("Adding %pMR to whitelist", ¶ms->addr);
}
/* Select filter policy to use white list */
@@ -784,7 +828,8 @@ static bool scan_use_rpa(struct hci_dev *hdev)
}
static void hci_req_start_scan(struct hci_request *req, u8 type, u16 interval,
- u16 window, u8 own_addr_type, u8 filter_policy)
+ u16 window, u8 own_addr_type, u8 filter_policy,
+ u8 filter_dup)
{
struct hci_dev *hdev = req->hdev;
@@ -836,7 +881,7 @@ static void hci_req_start_scan(struct hci_request *req, u8 type, u16 interval,
memset(&ext_enable_cp, 0, sizeof(ext_enable_cp));
ext_enable_cp.enable = LE_SCAN_ENABLE;
- ext_enable_cp.filter_dup = LE_SCAN_FILTER_DUP_ENABLE;
+ ext_enable_cp.filter_dup = filter_dup;
hci_req_add(req, HCI_OP_LE_SET_EXT_SCAN_ENABLE,
sizeof(ext_enable_cp), &ext_enable_cp);
@@ -855,17 +900,47 @@ static void hci_req_start_scan(struct hci_request *req, u8 type, u16 interval,
memset(&enable_cp, 0, sizeof(enable_cp));
enable_cp.enable = LE_SCAN_ENABLE;
- enable_cp.filter_dup = LE_SCAN_FILTER_DUP_ENABLE;
+ enable_cp.filter_dup = filter_dup;
hci_req_add(req, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(enable_cp),
&enable_cp);
}
}
-void hci_req_add_le_passive_scan(struct hci_request *req)
+static u16 __hci_get_scan_interval(struct hci_dev *hdev, bool suspending)
+{
+ if (suspending)
+ return 0x0060;
+ else
+ return hdev->le_scan_interval;
+}
+
+static u16 __hci_get_scan_window(struct hci_dev *hdev, bool suspending)
+{
+ if (suspending)
+ return 0x0012;
+ else
+ return hdev->le_scan_window;
+}
+
+void hci_req_add_le_passive_scan(struct hci_request *req, bool suspending)
{
struct hci_dev *hdev = req->hdev;
u8 own_addr_type;
u8 filter_policy;
+ u8 filter_dup;
+
+ /* We allow whitelisting even with RPAs in suspend. In the worst case,
+ * we won't be able to wake from devices that use the privacy1.2
+ * features. Additionally, once we support privacy1.2 and IRK
+ * offloading, we can update this to also check for those conditions.
+ */
+ bool allow_rpa = suspending;
+
+ /* Early exit if we've frozen filters for suspend*/
+ if (hdev->suspend.freeze_filters) {
+ BT_DBG("Filters are frozen for suspend");
+ return;
+ }
/* Set require_privacy to false since no SCAN_REQ are send
* during passive scanning. Not using an non-resolvable address
@@ -881,7 +956,8 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
* happen before enabling scanning. The controller does
* not allow white list modification while scanning.
*/
- filter_policy = update_white_list(req);
+ BT_DBG("Updating white list with suspending = %d", suspending);
+ filter_policy = update_white_list(req, allow_rpa, suspending);
/* When the controller is using random resolvable addresses and
* with that having LE privacy enabled, then controllers with
@@ -896,8 +972,14 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
(hdev->le_features[0] & HCI_LE_EXT_SCAN_POLICY))
filter_policy |= 0x02;
- hci_req_start_scan(req, LE_SCAN_PASSIVE, hdev->le_scan_interval,
- hdev->le_scan_window, own_addr_type, filter_policy);
+ filter_dup = suspending ? LE_SCAN_FILTER_DUP_DISABLE :
+ LE_SCAN_FILTER_DUP_ENABLE;
+
+ BT_DBG("LE passive scan with whitelist = %d", filter_policy);
+ hci_req_start_scan(req, LE_SCAN_PASSIVE,
+ __hci_get_scan_interval(hdev, suspending),
+ __hci_get_scan_window(hdev, suspending),
+ own_addr_type, filter_policy, filter_dup);
}
static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
@@ -918,6 +1000,170 @@ static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
return adv_instance->scan_rsp_len;
}
+static void hci_req_set_event_filter(struct hci_request *req, bool clear)
+{
+ struct bdaddr_list *b;
+ struct hci_cp_set_event_filter f;
+ struct hci_dev *hdev = req->hdev;
+ int filters_updated = 0;
+ u8 scan;
+
+ /* Always clear event filter when starting */
+ memset(&f, 0, sizeof(f));
+ f.flt_type = HCI_FLT_CLEAR_ALL;
+ hci_req_add(req, HCI_OP_SET_EVENT_FLT, 1, &f);
+
+ if (!clear) {
+ list_for_each_entry(b, &hdev->wakeable, list) {
+ if (b->bdaddr_type != BDADDR_BREDR)
+ continue;
+
+ memset(&f, 0, sizeof(f));
+ bacpy(&f.cond.conn.addr.bdaddr, &b->bdaddr);
+ f.flt_type = HCI_FLT_CONN_SETUP;
+ f.cond_type = HCI_CONN_SETUP_ALLOW_BDADDR;
+ f.cond.conn.addr.auto_accept = HCI_CONN_SETUP_AUTO_ON;
+
+ BT_DBG("Adding event filters for %pMR", &b->bdaddr);
+ hci_req_add(req, HCI_OP_SET_EVENT_FLT, sizeof(f), &f);
+
+ filters_updated++;
+ }
+
+ scan = filters_updated ? SCAN_PAGE : SCAN_DISABLED;
+ hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
+ } else {
+ /* Restore page scan to normal (i.e. there are disconnected
+ * devices in the whitelist.
+ */
+ __hci_req_update_scan(req);
+ }
+}
+
+static void hci_req_enable_le_suspend_scan(struct hci_request *req,
+ bool suspending)
+{
+ /* Can't change params without disabling first */
+ hci_req_add_le_scan_disable(req);
+
+ /* Configure params and enable scanning */
+ hci_req_add_le_passive_scan(req, suspending);
+
+ /* Block suspend notifier on response */
+ set_bit(SUSPEND_LE_SET_SCAN_ENABLE, req->hdev->suspend.tasks);
+}
+
+static void le_suspend_req_complete(struct hci_dev *hdev, u8 status, u16 opcode)
+{
+ BT_DBG("Request complete opcode=0x%x, status=0x%x", opcode, status);
+
+ /* Expecting LE Set scan to return */
+ if (opcode == HCI_OP_LE_SET_SCAN_ENABLE &&
+ test_and_clear_bit(SUSPEND_LE_SET_SCAN_ENABLE,
+ hdev->suspend.tasks)) {
+ wake_up(&hdev->suspend.tasks_wait_q);
+ }
+}
+
+/* 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;
+
+ if (next == hdev->suspend.state) {
+ BT_DBG("Same state before and after: %d", next);
+ goto done;
+ }
+
+ hci_req_init(&req, hdev);
+ if (next == BT_SUSPENDED) {
+ /* 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->suspend.discovery_paused = true;
+ hdev->suspend.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->suspend.advertising_paused = true;
+ hdev->suspend.advertising_old_state = old_state;
+
+ /* Enable event filter for existing devices */
+ hci_req_set_event_filter(&req, false);
+
+ /* Enable passive scan at lower duty cycle */
+ hci_req_enable_le_suspend_scan(&req, true);
+
+ hdev->suspend.freeze_filters = true;
+
+ /* Run commands before disconnecting */
+ hci_req_run(&req, le_suspend_req_complete);
+
+ hdev->suspend.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);
+ hdev->suspend.disconnect_counter++;
+ }
+
+ if (hdev->suspend.disconnect_counter > 0) {
+ BT_DBG("Had %d disconnects. Will wait on them",
+ hdev->suspend.disconnect_counter);
+ set_bit(SUSPEND_DISCONNECTING, hdev->suspend.tasks);
+ }
+ } else {
+ hdev->suspend.freeze_filters = false;
+
+ /* Clear event filter */
+ hci_req_set_event_filter(&req, true);
+
+ /* Reset passive/background scanning to normal */
+ hci_req_enable_le_suspend_scan(&req, false);
+
+ /* Unpause advertising */
+ hdev->suspend.advertising_paused = false;
+ if (hdev->suspend.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->suspend.advertising_old_state = 0;
+ }
+
+ /* Unpause discovery */
+ hdev->suspend.discovery_paused = false;
+ if (hdev->suspend.discovery_old_state != DISCOVERY_STOPPED &&
+ hdev->suspend.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, le_suspend_req_complete);
+ }
+
+ hdev->suspend.state = next;
+
+done:
+ clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend.tasks);
+ wake_up(&hdev->suspend.tasks_wait_q);
+}
+
static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev)
{
u8 instance = hdev->cur_adv_instance;
@@ -2015,6 +2261,9 @@ void __hci_req_update_scan(struct hci_request *req)
if (mgmt_powering_down(hdev))
return;
+ if (hdev->suspend.freeze_filters)
+ return;
+
if (hci_dev_test_flag(hdev, HCI_CONNECTABLE) ||
disconnected_whitelist_entries(hdev))
scan = SCAN_PAGE;
@@ -2538,7 +2787,7 @@ static int active_scan(struct hci_request *req, unsigned long opt)
own_addr_type = ADDR_LE_DEV_PUBLIC;
hci_req_start_scan(req, LE_SCAN_ACTIVE, interval, DISCOV_LE_SCAN_WIN,
- own_addr_type, 0);
+ own_addr_type, 0, LE_SCAN_FILTER_DUP_DISABLE);
return 0;
}
@@ -66,7 +66,9 @@ void __hci_req_update_name(struct hci_request *req);
void __hci_req_update_eir(struct hci_request *req);
void hci_req_add_le_scan_disable(struct hci_request *req);
-void hci_req_add_le_passive_scan(struct hci_request *req);
+void hci_req_add_le_passive_scan(struct hci_request *req, bool suspending);
+
+void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next);
void hci_req_reenable_advertising(struct hci_dev *hdev);
void __hci_req_enable_advertising(struct hci_request *req);
@@ -24,6 +24,7 @@
/* Bluetooth HCI Management interface */
+#include <linux/delay.h>
#include <linux/module.h>
#include <asm/unaligned.h>
@@ -1385,6 +1386,12 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
goto failed;
}
+ if (hdev->suspend.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;
@@ -3868,6 +3875,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_DBG("Unpaused discovery");
+ wake_up(&hdev->suspend.tasks_wait_q);
+ }
}
static bool discovery_type_is_valid(struct hci_dev *hdev, uint8_t type,
@@ -3929,6 +3943,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->suspend.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.
*/
@@ -4096,6 +4117,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_DBG("Paused discovery");
+ wake_up(&hdev->suspend.tasks_wait_q);
+ }
}
static int stop_discovery(struct sock *sk, struct hci_dev *hdev, void *data,
@@ -4327,6 +4354,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_DBG("Paused advertising");
+ wake_up(&hdev->suspend.tasks_wait_q);
+ } else if (test_and_clear_bit(SUSPEND_UNPAUSE_ADVERTISING,
+ hdev->suspend.tasks)) {
+ BT_DBG("Unpaused advertising");
+ wake_up(&hdev->suspend.tasks_wait_q);
+ }
+
/* If "Set Advertising" was just disabled and instance advertising was
* set up earlier, then re-enable multi-instance advertising.
*/
@@ -4378,6 +4416,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->suspend.advertising_paused)
+ return mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
+ MGMT_STATUS_BUSY);
+
hci_dev_lock(hdev);
val = !!cp->val;
@@ -4557,7 +4599,7 @@ static int set_scan_params(struct sock *sk, struct hci_dev *hdev,
hci_req_init(&req, hdev);
hci_req_add_le_scan_disable(&req);
- hci_req_add_le_passive_scan(&req);
+ hci_req_add_le_passive_scan(&req, false);
hci_req_run(&req, NULL);
}
@@ -7453,6 +7495,14 @@ void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr,
mgmt_event(MGMT_EV_DEVICE_DISCONNECTED, hdev, &ev, sizeof(ev), sk);
+ if (hdev->suspend.disconnect_counter > 0) {
+ hdev->suspend.disconnect_counter--;
+ if (hdev->suspend.disconnect_counter <= 0) {
+ clear_bit(SUSPEND_DISCONNECTING, hdev->suspend.tasks);
+ wake_up(&hdev->suspend.tasks_wait_q);
+ }
+ }
+
if (sk)
sock_put(sk);
Register for PM_SUSPEND_PREPARE and PM_POST_SUSPEND to make sure the Bluetooth controller is prepared correctly for suspend/resume. The suspend notifier will wait for all tasks to complete before returning. At a high level, we do the following when entering suspend: - Pause any active discovery - Pause any active advertising - Set the event filter with addresses of wake capable Classic devices and enable Page Scan - Update the LE whitelist to only include devices that can wake the system, update the scan parameters and enable passive scanning. - Disconnect all devices with a POWER_DOWN reason On resume, it reverses the above operations: - Clear event filters and restore page scan - Restore LE whitelist and restore passive scan - If advertising was active before suspend, re-enable it - If discovery was active before suspend, re-enable it Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> --- include/net/bluetooth/hci.h | 30 +++- include/net/bluetooth/hci_core.h | 45 +++++ net/bluetooth/hci_core.c | 70 ++++++++ net/bluetooth/hci_event.c | 24 ++- net/bluetooth/hci_request.c | 297 ++++++++++++++++++++++++++++--- net/bluetooth/hci_request.h | 4 +- net/bluetooth/mgmt.c | 52 +++++- 7 files changed, 486 insertions(+), 36 deletions(-)