blob: e13e72d37fc8ba413b7fa30ab5b46107de9c524c [file] [log] [blame]
// Copyright 2021 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 <list>
#include "power_manager/common/power_constants.h"
#include "power_manager/powerd/system/suspend_freezer.h"
#include <base/bind.h>
#include <base/check.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
namespace power_manager {
namespace system {
const base::FilePath kBasePath("/sys/fs/cgroup/freezer");
const base::FilePath kWakeupCountPath("/sys/power/wakeup_count");
namespace {
static constexpr base::TimeDelta kFreezerTimeout =
base::TimeDelta::FromSeconds(10);
static constexpr base::TimeDelta kFreezerSampleTime =
base::TimeDelta::FromMilliseconds(10);
} // namespace
SuspendFreezer::SuspendFreezer()
: sys_utils_(std::make_unique<SystemUtilsInterface>()),
clock_(std::make_unique<Clock>()) {}
void SuspendFreezer::Init(PrefsInterface* prefs) {
DCHECK(prefs);
prefs_ = prefs;
// Just in case powerd crashed and respawned after freezing userspace.
ThawUserspace();
}
bool SuspendFreezer::GetCgroups(std::vector<base::FilePath>* cgroups) {
sys_utils_->GetSubDirs(kBasePath, cgroups);
if (cgroups->empty()) {
LOG(ERROR) << "No children cgroups found in " << kBasePath;
return false;
}
return true;
}
bool SuspendFreezer::SetCgroupState(const base::FilePath& cgroup_path,
const std::string& state) {
base::FilePath state_path = cgroup_path.Append(kStateFile);
if (!sys_utils_->PathExists(state_path)) {
LOG(ERROR) << "File " << state_path
<< " does not exist. Suspend may not succeed as a result";
return false;
}
if (sys_utils_->WriteFile(state_path, state.c_str(), state.size()) !=
state.size()) {
LOG(ERROR) << "Failed to set " << state_path.value() << " to " << state
<< ". Suspend may not succeed as a result";
return false;
}
DVLOG(1) << "Processes in cgroup " << state_path.DirName() << " set to "
<< state;
return true;
}
bool SuspendFreezer::GetCgroupState(const base::FilePath& cgroup_path,
std::string* state) {
base::FilePath state_path = cgroup_path.Append(kStateFile);
std::string str;
if (!sys_utils_->PathExists(state_path)) {
LOG(ERROR) << "File " << state_path
<< " does not exist. Suspend may not succeed as a result";
return false;
}
if (!sys_utils_->ReadFileToString(state_path, &str)) {
LOG(ERROR) << "Failed to read state of file: " << state_path;
return false;
}
base::TrimWhitespaceASCII(str, base::TRIM_ALL, state);
return true;
}
void SuspendFreezer::PopulateCgroupDepsFromPref(
const base::FilePath& path,
std::unordered_map<base::FilePath, struct CgroupNode>* graph) {
std::string deps_name = kSuspendFreezerDepsPrefix + path.BaseName().value();
std::string deps_value;
// A freezer cgroup may have no dependencies. This can happen when a freezer
// cgroup is setup for reasons other than freeze ordering on suspend, and no
// processes depend on any processes in that cgroup to freeze successfully. It
// may also be one of the last cgroups that we want frozen before handing off
// to the kernel.
if (!prefs_->GetString(deps_name, &deps_value)) {
return;
}
auto lines = base::SplitString(deps_value, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
for (const std::string& line : lines) {
base::FilePath dep = kBasePath.Append(line);
// If the dependency doesn't exist, that's fine. Everything that a cgroup
// depends on directly should be listed in its own dependencies, so we don't
// need to add the missing cgroup's dependencies.
if (!sys_utils_->PathExists(dep.Append(kStateFile))) {
continue;
}
(*graph)[path].deps.insert(dep);
(*graph)[dep].rdep_count++;
}
}
bool SuspendFreezer::ProcessFreezingCgroups(
std::unordered_map<base::FilePath, struct CgroupNode>* graph,
std::list<base::FilePath>* freezing,
std::vector<base::FilePath>* frozen) {
auto it = freezing->begin();
while (it != freezing->end()) {
auto cur = it++;
base::FilePath cgroup = *cur;
std::string state;
if (!GetCgroupState(cgroup, &state)) {
return false;
} else if (state != kFreezerStateFrozen) {
continue;
}
DVLOG(1) << "Cgroup " << cgroup << " is now frozen";
freezing->erase(cur);
frozen->push_back(cgroup);
for (const auto& dep : (*graph)[cgroup].deps) {
(*graph)[dep].rdep_count--;
if ((*graph)[dep].rdep_count == 0) {
if (!SetCgroupState(dep, kFreezerStateFrozen)) {
return false;
}
freezing->push_back(dep);
}
CHECK((*graph)[dep].rdep_count >= 0)
<< "Negative reverse dependency count for " << dep.BaseName();
}
}
return true;
}
FreezeResult SuspendFreezer::TopologicalFreeze(
uint64_t wakeup_count,
bool wakeup_count_valid,
std::unordered_map<base::FilePath, struct CgroupNode>* graph) {
// |freezing| is a list since we remove all elements from it one at a time
// (assuming no timeout, etc.).
std::list<base::FilePath> freezing;
std::vector<base::FilePath> frozen;
base::TimeTicks deadline = clock_->GetCurrentTime() + kFreezerTimeout;
for (const auto& cgroup : *graph) {
if (cgroup.second.rdep_count == 0) {
if (!SetCgroupState(cgroup.first, kFreezerStateFrozen)) {
return FreezeResult::FAILURE;
}
freezing.push_back(cgroup.first);
}
}
// This performs a freeze on the cgroups with a topological ordering. This is
// done since cgroups may take a while to freeze, so we may as well freeze
// them in parallel when possible. Cgroups have deps (dependencies) that must
// be frozen after the cgroup is frozen.
if (!ProcessFreezingCgroups(graph, &freezing, &frozen)) {
return FreezeResult::FAILURE;
}
while (!freezing.empty()) {
if (clock_->GetCurrentTime() > deadline) {
std::string str;
for (const auto& cgroup : freezing) {
str += cgroup.BaseName().value() + " ";
}
LOG(ERROR) << "Timeout waiting for cgroups to freeze. Cgroups still "
"freezing: "
<< str;
return FreezeResult::FAILURE;
}
if (wakeup_count_valid) {
std::string wakeup_string;
uint64_t read_wakeup;
if (!sys_utils_->ReadFileToString(kWakeupCountPath, &wakeup_string)) {
LOG(ERROR) << "Error reading wakeup_count from " << kWakeupCountPath;
return FreezeResult::FAILURE;
}
if (!base::StringToUint64(
base::TrimWhitespaceASCII(wakeup_string, base::TRIM_ALL),
&read_wakeup)) {
LOG(ERROR) << "Error converting wakeup_string |" << wakeup_string
<< "| to uint64";
return FreezeResult::FAILURE;
}
if (read_wakeup != wakeup_count) {
LOG(INFO) << "Wakeup before system finished freezing cgroups";
return FreezeResult::CANCELED;
}
}
base::PlatformThread::Sleep(kFreezerSampleTime);
if (!ProcessFreezingCgroups(graph, &freezing, &frozen)) {
return FreezeResult::FAILURE;
}
}
if (frozen.size() != graph->size()) {
std::string frozen_str, cgroups_str;
for (const auto& cgroup : frozen) {
frozen_str += cgroup.BaseName().value() + " ";
}
for (const auto& cgroup : *graph) {
cgroups_str += cgroup.first.BaseName().value() + " ";
}
LOG(ERROR) << "Number of frozen cgroups is not correct. Check for circular "
"dependencies, etc. in suspend_freezer_deps_* files.\n"
"Frozen freezer cgroups: "
<< frozen_str << "\nAll freezer cgroups: " << cgroups_str;
return FreezeResult::FAILURE;
}
return FreezeResult::SUCCESS;
}
FreezeResult SuspendFreezer::FreezeUserspace(uint64_t wakeup_count,
bool wakeup_count_valid) {
std::vector<base::FilePath> cgroup_paths;
std::unordered_map<base::FilePath, struct CgroupNode> cgroup_graph;
if (!GetCgroups(&cgroup_paths)) {
return FreezeResult::FAILURE;
}
for (const auto& path : cgroup_paths) {
base::FilePath state_file = path.Append(kStateFile);
// We only operate on cgroups that are children of the root freezer cgroup.
// This means that we don't need to worry about frozen cgroups that are not
// self-frozen.
if (!sys_utils_->PathExists(state_file)) {
LOG(ERROR) << "File " << kStateFile << " for cgroup freezer directory "
<< path << " does not exist. All directories in " << kBasePath
<< " should be a cgroup with this file";
return FreezeResult::FAILURE;
}
std::string state;
if (!sys_utils_->ReadFileToString(state_file, &state)) {
LOG(ERROR) << "Could not read state of cgroup " << path;
return FreezeResult::FAILURE;
}
base::TrimWhitespaceASCII(state, base::TRIM_ALL, &state);
if (state != kFreezerStateThawed) {
LOG(ERROR) << "State of freezer cgroup " << path.BaseName() << " is "
<< state << " when it should be " << kFreezerStateThawed;
return FreezeResult::FAILURE;
}
cgroup_graph[path] = {
.deps = {},
.rdep_count = 0,
};
}
for (auto& cgroup : cgroup_graph) {
PopulateCgroupDepsFromPref(cgroup.first, &cgroup_graph);
}
return TopologicalFreeze(wakeup_count, wakeup_count_valid, &cgroup_graph);
}
bool SuspendFreezer::ThawUserspace() {
std::vector<base::FilePath> cgroups;
bool ret = true;
if (!GetCgroups(&cgroups)) {
return false;
}
for (const auto& cgroup : cgroups) {
if (!SetCgroupState(cgroup, kFreezerStateThawed)) {
ret = false;
}
}
return ret;
}
bool SuspendFreezer::SystemUtilsInterface::PathExists(
const base::FilePath& path) {
return base::PathExists(path);
}
bool SuspendFreezer::SystemUtilsInterface::ReadFileToString(
const base::FilePath& path, std::string* contents) {
return base::ReadFileToString(path, contents);
}
int SuspendFreezer::SystemUtilsInterface::WriteFile(const base::FilePath& path,
const char* data,
int size) {
return base::WriteFile(path, data, size);
}
void SuspendFreezer::SystemUtilsInterface::GetSubDirs(
const base::FilePath& root_path, std::vector<base::FilePath>* dirs) {
base::FileEnumerator dir(root_path, false, base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = dir.Next(); !path.empty(); path = dir.Next()) {
dirs->push_back(path);
}
}
} // namespace system
} // namespace power_manager