blob: d354a544aa061badeec824d10b685b1e3b5b544e [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.
// This file is meant for debugging use to manually trigger a proper
// suspend, excercising the full path through the power manager.
// The actual work to suspend the system is done by powerd_suspend.
// This tool will block and only exit after it has received a D-Bus
// resume signal from powerd.
#include <unistd.h>
#include <memory>
#include <base/at_exit.h>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/message_loop/message_pump_type.h>
#include <base/run_loop.h>
#include <base/strings/string_number_conversions.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/task/single_thread_task_executor.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include <brillo/file_utils.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>
#include "power_manager/common/util.h"
#include "power_manager/proto_bindings/suspend.pb.h"
namespace {
// The sysfs entry that controls RTC wake alarms. To set an alarm, write
// into this file the time of the alarm in seconds since the epoch.
const char kRtcWakeAlarmPath[] = "/sys/class/rtc/rtc0/wakealarm";
// Location of disable_dark_resume preference file. Writing 1 to this file
// disables dark resume.
const char kDisableDarkResumePath[] =
"/var/lib/power_manager/disable_dark_resume";
std::string WakeupTypeToString(power_manager::SuspendDone::WakeupType type) {
switch (type) {
case power_manager::SuspendDone_WakeupType_INPUT:
return "input";
case power_manager::SuspendDone_WakeupType_OTHER:
return "other";
case power_manager::SuspendDone_WakeupType_NOT_APPLICABLE:
return "not applicable";
case power_manager::SuspendDone_WakeupType_UNKNOWN:
return "not applicable";
}
}
// Exits when powerd announces that the suspend attempt has completed.
void OnSuspendDone(base::RunLoop* run_loop,
bool print_wakeup_type,
dbus::Signal* signal) {
if (print_wakeup_type) {
power_manager::SuspendDone info;
dbus::MessageReader reader(signal);
CHECK(reader.PopArrayOfBytesAsProto(&info));
LOG(INFO) << "Wakeup type: " << WakeupTypeToString(info.wakeup_type());
}
run_loop->Quit();
}
// Handles the result of an attempt to connect to a D-Bus signal.
void OnDBusSignalConnected(const std::string& interface,
const std::string& signal,
bool success) {
CHECK(success) << "Unable to connect to " << interface << "." << signal;
}
// Invoked if a SuspendDone signal announcing resume isn't received before
// FLAGS_timeout.
void OnTimeout() {
LOG(FATAL) << "Did not receive a " << power_manager::kSuspendDoneSignal
<< " signal within the timeout.";
}
class DarkResumeConfigurator {
public:
explicit DarkResumeConfigurator(bool disable) {
// Store previous setting of dark resume.
dark_resume_pref_exist_before_ =
base::PathExists(base::FilePath(kDisableDarkResumePath));
if (dark_resume_pref_exist_before_) {
if (!base::ReadFileToString(base::FilePath(kDisableDarkResumePath),
&prev_dark_resume_pref_state_)) {
PLOG(ERROR) << "Failed to read previous dark resume state from "
<< kDisableDarkResumePath;
exit(1);
}
}
if (!SetDarkResumeState(disable ? "1" : "0"))
exit(1);
}
~DarkResumeConfigurator() {
// Restore dark resume state.
if (!dark_resume_pref_exist_before_) {
if (!base::DeleteFile(base::FilePath(kDisableDarkResumePath)))
PLOG(ERROR) << "Failed to restore dark resume state.";
} else if (!SetDarkResumeState(prev_dark_resume_pref_state_)) {
PLOG(ERROR) << "Failed to restore dark resume state.";
}
}
private:
bool SetDarkResumeState(std::string state) {
auto dark_resume_pref_path = base::FilePath(kDisableDarkResumePath);
if (!base::CreateDirectory(dark_resume_pref_path.DirName())) {
PLOG(ERROR) << "Failed to create parent directories for "
<< kDisableDarkResumePath;
return false;
}
if (!brillo::WriteStringToFile(dark_resume_pref_path, state)) {
PLOG(ERROR) << "Failed to write " << state << " to "
<< kDisableDarkResumePath;
return false;
}
return true;
}
// Whether |kDisableDarkResumePath| exist before the script start.
bool dark_resume_pref_exist_before_ = false;
// Used to Store the original preference in |kDisableDarkResumePath| if the
// file exist(dark_resume_pref_exist_before_) before the start of the script.
std::string prev_dark_resume_pref_state_;
};
} // namespace
int main(int argc, char* argv[]) {
DEFINE_int32(delay, 1,
"Delay before suspending in seconds. Useful if "
"running interactively to ensure that typing this command "
"isn't recognized as user activity that cancels the suspend "
"request.");
DEFINE_int32(timeout, 0, "How long to wait for a resume signal in seconds.");
DEFINE_uint64(wakeup_count, 0,
"Wakeup count to pass to powerd or 0 if "
"unset.");
DEFINE_int32(wakeup_timeout, 0,
"Sets an RTC alarm immediately that fires after the given "
"interval. This ensures that device resumes while testing "
"remotely.");
DEFINE_int32(suspend_for_sec, 0,
"Ask powerd to suspend the device for this many seconds. powerd "
"then sets an alarm just before going to suspend.");
DEFINE_bool(print_wakeup_type, false, "Print wakeup type of last resume.");
DEFINE_bool(disable_dark_resume, true,
"whether or not to disable dark resume before suspend. Resets to "
"previous preference on resume. Defaults to True.");
brillo::FlagHelper::Init(argc, argv,
"Instruct powerd to suspend the system.");
base::AtExitManager at_exit_manager;
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
base::FileDescriptorWatcher watcher(task_executor.task_runner());
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
CHECK(bus->Connect());
dbus::ObjectProxy* powerd_proxy = bus->GetObjectProxy(
power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath));
if (FLAGS_delay)
sleep(FLAGS_delay);
// Set an RTC alarm to wake up the system.
if (FLAGS_wakeup_timeout > 0) {
std::string alarm_string = "+" + base::NumberToString(FLAGS_wakeup_timeout);
// Write 0 first to clear any existing RTC alarm.
CHECK(power_manager::util::WriteFileFully(base::FilePath(kRtcWakeAlarmPath),
"0", 1));
CHECK(power_manager::util::WriteFileFully(base::FilePath(kRtcWakeAlarmPath),
alarm_string.c_str(),
alarm_string.length()));
}
// Enables/Disables dark resume for this script run based on
// |FLAGS_disable_dark_resume|. Restores the original preference on exit.
DarkResumeConfigurator dark_resume_configurator(FLAGS_disable_dark_resume);
base::RunLoop run_loop;
powerd_proxy->ConnectToSignal(
power_manager::kPowerManagerInterface, power_manager::kSuspendDoneSignal,
base::Bind(&OnSuspendDone, &run_loop, FLAGS_print_wakeup_type),
base::Bind(&OnDBusSignalConnected));
// Send a suspend request.
dbus::MethodCall method_call(power_manager::kPowerManagerInterface,
power_manager::kRequestSuspendMethod);
if (FLAGS_wakeup_count) {
dbus::MessageWriter writer(&method_call);
writer.AppendUint64(FLAGS_wakeup_count);
}
// Pass suspend_for_sec to the daemon.
if (FLAGS_suspend_for_sec) {
dbus::MessageWriter writer(&method_call);
writer.AppendInt32(FLAGS_suspend_for_sec);
}
std::unique_ptr<dbus::Response> response(powerd_proxy->CallMethodAndBlock(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
CHECK(response) << power_manager::kRequestSuspendMethod << " failed";
// Schedule a task to fire after the timeout.
if (FLAGS_timeout) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&OnTimeout),
base::TimeDelta::FromSeconds(FLAGS_timeout));
}
run_loop.Run();
return 0;
}