#include "chromeos/dbus/power/native_timer.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/posix/unix_domain_socket.h"
#include "base/rand_util.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/dbus/power/power_manager_client.h"
namespace chromeos {
namespace {
const PowerManagerClient::TimerId kNotCreatedId = -1;
const PowerManagerClient::TimerId kErrorId = -2;
}
struct NativeTimer::StartTimerParams {
StartTimerParams() = default;
StartTimerParams(base::TimeTicks absolute_expiration_time,
base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback)
: absolute_expiration_time(absolute_expiration_time),
timer_expiration_callback(std::move(timer_expiration_callback)),
result_callback(std::move(result_callback)) {}
StartTimerParams(const StartTimerParams&) = delete;
StartTimerParams& operator=(const StartTimerParams&) = delete;
StartTimerParams(StartTimerParams&&) = default;
~StartTimerParams() = default;
base::TimeTicks absolute_expiration_time;
base::OnceClosure timer_expiration_callback;
OnStartNativeTimerCallback result_callback;
};
bool NativeTimer::simulate_timer_creation_failure_for_testing_ = false;
NativeTimer::NativeTimer(const std::string& tag)
: timer_id_(kNotCreatedId), tag_(tag) {
if (simulate_timer_creation_failure_for_testing_) {
timer_id_ = kErrorId;
return;
}
base::ScopedFD powerd_fd;
base::ScopedFD expiration_fd;
base::CreateSocketPair(&powerd_fd, &expiration_fd);
if (!powerd_fd.is_valid() || !expiration_fd.is_valid()) {
LOG(ERROR) << "Invalid file descriptor";
timer_id_ = kErrorId;
return;
}
std::vector<std::pair<clockid_t, base::ScopedFD>> create_timers_request;
create_timers_request.push_back(
std::make_pair(CLOCK_BOOTTIME_ALARM, std::move(powerd_fd)));
PowerManagerClient::Get()->CreateArcTimers(
tag, std::move(create_timers_request),
base::BindOnce(&NativeTimer::OnCreateTimer, weak_factory_.GetWeakPtr(),
std::move(expiration_fd)));
}
NativeTimer::~NativeTimer() {
if (timer_id_ < 0) {
return;
}
PowerManagerClient::Get()->DeleteArcTimers(tag_, base::DoNothing());
}
void NativeTimer::Start(base::TimeTicks absolute_expiration_time,
base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (timer_id_ == kErrorId) {
std::move(result_callback).Run(false);
return;
}
if (timer_id_ == kNotCreatedId) {
if (in_flight_start_timer_params_) {
std::move(in_flight_start_timer_params_->result_callback).Run(true);
}
in_flight_start_timer_params_ = std::make_unique<StartTimerParams>(
absolute_expiration_time, std::move(timer_expiration_callback),
std::move(result_callback));
return;
}
ResetState();
DCHECK_GE(timer_id_, 0);
PowerManagerClient::Get()->StartArcTimer(
timer_id_, absolute_expiration_time,
base::BindOnce(&NativeTimer::OnStartTimer, weak_factory_.GetWeakPtr(),
std::move(timer_expiration_callback),
std::move(result_callback)));
}
void NativeTimer::OnCreateTimer(base::ScopedFD expiration_fd,
std::optional<std::vector<int32_t>> timer_ids) {
DCHECK(expiration_fd.is_valid());
if (!timer_ids.has_value()) {
LOG(ERROR) << "No timers returned";
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
std::vector<int32_t> result = timer_ids.value();
if (result.size() != 1) {
LOG(ERROR) << "powerd created " << result.size() << " timers instead of 1";
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
if (result[0] < 0) {
LOG(ERROR) << "Error timer ID " << result[0];
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
timer_id_ = result[0];
expiration_fd_ = std::move(expiration_fd);
ProcessAndResetInFlightStartParams(true);
}
void NativeTimer::OnStartTimer(base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback,
bool result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result) {
LOG(ERROR) << "Starting timer ID " << timer_id_ << " failed";
std::move(result_callback).Run(false);
return;
}
timer_expiration_callback_ = std::move(timer_expiration_callback);
expiration_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
expiration_fd_.get(), base::BindRepeating(&NativeTimer::OnExpiration,
weak_factory_.GetWeakPtr()));
std::move(result_callback).Run(true);
}
void NativeTimer::OnExpiration() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(expiration_fd_.is_valid());
uint64_t timer_data;
std::vector<base::ScopedFD> fds;
if (!base::UnixDomainSocket::RecvMsg(expiration_fd_.get(), &timer_data,
sizeof(timer_data), &fds)) {
PLOG(ERROR) << "Bad data in expiration fd";
}
ResetState();
std::move(timer_expiration_callback_).Run();
}
void NativeTimer::ResetState() {
weak_factory_.InvalidateWeakPtrs();
expiration_fd_watcher_.reset();
in_flight_start_timer_params_.reset();
}
void NativeTimer::ProcessAndResetInFlightStartParams(bool result) {
if (!in_flight_start_timer_params_) {
return;
}
if (!result) {
DCHECK_LT(timer_id_, 0);
std::move(in_flight_start_timer_params_->result_callback).Run(false);
in_flight_start_timer_params_.reset();
return;
}
PowerManagerClient::Get()->StartArcTimer(
timer_id_, in_flight_start_timer_params_->absolute_expiration_time,
base::BindOnce(
&NativeTimer::OnStartTimer, weak_factory_.GetWeakPtr(),
std::move(in_flight_start_timer_params_->timer_expiration_callback),
std::move(in_flight_start_timer_params_->result_callback)));
in_flight_start_timer_params_.reset();
}
NativeTimer::ScopedFailureSimulatorForTesting::
ScopedFailureSimulatorForTesting() {
NativeTimer::simulate_timer_creation_failure_for_testing_ = true;
}
NativeTimer::ScopedFailureSimulatorForTesting::
~ScopedFailureSimulatorForTesting() {
NativeTimer::simulate_timer_creation_failure_for_testing_ = false;
}
}