blob: 9e17cd25f61eba67a80cb3602ab5edf462ee2639 [file] [log] [blame]
// Copyright (c) 2012 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 "power_manager/powerd/policy/suspender.h"
#include <algorithm>
#include <base/file_util.h>
#include <base/logging.h>
#include <chromeos/dbus/service_constants.h>
#include "power_manager/common/clock.h"
#include "power_manager/common/dbus_sender.h"
#include "power_manager/common/power_constants.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/policy/dark_resume_policy.h"
#include "power_manager/powerd/policy/suspend_delay_controller.h"
#include "power_manager/powerd/system/input.h"
#include "power_manager/proto_bindings/suspend.pb.h"
namespace power_manager {
namespace policy {
Suspender::TestApi::TestApi(Suspender* suspender) : suspender_(suspender) {}
void Suspender::TestApi::SetCurrentWallTime(base::Time wall_time) {
suspender_->clock_->set_current_wall_time_for_testing(wall_time);
}
bool Suspender::TestApi::TriggerRetryTimeout() {
if (!suspender_->retry_suspend_timer_.IsRunning())
return false;
suspender_->retry_suspend_timer_.Stop();
suspender_->RetrySuspend();
return true;
}
Suspender::Suspender()
: delegate_(NULL),
dbus_sender_(NULL),
dark_resume_policy_(NULL),
clock_(new Clock),
waiting_for_readiness_(false),
suspend_id_(0),
wakeup_count_(0),
wakeup_count_valid_(false),
got_external_wakeup_count_(false),
max_retries_(0),
num_attempts_(0),
shutting_down_(false) {
}
Suspender::~Suspender() {
}
void Suspender::Init(Delegate *delegate,
DBusSenderInterface *dbus_sender,
DarkResumePolicy *dark_resume_policy,
PrefsInterface *prefs) {
delegate_ = delegate;
dbus_sender_ = dbus_sender;
dark_resume_policy_ = dark_resume_policy;
const int initial_id = delegate_->GetInitialId();
suspend_id_ = initial_id - 1;
suspend_delay_controller_.reset(
new SuspendDelayController(dbus_sender_, initial_id));
suspend_delay_controller_->AddObserver(this);
int64 retry_delay_ms = 0;
CHECK(prefs->GetInt64(kRetrySuspendMsPref, &retry_delay_ms));
retry_delay_ = base::TimeDelta::FromMilliseconds(retry_delay_ms);
CHECK(prefs->GetInt64(kRetrySuspendAttemptsPref, &max_retries_));
// Clean up if powerd was previously restarted after emitting SuspendImminent
// but before emitting SuspendDone.
if (delegate_->GetSuspendAnnounced()) {
LOG(INFO) << "Previous run exited mid-suspend; emitting SuspendDone";
AnnounceSuspendCompletion(0, base::TimeDelta());
}
}
void Suspender::RequestSuspend() {
got_external_wakeup_count_ = false;
StartSuspendAttempt();
}
void Suspender::RequestSuspendWithExternalWakeupCount(uint64 wakeup_count) {
wakeup_count_ = wakeup_count;
wakeup_count_valid_ = true;
got_external_wakeup_count_ = true;
StartSuspendAttempt();
}
void Suspender::RegisterSuspendDelay(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
RegisterSuspendDelayRequest request;
dbus::MessageReader reader(method_call);
if (!reader.PopArrayOfBytesAsProto(&request)) {
LOG(ERROR) << "Unable to parse " << kRegisterSuspendDelayMethod
<< " request";
response_sender.Run(scoped_ptr<dbus::Response>(
dbus::ErrorResponse::FromMethodCall(method_call,
DBUS_ERROR_INVALID_ARGS, "Expected serialized protocol buffer")));
return;
}
RegisterSuspendDelayReply reply_proto;
suspend_delay_controller_->RegisterSuspendDelay(
request, method_call->GetSender(), &reply_proto);
scoped_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
writer.AppendProtoAsArrayOfBytes(reply_proto);
response_sender.Run(response.Pass());
}
void Suspender::UnregisterSuspendDelay(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
UnregisterSuspendDelayRequest request;
dbus::MessageReader reader(method_call);
if (!reader.PopArrayOfBytesAsProto(&request)) {
LOG(ERROR) << "Unable to parse " << kUnregisterSuspendDelayMethod
<< " request";
response_sender.Run(scoped_ptr<dbus::Response>(
dbus::ErrorResponse::FromMethodCall(method_call,
DBUS_ERROR_INVALID_ARGS, "Expected serialized protocol buffer")));
return;
}
suspend_delay_controller_->UnregisterSuspendDelay(
request, method_call->GetSender());
response_sender.Run(dbus::Response::FromMethodCall(method_call));
}
void Suspender::HandleSuspendReadiness(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
SuspendReadinessInfo info;
dbus::MessageReader reader(method_call);
if (!reader.PopArrayOfBytesAsProto(&info)) {
LOG(ERROR) << "Unable to parse " << kHandleSuspendReadinessMethod
<< " request";
response_sender.Run(scoped_ptr<dbus::Response>(
dbus::ErrorResponse::FromMethodCall(method_call,
DBUS_ERROR_INVALID_ARGS, "Expected serialized protocol buffer")));
return;
}
suspend_delay_controller_->HandleSuspendReadiness(
info, method_call->GetSender());
response_sender.Run(dbus::Response::FromMethodCall(method_call));
}
void Suspender::HandleLidOpened() {
CancelSuspend();
}
void Suspender::HandleUserActivity() {
// Avoid canceling suspend for errant touchpad, power button, etc. events
// that can be generated by closing the lid.
if (!delegate_->IsLidClosed())
CancelSuspend();
}
void Suspender::HandleShutdown() {
shutting_down_ = true;
CancelSuspend();
}
void Suspender::HandleDBusNameOwnerChanged(const std::string& name,
const std::string& old_owner,
const std::string& new_owner) {
if (new_owner.empty())
suspend_delay_controller_->HandleDBusClientDisconnected(name);
}
void Suspender::OnReadyForSuspend(int suspend_id) {
if (waiting_for_readiness_ && suspend_id == suspend_id_) {
LOG(INFO) << "Ready to suspend";
waiting_for_readiness_ = false;
Suspend();
}
}
void Suspender::StartSuspendAttempt() {
// Suspend shouldn't be requested after the system has started shutting
// down, but if it is, avoid doing anything.
if (shutting_down_) {
LOG(ERROR) << "Not starting suspend attempt; shutdown in progress";
return;
}
if (waiting_for_readiness_)
return;
retry_suspend_timer_.Stop();
if (!got_external_wakeup_count_)
wakeup_count_valid_ = delegate_->GetWakeupCount(&wakeup_count_);
suspend_id_++;
num_attempts_++;
waiting_for_readiness_ = true;
delegate_->PrepareForSuspendAnnouncement();
delegate_->SetSuspendAnnounced(true);
suspend_delay_controller_->PrepareForSuspend(suspend_id_);
}
void Suspender::Suspend() {
// Note: If this log message is changed, the power_AudioDetector test
// must be updated.
LOG(INFO) << "Starting suspend";
bool dark_resume = false;
Delegate::SuspendResult result = Delegate::SUSPEND_SUCCESSFUL;
bool success = false;
const base::Time start_wall_time = clock_->GetCurrentWallTime();
delegate_->PrepareForSuspend();
do {
base::TimeDelta suspend_duration;
DarkResumePolicy::Action action = dark_resume_policy_->GetAction();
switch (action) {
case DarkResumePolicy::SHUT_DOWN:
LOG(INFO) << "Shutting down from dark resume";
delegate_->ShutDownForDarkResume();
return;
case DarkResumePolicy::SUSPEND_FOR_DURATION:
suspend_duration = dark_resume_policy_->GetSuspendDuration();
LOG(INFO) << "Suspending for " << suspend_duration.InSeconds()
<< " seconds";
break;
case DarkResumePolicy::SUSPEND_INDEFINITELY:
suspend_duration = base::TimeDelta();
break;
default:
NOTREACHED() << "Unhandled dark resume action " << action;
}
// We don't want to use the wakeup_count in the case of a dark resume. The
// kernel may not have initialized some of the devices to make the dark
// resume as inconspicuous as possible, so allowing the user to use the
// system in this state would be bad.
result = delegate_->Suspend(
wakeup_count_, wakeup_count_valid_ && !dark_resume, suspend_duration);
success = result == Delegate::SUSPEND_SUCCESSFUL;
dark_resume = dark_resume_policy_->CurrentlyInDarkResume();
// Failure handling for dark resume. We don't want to process events during
// a dark resume, even if we fail to suspend. To solve this, instead of
// scheduling a retry later, delay here and retry without returning from
// this function.
if (!success && dark_resume) {
if (ShutDownIfRetryLimitReached())
return;
LOG(WARNING) << "Retry #" << num_attempts_ << " from dark resume";
sleep(retry_delay_.InSeconds());
num_attempts_++;
}
} while (dark_resume);
// Don't retry if an external wakeup count was supplied and the suspend
// attempt failed due to a wakeup count mismatch -- a test probably triggered
// this suspend attempt after setting a wake alarm, and if we retry later,
// it's likely that the alarm will have already fired and the system will
// never wake up.
const bool done = success ||
(got_external_wakeup_count_ && result == Delegate::SUSPEND_CANCELED);
const int old_suspend_id = suspend_id_;
const int old_num_attempts = num_attempts_;
if (done) {
num_attempts_ = 0;
if (success) {
LOG(INFO) << "Resumed successfully from suspend attempt " << suspend_id_;
} else {
LOG(WARNING) << "Giving up after canceled suspend attempt with external "
<< "wakeup count";
}
} else {
LOG(INFO) << "Suspend attempt " << suspend_id_ << " failed; "
<< "will retry in " << retry_delay_.InMilliseconds() << " ms";
retry_suspend_timer_.Start(FROM_HERE, retry_delay_, this,
&Suspender::RetrySuspend);
}
dark_resume_policy_->HandleResume();
// Protect against the system clock having gone backwards.
base::TimeDelta elapsed_time = std::max(base::TimeDelta(),
clock_->GetCurrentWallTime() - start_wall_time);
AnnounceSuspendCompletion(old_suspend_id, elapsed_time);
// Check for bugs where another suspend attempt is started before the previous
// one is fully cleaned up.
DCHECK_EQ(suspend_id_, old_suspend_id)
<< "Started new suspend attempt " << suspend_id_
<< " while still cleaning up attempt " << old_suspend_id;
// Notify the delegate after all other cleanup is done; it may synchronously
// trigger another suspend attempt.
delegate_->HandleSuspendAttemptCompletion(success, old_num_attempts);
}
bool Suspender::ShutDownIfRetryLimitReached() {
if (num_attempts_ > max_retries_) {
LOG(ERROR) << "Unsuccessfully attempted to suspend " << num_attempts_
<< " times; shutting down";
delegate_->ShutDownForFailedSuspend();
return true;
}
return false;
}
void Suspender::RetrySuspend() {
if (ShutDownIfRetryLimitReached())
return;
LOG(WARNING) << "Retry #" << num_attempts_;
StartSuspendAttempt();
}
void Suspender::CancelSuspend() {
if (waiting_for_readiness_) {
LOG(INFO) << "Canceling suspend before running powerd_suspend";
waiting_for_readiness_ = false;
DCHECK(!retry_suspend_timer_.IsRunning());
AnnounceSuspendCompletion(suspend_id_, base::TimeDelta());
delegate_->HandleCanceledSuspendAnnouncement();
} else if (retry_suspend_timer_.IsRunning()) {
LOG(INFO) << "Canceling suspend between retries";
retry_suspend_timer_.Stop();
}
if (num_attempts_) {
delegate_->HandleCanceledSuspendRequest(num_attempts_);
num_attempts_ = 0;
}
}
void Suspender::AnnounceSuspendCompletion(
int suspend_id,
const base::TimeDelta& suspend_duration) {
SuspendDone proto;
proto.set_suspend_id(suspend_id);
proto.set_suspend_duration(suspend_duration.ToInternalValue());
dbus_sender_->EmitSignalWithProtocolBuffer(kSuspendDoneSignal, proto);
delegate_->SetSuspendAnnounced(false);
}
} // namespace policy
} // namespace power_manager