Bluetooth: Handle PM_SUSPEND_PREPARE and PM_POST_SUSPEND

Register for PM_SUSPEND_PREPARE and PM_POST_SUSPEND to make sure the
Bluetooth controller is prepared correctly for suspend/resume. Implement
the registration, scheduling and task handling portions only in this
patch.

Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
Abhishek Pandit-Subedi 2020-03-11 08:54:00 -07:00 committed by Marcel Holtmann
parent 72da7b2cca
commit 9952d90ea2
4 changed files with 126 additions and 0 deletions

View File

@ -88,6 +88,20 @@ struct discovery_state {
unsigned long scan_duration;
};
#define SUSPEND_NOTIFIER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */
enum suspend_tasks {
SUSPEND_POWERING_DOWN,
SUSPEND_PREPARE_NOTIFIER,
__SUSPEND_NUM_TASKS
};
enum suspended_state {
BT_RUNNING = 0,
BT_SUSPENDED,
};
struct hci_conn_hash {
struct list_head list;
unsigned int acl_num;
@ -390,6 +404,15 @@ struct hci_dev {
void *smp_bredr_data;
struct discovery_state discovery;
struct notifier_block suspend_notifier;
struct work_struct suspend_prepare;
enum suspended_state suspend_state_next;
enum suspended_state suspend_state;
wait_queue_head_t suspend_wait_q;
DECLARE_BITMAP(suspend_tasks, __SUSPEND_NUM_TASKS);
struct hci_conn_hash conn_hash;
struct list_head mgmt_pending;

View File

@ -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>
@ -1787,6 +1789,9 @@ int hci_dev_do_close(struct hci_dev *hdev)
clear_bit(HCI_RUNNING, &hdev->flags);
hci_sock_dev_event(hdev, HCI_DEV_CLOSE);
if (test_and_clear_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks))
wake_up(&hdev->suspend_wait_q);
/* After this point our queues are empty
* and no tasks are scheduled. */
hdev->close(hdev);
@ -3264,6 +3269,78 @@ 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_wait_q,
WAKE_COND, SUSPEND_NOTIFIER_TIMEOUT);
if (ret == 0) {
bt_dev_dbg(hdev, "Timed out waiting for suspend");
for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) {
if (test_bit(i, hdev->suspend_tasks))
bt_dev_dbg(hdev, "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_state_next);
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 powering down, wait for completion. */
if (mgmt_powering_down(hdev)) {
set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
ret = hci_suspend_wait_event(hdev);
if (ret)
goto done;
}
/* Suspend notifier should only act on events when powered. */
if (!hdev_is_powered(hdev))
goto done;
if (action == PM_SUSPEND_PREPARE) {
hdev->suspend_state_next = 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_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);
}
done:
return ret ? notifier_from_errno(-EBUSY) : NOTIFY_STOP;
}
/* Alloc HCI device */
struct hci_dev *hci_alloc_dev(void)
{
@ -3341,6 +3418,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);
@ -3349,6 +3427,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_wait_q);
INIT_DELAYED_WORK(&hdev->cmd_timer, hci_cmd_timeout);
@ -3460,6 +3539,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;
@ -3493,6 +3577,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)) {

View File

@ -918,6 +918,21 @@ static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
return adv_instance->scan_rsp_len;
}
/* Call with hci_dev_lock */
void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next)
{
if (next == hdev->suspend_state) {
bt_dev_dbg(hdev, "Same state before and after: %d", next);
goto done;
}
hdev->suspend_state = next;
done:
clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
wake_up(&hdev->suspend_wait_q);
}
static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev)
{
u8 instance = hdev->cur_adv_instance;

View File

@ -68,6 +68,8 @@ 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_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);
void __hci_req_disable_advertising(struct hci_request *req);