| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/power_manager.h" |
| |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <linux/nl80211.h> |
| |
| #include "shill/control_interface.h" |
| #include "shill/logging.h" |
| #include "shill/power_manager_proxy_interface.h" |
| |
| namespace shill { |
| |
| // static |
| const int PowerManager::kInvalidSuspendId = -1; |
| const char PowerManager::kSuspendDelayDescription[] = "shill"; |
| const char PowerManager::kDarkSuspendDelayDescription[] = "shill"; |
| const int PowerManager::kSuspendTimeoutMilliseconds = 15 * 1000; |
| |
| PowerManager::PowerManager(ControlInterface* control_interface) |
| : control_interface_(control_interface), |
| suspend_delay_registered_(false), |
| suspend_delay_id_(0), |
| dark_suspend_delay_registered_(false), |
| dark_suspend_delay_id_(0), |
| suspending_(false), |
| suspend_ready_(false), |
| suspend_done_deferred_(false), |
| in_dark_resume_(false), |
| current_suspend_id_(0), |
| current_dark_suspend_id_(0), |
| suspend_duration_us_(0), |
| wifi_reg_domain_is_set(false) {} |
| |
| PowerManager::~PowerManager() = default; |
| |
| void PowerManager::Start( |
| base::TimeDelta suspend_delay, |
| const SuspendImminentCallback& suspend_imminent_callback, |
| const SuspendDoneCallback& suspend_done_callback, |
| const DarkSuspendImminentCallback& dark_suspend_imminent_callback) { |
| power_manager_proxy_ = control_interface_->CreatePowerManagerProxy( |
| this, |
| base::Bind(&PowerManager::OnPowerManagerAppeared, base::Unretained(this)), |
| base::Bind(&PowerManager::OnPowerManagerVanished, |
| base::Unretained(this))); |
| suspend_delay_ = suspend_delay; |
| suspend_imminent_callback_ = suspend_imminent_callback; |
| suspend_done_callback_ = suspend_done_callback; |
| dark_suspend_imminent_callback_ = dark_suspend_imminent_callback; |
| } |
| |
| void PowerManager::Stop() { |
| LOG(INFO) << __func__; |
| // We may attempt to unregister with a stale |suspend_delay_id_| if powerd |
| // reappeared behind our back. It is safe to do so. |
| if (suspend_delay_registered_) |
| power_manager_proxy_->UnregisterSuspendDelay(suspend_delay_id_); |
| if (dark_suspend_delay_registered_) |
| power_manager_proxy_->UnregisterDarkSuspendDelay(dark_suspend_delay_id_); |
| |
| suspend_delay_registered_ = false; |
| dark_suspend_delay_registered_ = false; |
| power_manager_proxy_.reset(); |
| } |
| |
| bool PowerManager::ReportSuspendReadiness() { |
| // If |suspend_done_deferred_| is true, a SuspendDone notification was |
| // observed before SuspendReadiness was reported and no further |
| // SuspendImminent notification was observed after the SuspendDone |
| // notification. We don't need to report SuspendReadiness, but instead notify |
| // the deferred SuspendDone. |
| if (suspend_done_deferred_) { |
| LOG(INFO) << __func__ << ": Notifying deferred SuspendDone."; |
| NotifySuspendDone(); |
| return false; |
| } |
| |
| suspend_ready_ = true; |
| if (!suspending_) { |
| LOG(INFO) << __func__ << ": Suspend attempt (" << current_suspend_id_ |
| << ") not active. Ignoring signal."; |
| return false; |
| } |
| return power_manager_proxy_->ReportSuspendReadiness(suspend_delay_id_, |
| current_suspend_id_); |
| } |
| |
| bool PowerManager::ReportDarkSuspendReadiness() { |
| return power_manager_proxy_->ReportDarkSuspendReadiness( |
| dark_suspend_delay_id_, current_dark_suspend_id_); |
| } |
| |
| bool PowerManager::RecordDarkResumeWakeReason(const std::string& wake_reason) { |
| return power_manager_proxy_->RecordDarkResumeWakeReason(wake_reason); |
| } |
| |
| bool PowerManager::ChangeRegDomain(nl80211_dfs_regions domain) { |
| auto new_domain = power_manager::WIFI_REG_DOMAIN_NONE; |
| switch (domain) { |
| case NL80211_DFS_FCC: |
| new_domain = power_manager::WIFI_REG_DOMAIN_FCC; |
| break; |
| case NL80211_DFS_ETSI: |
| new_domain = power_manager::WIFI_REG_DOMAIN_EU; |
| break; |
| case NL80211_DFS_JP: |
| new_domain = power_manager::WIFI_REG_DOMAIN_REST_OF_WORLD; |
| break; |
| case NL80211_DFS_UNSET: |
| new_domain = power_manager::WIFI_REG_DOMAIN_NONE; |
| break; |
| default: |
| LOG(WARNING) << "Unrecognized WiFi reg domain: " |
| << std::to_string(domain); |
| return false; |
| } |
| wifi_reg_domain_is_set = true; |
| |
| if (new_domain != wifi_reg_domain_) { |
| wifi_reg_domain_ = new_domain; |
| return power_manager_proxy_->ChangeRegDomain(wifi_reg_domain_); |
| } |
| return false; |
| } |
| |
| void PowerManager::OnSuspendImminent(int suspend_id) { |
| LOG(INFO) << __func__ << "(" << suspend_id << ")"; |
| current_suspend_id_ = suspend_id; |
| |
| // Ignore any previously deferred SuspendDone notification as we're going to |
| // suspend again and expect a new SuspendDone notification later. |
| suspend_done_deferred_ = false; |
| |
| // If we're already suspending, don't call the |suspend_imminent_callback_| |
| // again. |
| if (!suspending_) { |
| // Change the power state to suspending as soon as this signal is received |
| // so that the manager can suppress auto-connect, for example. |
| // Also, we must set this before running the callback below, because the |
| // callback may synchronously report suspend readiness. |
| suspending_ = true; |
| suspend_duration_us_ = 0; |
| suspend_imminent_callback_.Run(); |
| } |
| } |
| |
| void PowerManager::OnSuspendDone(int suspend_id, int64_t suspend_duration_us) { |
| // NB: |suspend_id| could be -1. See OnPowerManagerVanished. |
| LOG(INFO) << __func__ << "(" << suspend_id << ")"; |
| if (!suspending_) { |
| LOG(WARNING) << "Received unexpected SuspendDone (" << suspend_id |
| << "). Ignoring."; |
| return; |
| } |
| |
| suspend_duration_us_ = suspend_duration_us; |
| |
| if (!suspend_ready_) { |
| LOG(INFO) << "Received SuspendDone (" << suspend_id |
| << ") before SuspendReadiness is reported. " |
| << "Defer SuspendDone notification."; |
| suspend_done_deferred_ = true; |
| return; |
| } |
| |
| NotifySuspendDone(); |
| } |
| |
| void PowerManager::NotifySuspendDone() { |
| suspending_ = false; |
| suspend_ready_ = false; |
| suspend_done_deferred_ = false; |
| in_dark_resume_ = false; |
| suspend_done_callback_.Run(); |
| } |
| |
| void PowerManager::OnDarkSuspendImminent(int suspend_id) { |
| LOG(INFO) << __func__ << "(" << suspend_id << ")"; |
| if (!dark_suspend_delay_registered_) { |
| LOG(WARNING) << "Ignoring DarkSuspendImminent signal from powerd. shill " |
| << "does not have a dark suspend delay registered. This " |
| << "means that shill is not guaranteed any time before a " |
| << "resuspend."; |
| return; |
| } |
| in_dark_resume_ = true; |
| current_dark_suspend_id_ = suspend_id; |
| dark_suspend_imminent_callback_.Run(); |
| } |
| |
| void PowerManager::OnPowerManagerAppeared() { |
| LOG(INFO) << __func__; |
| |
| // This function could get called twice in a row due to races in |
| // ObjectProxy. |
| if (suspend_delay_registered_) { |
| return; |
| } |
| |
| if (power_manager_proxy_->RegisterSuspendDelay( |
| suspend_delay_, kSuspendDelayDescription, &suspend_delay_id_)) |
| suspend_delay_registered_ = true; |
| |
| if (power_manager_proxy_->RegisterDarkSuspendDelay( |
| suspend_delay_, kDarkSuspendDelayDescription, |
| &dark_suspend_delay_id_)) |
| dark_suspend_delay_registered_ = true; |
| if (wifi_reg_domain_is_set) { |
| power_manager_proxy_->ChangeRegDomain(wifi_reg_domain_); |
| } |
| } |
| |
| void PowerManager::OnPowerManagerVanished() { |
| LOG(INFO) << __func__; |
| // If powerd vanished during a suspend, we need to wake ourselves up. |
| if (suspending_) { |
| suspend_ready_ = true; |
| OnSuspendDone(kInvalidSuspendId, 0); |
| } |
| suspend_delay_registered_ = false; |
| dark_suspend_delay_registered_ = false; |
| } |
| |
| } // namespace shill |