| // 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 |