mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 13:11:40 +00:00
Bluetooth: Handle LE devices during suspend
To handle LE devices, we must first disable passive scanning and disconnect all connected devices. Once that is complete, we update the whitelist and re-enable scanning Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
parent
4f40afc6c7
commit
dd522a7429
@ -607,6 +607,7 @@ struct hci_conn_params {
|
|||||||
|
|
||||||
struct hci_conn *conn;
|
struct hci_conn *conn;
|
||||||
bool explicit_connect;
|
bool explicit_connect;
|
||||||
|
bool wakeable;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct list_head hci_dev_list;
|
extern struct list_head hci_dev_list;
|
||||||
|
@ -34,6 +34,9 @@
|
|||||||
#define HCI_REQ_PEND 1
|
#define HCI_REQ_PEND 1
|
||||||
#define HCI_REQ_CANCELED 2
|
#define HCI_REQ_CANCELED 2
|
||||||
|
|
||||||
|
#define LE_SUSPEND_SCAN_WINDOW 0x0012
|
||||||
|
#define LE_SUSPEND_SCAN_INTERVAL 0x0060
|
||||||
|
|
||||||
void hci_req_init(struct hci_request *req, struct hci_dev *hdev)
|
void hci_req_init(struct hci_request *req, struct hci_dev *hdev)
|
||||||
{
|
{
|
||||||
skb_queue_head_init(&req->cmd_q);
|
skb_queue_head_init(&req->cmd_q);
|
||||||
@ -654,6 +657,11 @@ void hci_req_add_le_scan_disable(struct hci_request *req)
|
|||||||
{
|
{
|
||||||
struct hci_dev *hdev = req->hdev;
|
struct hci_dev *hdev = req->hdev;
|
||||||
|
|
||||||
|
if (hdev->scanning_paused) {
|
||||||
|
bt_dev_dbg(hdev, "Scanning is paused for suspend");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (use_ext_scan(hdev)) {
|
if (use_ext_scan(hdev)) {
|
||||||
struct hci_cp_le_set_ext_scan_enable cp;
|
struct hci_cp_le_set_ext_scan_enable cp;
|
||||||
|
|
||||||
@ -670,15 +678,55 @@ void hci_req_add_le_scan_disable(struct hci_request *req)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_to_white_list(struct hci_request *req,
|
static void del_from_white_list(struct hci_request *req, bdaddr_t *bdaddr,
|
||||||
struct hci_conn_params *params)
|
u8 bdaddr_type)
|
||||||
|
{
|
||||||
|
struct hci_cp_le_del_from_white_list cp;
|
||||||
|
|
||||||
|
cp.bdaddr_type = bdaddr_type;
|
||||||
|
bacpy(&cp.bdaddr, bdaddr);
|
||||||
|
|
||||||
|
bt_dev_dbg(req->hdev, "Remove %pMR (0x%x) from whitelist", &cp.bdaddr,
|
||||||
|
cp.bdaddr_type);
|
||||||
|
hci_req_add(req, HCI_OP_LE_DEL_FROM_WHITE_LIST, sizeof(cp), &cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adds connection to white list if needed. On error, returns -1. */
|
||||||
|
static int add_to_white_list(struct hci_request *req,
|
||||||
|
struct hci_conn_params *params, u8 *num_entries,
|
||||||
|
bool allow_rpa)
|
||||||
{
|
{
|
||||||
struct hci_cp_le_add_to_white_list cp;
|
struct hci_cp_le_add_to_white_list cp;
|
||||||
|
struct hci_dev *hdev = req->hdev;
|
||||||
|
|
||||||
|
/* Already in white list */
|
||||||
|
if (hci_bdaddr_list_lookup(&hdev->le_white_list, ¶ms->addr,
|
||||||
|
params->addr_type))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Select filter policy to accept all advertising */
|
||||||
|
if (*num_entries >= hdev->le_white_list_size)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* White list can not be used with RPAs */
|
||||||
|
if (!allow_rpa &&
|
||||||
|
hci_find_irk_by_addr(hdev, ¶ms->addr, params->addr_type)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* During suspend, only wakeable devices can be in whitelist */
|
||||||
|
if (hdev->suspended && !params->wakeable)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*num_entries += 1;
|
||||||
cp.bdaddr_type = params->addr_type;
|
cp.bdaddr_type = params->addr_type;
|
||||||
bacpy(&cp.bdaddr, ¶ms->addr);
|
bacpy(&cp.bdaddr, ¶ms->addr);
|
||||||
|
|
||||||
|
bt_dev_dbg(hdev, "Add %pMR (0x%x) to whitelist", &cp.bdaddr,
|
||||||
|
cp.bdaddr_type);
|
||||||
hci_req_add(req, HCI_OP_LE_ADD_TO_WHITE_LIST, sizeof(cp), &cp);
|
hci_req_add(req, HCI_OP_LE_ADD_TO_WHITE_LIST, sizeof(cp), &cp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 update_white_list(struct hci_request *req)
|
static u8 update_white_list(struct hci_request *req)
|
||||||
@ -686,7 +734,14 @@ static u8 update_white_list(struct hci_request *req)
|
|||||||
struct hci_dev *hdev = req->hdev;
|
struct hci_dev *hdev = req->hdev;
|
||||||
struct hci_conn_params *params;
|
struct hci_conn_params *params;
|
||||||
struct bdaddr_list *b;
|
struct bdaddr_list *b;
|
||||||
uint8_t white_list_entries = 0;
|
u8 num_entries = 0;
|
||||||
|
bool pend_conn, pend_report;
|
||||||
|
/* 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 = hdev->suspended;
|
||||||
|
|
||||||
/* Go through the current white list programmed into the
|
/* Go through the current white list programmed into the
|
||||||
* controller one by one and check if that address is still
|
* controller one by one and check if that address is still
|
||||||
@ -695,29 +750,28 @@ static u8 update_white_list(struct hci_request *req)
|
|||||||
* command to remove it from the controller.
|
* command to remove it from the controller.
|
||||||
*/
|
*/
|
||||||
list_for_each_entry(b, &hdev->le_white_list, list) {
|
list_for_each_entry(b, &hdev->le_white_list, list) {
|
||||||
/* If the device is neither in pend_le_conns nor
|
pend_conn = hci_pend_le_action_lookup(&hdev->pend_le_conns,
|
||||||
* pend_le_reports then remove it from the whitelist.
|
&b->bdaddr,
|
||||||
|
b->bdaddr_type);
|
||||||
|
pend_report = hci_pend_le_action_lookup(&hdev->pend_le_reports,
|
||||||
|
&b->bdaddr,
|
||||||
|
b->bdaddr_type);
|
||||||
|
|
||||||
|
/* If the device is not likely to connect or report,
|
||||||
|
* remove it from the whitelist.
|
||||||
*/
|
*/
|
||||||
if (!hci_pend_le_action_lookup(&hdev->pend_le_conns,
|
if (!pend_conn && !pend_report) {
|
||||||
&b->bdaddr, b->bdaddr_type) &&
|
del_from_white_list(req, &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);
|
|
||||||
|
|
||||||
hci_req_add(req, HCI_OP_LE_DEL_FROM_WHITE_LIST,
|
|
||||||
sizeof(cp), &cp);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hci_find_irk_by_addr(hdev, &b->bdaddr, b->bdaddr_type)) {
|
/* White list can not be used with RPAs */
|
||||||
/* White list can not be used with RPAs */
|
if (!allow_rpa &&
|
||||||
|
hci_find_irk_by_addr(hdev, &b->bdaddr, b->bdaddr_type)) {
|
||||||
return 0x00;
|
return 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
white_list_entries++;
|
num_entries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Since all no longer valid white list entries have been
|
/* Since all no longer valid white list entries have been
|
||||||
@ -731,47 +785,17 @@ static u8 update_white_list(struct hci_request *req)
|
|||||||
* white list.
|
* white list.
|
||||||
*/
|
*/
|
||||||
list_for_each_entry(params, &hdev->pend_le_conns, action) {
|
list_for_each_entry(params, &hdev->pend_le_conns, action) {
|
||||||
if (hci_bdaddr_list_lookup(&hdev->le_white_list,
|
if (add_to_white_list(req, params, &num_entries, allow_rpa))
|
||||||
¶ms->addr, params->addr_type))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (white_list_entries >= hdev->le_white_list_size) {
|
|
||||||
/* Select filter policy to accept all advertising */
|
|
||||||
return 0x00;
|
return 0x00;
|
||||||
}
|
|
||||||
|
|
||||||
if (hci_find_irk_by_addr(hdev, ¶ms->addr,
|
|
||||||
params->addr_type)) {
|
|
||||||
/* White list can not be used with RPAs */
|
|
||||||
return 0x00;
|
|
||||||
}
|
|
||||||
|
|
||||||
white_list_entries++;
|
|
||||||
add_to_white_list(req, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* After adding all new pending connections, walk through
|
/* After adding all new pending connections, walk through
|
||||||
* the list of pending reports and also add these to the
|
* the list of pending reports and also add these to the
|
||||||
* white list if there is still space.
|
* white list if there is still space. Abort if space runs out.
|
||||||
*/
|
*/
|
||||||
list_for_each_entry(params, &hdev->pend_le_reports, action) {
|
list_for_each_entry(params, &hdev->pend_le_reports, action) {
|
||||||
if (hci_bdaddr_list_lookup(&hdev->le_white_list,
|
if (add_to_white_list(req, params, &num_entries, allow_rpa))
|
||||||
¶ms->addr, params->addr_type))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (white_list_entries >= hdev->le_white_list_size) {
|
|
||||||
/* Select filter policy to accept all advertising */
|
|
||||||
return 0x00;
|
return 0x00;
|
||||||
}
|
|
||||||
|
|
||||||
if (hci_find_irk_by_addr(hdev, ¶ms->addr,
|
|
||||||
params->addr_type)) {
|
|
||||||
/* White list can not be used with RPAs */
|
|
||||||
return 0x00;
|
|
||||||
}
|
|
||||||
|
|
||||||
white_list_entries++;
|
|
||||||
add_to_white_list(req, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select filter policy to use white list */
|
/* Select filter policy to use white list */
|
||||||
@ -866,6 +890,12 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
|
|||||||
struct hci_dev *hdev = req->hdev;
|
struct hci_dev *hdev = req->hdev;
|
||||||
u8 own_addr_type;
|
u8 own_addr_type;
|
||||||
u8 filter_policy;
|
u8 filter_policy;
|
||||||
|
u8 window, interval;
|
||||||
|
|
||||||
|
if (hdev->scanning_paused) {
|
||||||
|
bt_dev_dbg(hdev, "Scanning is paused for suspend");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set require_privacy to false since no SCAN_REQ are send
|
/* Set require_privacy to false since no SCAN_REQ are send
|
||||||
* during passive scanning. Not using an non-resolvable address
|
* during passive scanning. Not using an non-resolvable address
|
||||||
@ -896,8 +926,17 @@ void hci_req_add_le_passive_scan(struct hci_request *req)
|
|||||||
(hdev->le_features[0] & HCI_LE_EXT_SCAN_POLICY))
|
(hdev->le_features[0] & HCI_LE_EXT_SCAN_POLICY))
|
||||||
filter_policy |= 0x02;
|
filter_policy |= 0x02;
|
||||||
|
|
||||||
hci_req_start_scan(req, LE_SCAN_PASSIVE, hdev->le_scan_interval,
|
if (hdev->suspended) {
|
||||||
hdev->le_scan_window, own_addr_type, filter_policy);
|
window = LE_SUSPEND_SCAN_WINDOW;
|
||||||
|
interval = LE_SUSPEND_SCAN_INTERVAL;
|
||||||
|
} else {
|
||||||
|
window = hdev->le_scan_window;
|
||||||
|
interval = hdev->le_scan_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_dev_dbg(hdev, "LE passive scan with whitelist = %d", filter_policy);
|
||||||
|
hci_req_start_scan(req, LE_SCAN_PASSIVE, interval, window,
|
||||||
|
own_addr_type, filter_policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
|
static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
|
||||||
@ -957,6 +996,18 @@ static void hci_req_set_event_filter(struct hci_request *req)
|
|||||||
hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
|
hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hci_req_config_le_suspend_scan(struct hci_request *req)
|
||||||
|
{
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* Block suspend notifier on response */
|
||||||
|
set_bit(SUSPEND_SCAN_ENABLE, req->hdev->suspend_tasks);
|
||||||
|
}
|
||||||
|
|
||||||
static void suspend_req_complete(struct hci_dev *hdev, u8 status, u16 opcode)
|
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,
|
bt_dev_dbg(hdev, "Request complete opcode=0x%x, status=0x%x", opcode,
|
||||||
@ -991,6 +1042,9 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
|
|||||||
page_scan = SCAN_DISABLED;
|
page_scan = SCAN_DISABLED;
|
||||||
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &page_scan);
|
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &page_scan);
|
||||||
|
|
||||||
|
/* Disable LE passive scan */
|
||||||
|
hci_req_add_le_scan_disable(&req);
|
||||||
|
|
||||||
/* Mark task needing completion */
|
/* Mark task needing completion */
|
||||||
set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
|
set_bit(SUSPEND_SCAN_DISABLE, hdev->suspend_tasks);
|
||||||
|
|
||||||
@ -1018,6 +1072,8 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
|
|||||||
hdev->scanning_paused = false;
|
hdev->scanning_paused = false;
|
||||||
/* Enable event filter for paired devices */
|
/* Enable event filter for paired devices */
|
||||||
hci_req_set_event_filter(&req);
|
hci_req_set_event_filter(&req);
|
||||||
|
/* Enable passive scan at lower duty cycle */
|
||||||
|
hci_req_config_le_suspend_scan(&req);
|
||||||
/* Pause scan changes again. */
|
/* Pause scan changes again. */
|
||||||
hdev->scanning_paused = true;
|
hdev->scanning_paused = true;
|
||||||
hci_req_run(&req, suspend_req_complete);
|
hci_req_run(&req, suspend_req_complete);
|
||||||
@ -1026,6 +1082,8 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
|
|||||||
hdev->scanning_paused = false;
|
hdev->scanning_paused = false;
|
||||||
|
|
||||||
hci_req_clear_event_filter(&req);
|
hci_req_clear_event_filter(&req);
|
||||||
|
/* Reset passive/background scanning to normal */
|
||||||
|
hci_req_config_le_suspend_scan(&req);
|
||||||
hci_req_run(&req, suspend_req_complete);
|
hci_req_run(&req, suspend_req_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user