Some older controllers fail to enter a quiescent state reliably when supporting remote wake. For those cases, add a quirk that will power down the controller when suspending and power it back up when resuming. Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx> Reviewed-by: Miao-chen Chou <mcchou@xxxxxxxxxxxx> --- include/net/bluetooth/hci.h | 7 +++++ include/net/bluetooth/hci_core.h | 4 +++ net/bluetooth/hci_core.c | 48 ++++++++++++++++++++++++++++++-- net/bluetooth/hci_request.c | 26 ++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index c8e67042a3b14c..88d5c9554e4840 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -238,6 +238,13 @@ enum { * during the hdev->setup vendor callback. */ HCI_QUIRK_BROKEN_ERR_DATA_REPORTING, + + /* When this quirk is set, the adapter will be powered down during + * system suspend and powerd up on resume. This should be used on + * controllers that don't behave well during suspend, either causing + * spurious wakeups or not entering a suspend state reliably. + */ + HCI_QUIRK_POWER_DOWN_SYSTEM_SUSPEND, }; /* HCI device flags */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index ff32d5a856f17f..e7dc6e3efbf246 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -90,6 +90,7 @@ struct discovery_state { }; #define SUSPEND_NOTIFIER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ +#define SUSPEND_POWER_DOWN_TIMEOUT msecs_to_jiffies(1000) enum suspend_tasks { SUSPEND_PAUSE_DISCOVERY, @@ -112,6 +113,9 @@ enum suspended_state { BT_RUNNING = 0, BT_SUSPEND_DISCONNECT, BT_SUSPEND_CONFIGURE_WAKE, + BT_SUSPEND_DO_POWER_DOWN, + BT_SUSPEND_DO_POWER_UP, + BT_SUSPEND_POWERED_DOWN, /* Powered down prior to suspend */ }; struct hci_conn_hash { diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 8e90850d6d769e..d73e097d3ce16b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3562,6 +3562,7 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action, container_of(nb, struct hci_dev, suspend_notifier); int ret = 0; u8 state = BT_RUNNING; + bool powered; /* If powering down, wait for completion. */ if (mgmt_powering_down(hdev)) { @@ -3571,8 +3572,51 @@ static int hci_suspend_notifier(struct notifier_block *nb, unsigned long action, goto done; } - /* Suspend notifier should only act on events when powered. */ - if (!hdev_is_powered(hdev)) + powered = hdev_is_powered(hdev); + + /* Update the suspend state when entering suspend if the system is + * currently powered off or if it is powered on but was previously + * powered off. + */ + if (action == PM_SUSPEND_PREPARE) { + /* Must hold dev lock when modifying suspend state. */ + hci_dev_lock(hdev); + if (powered && hdev->suspend_state == BT_SUSPEND_POWERED_DOWN) + hdev->suspend_state = BT_RUNNING; + else if (!powered && + hdev->suspend_state != BT_SUSPEND_POWERED_DOWN) + hdev->suspend_state = BT_SUSPEND_POWERED_DOWN; + + hci_dev_unlock(hdev); + } + + /* When the power down quirk is set, we power down the adapter when + * suspending and power it up when resuming. If the adapter was already + * powered down before suspend, we don't do anything here. + */ + if (test_bit(HCI_QUIRK_POWER_DOWN_SYSTEM_SUSPEND, &hdev->quirks) && + hdev->suspend_state != BT_SUSPEND_POWERED_DOWN) { + if (action == PM_SUSPEND_PREPARE && powered) { + state = BT_SUSPEND_DO_POWER_DOWN; + ret = hci_change_suspend_state(hdev, state); + + /* Emit that we're powering down for suspend */ + hci_clear_wake_reason(hdev); + mgmt_suspending(hdev, state); + goto done; + } else if (action == PM_POST_SUSPEND && !powered) { + /* Emit that we're resuming before powering up. */ + mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr, + hdev->wake_addr_type); + + state = BT_SUSPEND_DO_POWER_UP; + ret = hci_change_suspend_state(hdev, state); + goto done; + } + } + + /* Skip to end if we weren't powered. */ + if (!powered) goto done; if (action == PM_SUSPEND_PREPARE) { diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c index 048d4db9d4ea53..804bd0652edd1c 100644 --- a/net/bluetooth/hci_request.c +++ b/net/bluetooth/hci_request.c @@ -1194,6 +1194,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next) struct hci_request req; u8 page_scan; int disconnect_counter; + int err; if (next == hdev->suspend_state) { bt_dev_dbg(hdev, "Same state before and after: %d", next); @@ -1273,7 +1274,7 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next) /* Pause scan changes again. */ hdev->scanning_paused = true; hci_req_run(&req, suspend_req_complete); - } else { + } else if (next == BT_RUNNING) { hdev->suspended = false; hdev->scanning_paused = false; @@ -1306,6 +1307,29 @@ void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next) } hci_req_run(&req, suspend_req_complete); + } else if (next == BT_SUSPEND_DO_POWER_DOWN) { + hdev->suspended = true; + hdev->scanning_paused = true; + + err = hci_clean_up_state(hdev); + + if (!err) + queue_delayed_work(hdev->req_workqueue, + &hdev->power_off, + SUSPEND_POWER_DOWN_TIMEOUT); + + if (err == -ENODATA) { + cancel_delayed_work(&hdev->power_off); + queue_work(hdev->req_workqueue, &hdev->power_off.work); + err = 0; + } + + set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks); + } else if (next == BT_SUSPEND_DO_POWER_UP) { + hdev->suspended = false; + hdev->scanning_paused = false; + + queue_work(hdev->req_workqueue, &hdev->power_on); } hdev->suspend_state = next; -- 2.29.2.299.gdc1121823c-goog