blob: 75b96035d81934a53f4c58c32979bae472157db1 [file] [log] [blame]
// 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 "power_manager/powerd/system/arc_timer_manager.h"
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <set>
#include <utility>
#include <base/bind.h>
#include <base/check.h>
#include <base/files/file.h>
#include <base/memory/ptr_util.h>
#include <base/posix/unix_domain_socket.h>
#include <brillo/daemons/daemon.h>
#include <components/timers/alarm_timer_chromeos.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/message.h>
#include "power_manager/common/clock.h"
namespace power_manager {
namespace system {
namespace {
// Creates a new "invalid args" reply to |method_call|.
std::unique_ptr<dbus::Response> CreateInvalidArgsError(
dbus::MethodCall* method_call, const std::string& message) {
return std::unique_ptr<dbus::Response>(dbus::ErrorResponse::FromMethodCall(
method_call, DBUS_ERROR_INVALID_ARGS, message));
}
// Only wake up alarms are supported.
bool IsSupportedClock(clockid_t clock_id) {
return clock_id == CLOCK_BOOTTIME_ALARM || clock_id == CLOCK_REALTIME_ALARM;
}
// Expiration callback for timer of type |timer_id|. |expiration_fd| is the fd
// to write to to indicate timer expiration to the instance.
void OnExpiration(ArcTimerManager::TimerId timer_id, int expiration_fd) {
DVLOG(1) << "Expiration callback for timer id=" << timer_id;
// Write to the |expiration_fd| to indicate to the instance that the timer has
// expired. The instance expects 8 bytes on the read end similar to what
// happens on a timerfd expiration. The timerfd API expects this to be the
// number of expirations, however, more than one expiration isn't tracked
// currently. This can block in the unlikely scenario of multiple writes
// happening but the instance not reading the data. When the send queue is
// full (64Kb), a write attempt here will block.
const uint64_t timer_data = 1;
if (!base::UnixDomainSocket::SendMsg(
expiration_fd, &timer_data, sizeof(timer_data), std::vector<int>())) {
PLOG(ERROR) << "Failed to indicate timer expiration to the instance";
}
}
// Writes |timer_ids| as an array of int32s to |writer|.
void WriteTimerIdsToDBusResponse(
const std::vector<ArcTimerManager::TimerId>& timer_ids,
dbus::MessageWriter* writer) {
dbus::MessageWriter array_writer(nullptr);
writer->OpenArray("i", &array_writer);
for (auto id : timer_ids)
array_writer.AppendInt32(id);
writer->CloseContainer(&array_writer);
}
} // namespace
ArcTimerManager::ArcTimerManager()
: clock_(std::make_unique<Clock>()), weak_ptr_factory_(this) {}
ArcTimerManager::~ArcTimerManager() = default;
struct ArcTimerManager::ArcTimerInfo {
ArcTimerInfo() = delete;
ArcTimerInfo(ArcTimerInfo&&) = delete;
ArcTimerInfo(clockid_t clock_id,
base::ScopedFD expiration_fd,
std::unique_ptr<timers::SimpleAlarmTimer> timer)
: clock_id(clock_id),
expiration_fd(std::move(expiration_fd)),
timer(std::move(timer)) {}
ArcTimerInfo(const ArcTimerInfo&) = delete;
ArcTimerInfo& operator=(const ArcTimerInfo&) = delete;
// Clock id associated with this timer.
const clockid_t clock_id;
// The file descriptor which will be written to when |timer| expires.
const base::ScopedFD expiration_fd;
// TODO(b/69759087): Make |SimpleAlarmTimer| take a clock id in its
// constructor to create timers of different clock types.
//
// The timer that will be scheduled.
const std::unique_ptr<timers::SimpleAlarmTimer> timer;
};
void ArcTimerManager::Init(DBusWrapperInterface* dbus_wrapper) {
DCHECK(dbus_wrapper);
dbus_wrapper->ExportMethod(kCreateArcTimersMethod,
base::Bind(&ArcTimerManager::HandleCreateArcTimers,
weak_ptr_factory_.GetWeakPtr()));
dbus_wrapper->ExportMethod(kStartArcTimerMethod,
base::Bind(&ArcTimerManager::HandleStartArcTimer,
weak_ptr_factory_.GetWeakPtr()));
dbus_wrapper->ExportMethod(kDeleteArcTimersMethod,
base::Bind(&ArcTimerManager::HandleDeleteArcTimers,
weak_ptr_factory_.GetWeakPtr()));
}
std::vector<ArcTimerManager::TimerId> ArcTimerManager::GetTimerIdsForTesting(
const std::string& tag) {
auto it = client_timer_ids_.find(tag);
if (it == client_timer_ids_.end())
return std::vector<TimerId>();
return it->second;
}
void ArcTimerManager::HandleCreateArcTimers(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
DVLOG(1) << "CreateArcTimers";
dbus::MessageReader reader(method_call);
std::string tag;
if (!reader.PopString(&tag)) {
LOG(WARNING) << "Failed to pop tag string arg from "
<< kCreateArcTimersMethod << " D-Bus method call";
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call, "Expected tag string"));
return;
}
DVLOG(1) << "Creating timers for tag=" << tag;
dbus::MessageReader array_reader(nullptr);
if (!reader.PopArray(&array_reader)) {
LOG(WARNING) << "Failed to pop {clock id, expiration fd} array from "
<< kCreateArcTimersMethod << " D-Bus method call";
std::move(response_sender)
.Run(CreateInvalidArgsError(
method_call, "Expected array of clock id and descriptors"));
return;
}
DeleteArcTimers(tag);
// Iterate over the array of |clock_id, expiration_fd| and create an
// |ArcTimerInfo| entry for each clock.
std::vector<std::unique_ptr<ArcTimerInfo>> arc_timers =
CreateArcTimers(&array_reader, is_testing_);
if (arc_timers.size() == 0) {
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call, "Failed to create timers"));
return;
}
if (ContainsDuplicateClocks(arc_timers)) {
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call,
"Duplicate clocks not supported"));
return;
}
// For each timer:
// - Map an entry from a timer id to the timer.
// - Push the timer id to the list of ids associated with the client's tag.
// Newly generated ids guarantee that there are no key collisions in the first
// operation. Earlier checks guarantee that there are no key collisions in the
// second operation.
for (auto& timer : arc_timers) {
DVLOG(1) << "Adding tag=" << tag << " timer id=" << next_timer_id_;
CHECK(timers_.emplace(next_timer_id_, std::move(timer)).second);
client_timer_ids_[tag].push_back(next_timer_id_);
next_timer_id_++;
}
std::unique_ptr<dbus::Response> response(
dbus::Response::FromMethodCall(method_call));
dbus::MessageWriter writer(response.get());
WriteTimerIdsToDBusResponse(client_timer_ids_[tag], &writer);
std::move(response_sender).Run(std::move(response));
}
// static:
std::vector<std::unique_ptr<ArcTimerManager::ArcTimerInfo>>
ArcTimerManager::CreateArcTimers(dbus::MessageReader* array_reader,
bool create_for_testing) {
std::vector<std::unique_ptr<ArcTimerInfo>> result;
while (array_reader->HasMoreData()) {
std::unique_ptr<ArcTimerInfo> arc_timer =
CreateArcTimer(array_reader, create_for_testing);
if (!arc_timer) {
result.clear();
return result;
}
result.push_back(std::move(arc_timer));
}
return result;
}
// static:
std::unique_ptr<ArcTimerManager::ArcTimerInfo> ArcTimerManager::CreateArcTimer(
dbus::MessageReader* array_reader, bool create_for_testing) {
dbus::MessageReader struct_reader(nullptr);
if (!array_reader->PopStruct(&struct_reader)) {
LOG(WARNING) << "Failed to pop struct";
return nullptr;
}
int32_t clock_id;
if (!struct_reader.PopInt32(&clock_id)) {
LOG(WARNING) << "Failed to pop clock id";
return nullptr;
}
// The instance opens clocks of type CLOCK_BOOTTIME_ALARM and
// CLOCK_REALTIME_ALARM. However, it uses only CLOCK_BOOTTIME_ALARM to set
// wake up alarms. At this point, it's okay to pretend the host supports
// CLOCK_REALTIME_ALARM instead of returning an error.
if (!IsSupportedClock(static_cast<clockid_t>(clock_id))) {
LOG(WARNING) << "Unsupported clock=" << clock_id;
return nullptr;
}
base::ScopedFD expiration_fd;
if (!struct_reader.PopFileDescriptor(&expiration_fd)) {
LOG(WARNING) << "Failed to pop file descriptor for clock=" << clock_id;
return nullptr;
}
if (!expiration_fd.is_valid()) {
LOG(WARNING) << "Bad file descriptor for clock=" << clock_id;
return nullptr;
}
if (create_for_testing) {
return std::make_unique<ArcTimerInfo>(
clock_id, std::move(expiration_fd),
timers::SimpleAlarmTimer::CreateForTesting());
}
return std::make_unique<ArcTimerInfo>(clock_id, std::move(expiration_fd),
timers::SimpleAlarmTimer::Create());
}
// static.
bool ArcTimerManager::ContainsDuplicateClocks(
const std::vector<std::unique_ptr<ArcTimerInfo>>& arc_timers) {
std::set<clockid_t> seen_clock_ids;
for (const auto& timer : arc_timers) {
if (!seen_clock_ids.emplace(timer->clock_id).second)
return true;
}
return false;
}
void ArcTimerManager::HandleStartArcTimer(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
dbus::MessageReader reader(method_call);
ArcTimerManager::TimerId timer_id;
if (!reader.PopInt32(&timer_id)) {
LOG(WARNING) << "Failed to pop timer id from " << kStartArcTimerMethod
<< " D-Bus method call";
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call, "Expected timer id"));
return;
}
int64_t absolute_expiration_time_us;
if (!reader.PopInt64(&absolute_expiration_time_us)) {
LOG(WARNING) << "Failed to pop absolute expiration time from "
<< kStartArcTimerMethod << " D-Bus method call";
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call,
"Expected absolute expiration time"));
return;
}
base::TimeTicks absolute_expiration_time =
base::TimeTicks() +
base::TimeDelta::FromMicroseconds(absolute_expiration_time_us);
// If a timer for the given clock is not created prior to this call then
// return error. Else retrieve the timer associated with it.
ArcTimerInfo* arc_timer = FindArcTimerInfo(timer_id);
if (!arc_timer) {
std::move(response_sender)
.Run(CreateInvalidArgsError(
method_call, "Invalid timer id " + std::to_string(timer_id)));
return;
}
// Start the timer to expire at |absolute_expiration_time|. This call
// automatically overrides the previous timer set.
//
// If the firing time has expired then set the timer to expire
// immediately. The |current_time_ticks| should always include ticks spent
// in sleep.
base::TimeTicks current_time_ticks = clock_->GetCurrentBootTime();
base::TimeDelta delay;
if (absolute_expiration_time > current_time_ticks)
delay = absolute_expiration_time - current_time_ticks;
base::Time current_time = base::Time::Now();
DVLOG(1) << "TimerId=" << timer_id << " CurrentTime=" << current_time
<< " NextAlarmAt=" << current_time + delay;
// Pass the raw fd to write to when the timer expires. This is safe to do
// because if the parent object goes away the timers are cleared and all
// pending callbacks are cancelled. If the instance sets new timers after a
// respawn, again, the old timers and pending callbacks are cancelled.
// TODO(abhishekbh): This needs to be base::BindRepeating but it's not
// available in the current version of libchrome.
arc_timer->timer->Start(
FROM_HERE, delay,
base::Bind(&OnExpiration, timer_id, arc_timer->expiration_fd.get()));
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void ArcTimerManager::HandleDeleteArcTimers(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender response_sender) {
DVLOG(1) << "DeleteArcTimers";
dbus::MessageReader reader(method_call);
std::string tag;
if (!reader.PopString(&tag)) {
LOG(WARNING) << "Failed to pop tag string arg from "
<< kDeleteArcTimersMethod << " D-Bus method call";
std::move(response_sender)
.Run(CreateInvalidArgsError(method_call, "Expected tag string"));
return;
}
DeleteArcTimers(tag);
std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
}
void ArcTimerManager::DeleteArcTimers(const std::string& tag) {
// Iterate over timer ids associated with |tag| and delete the timers
// associated with each timer id.
auto it = client_timer_ids_.find(tag);
if (it == client_timer_ids_.end()) {
DVLOG(1) << "Tag=" << tag << " not found";
return;
}
DVLOG(1) << "Deleting timers for tag=" << tag;
const auto& timer_ids = it->second;
for (auto timer_id : timer_ids)
timers_.erase(timer_id);
client_timer_ids_.erase(it);
}
ArcTimerManager::ArcTimerInfo* ArcTimerManager::FindArcTimerInfo(
ArcTimerManager::TimerId timer_id) {
auto it = timers_.find(timer_id);
return (it == timers_.end()) ? nullptr : it->second.get();
}
} // namespace system
} // namespace power_manager