blob: d579e8acb9a4b31c1f83b029b93af9faa46b9f84 [file] [log] [blame] [edit]
// Copyright 2014 The Chromium Authors
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Adapted from deleted Chromium's components/timers/alarm_timer_chromeos.cc
#include "brillo/timers/alarm_timer.h"
#include <stdint.h>
#include <sys/timerfd.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/task/common/task_annotator.h>
namespace brillo {
namespace timers {
// static
bool SimpleAlarmTimer::IsSupportedClock(clockid_t clock_id) {
// Only wake up alarms are supported.
return clock_id == CLOCK_BOOTTIME_ALARM || clock_id == CLOCK_REALTIME_ALARM;
}
// static
std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::Create(clockid_t clock_id) {
if (!IsSupportedClock(clock_id)) {
return nullptr;
}
return CreateInternal(clock_id);
}
// static
std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::CreateForTesting() {
// For unittest, use CLOCK_REALTIME in order to run the tests without
// CAP_WAKE_ALARM.
return CreateInternal(CLOCK_REALTIME);
}
// static
std::unique_ptr<SimpleAlarmTimer> SimpleAlarmTimer::CreateInternal(
clockid_t clock_id) {
base::ScopedFD alarm_fd(timerfd_create(clock_id, TFD_CLOEXEC));
if (!alarm_fd.is_valid()) {
PLOG(ERROR) << "Failed to create timer fd";
return nullptr;
}
// Note: std::make_unique<> cannot be used because the constructor is
// private.
return base::WrapUnique(new SimpleAlarmTimer(std::move(alarm_fd)));
}
SimpleAlarmTimer::SimpleAlarmTimer(base::ScopedFD alarm_fd)
: alarm_fd_(std::move(alarm_fd)) {}
SimpleAlarmTimer::~SimpleAlarmTimer() {
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
Stop();
}
void SimpleAlarmTimer::Start(const base::Location& location,
base::TimeDelta delay,
base::RepeatingClosure user_task) {
user_task_ = std::move(user_task);
posted_from_ = location;
delay_ = delay;
Reset();
}
void SimpleAlarmTimer::Stop() {
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
if (!IsRunning())
return;
// Cancel any previous callbacks.
weak_factory_.InvalidateWeakPtrs();
is_running_ = false;
alarm_fd_watcher_.reset();
pending_task_.reset();
// This disarms the CLOCK_REALTIME alarm that will wake the system by
// writing an itimerspec with values of 0.
itimerspec alarm_time = {};
if (timerfd_settime(alarm_fd_.get(), 0, &alarm_time, NULL) < 0)
PLOG(ERROR) << "Error while disarming RTC alarm. Hardware timer may fire";
}
void SimpleAlarmTimer::Reset() {
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!user_task_.is_null());
// Cancel any previous callbacks and stop watching |alarm_fd_|.
weak_factory_.InvalidateWeakPtrs();
alarm_fd_watcher_.reset();
// Ensure that the delay is not negative.
const base::TimeDelta delay = std::max(base::TimeDelta(), delay_);
// Set up the pending task.
base::TimeTicks desired_run_time =
delay.is_zero() ? base::TimeTicks() : base::TimeTicks::Now() + delay;
pending_task_ = std::make_unique<base::PendingTask>(
posted_from_, user_task_, base::TimeTicks::Now(), desired_run_time);
// Set |alarm_fd_| to be signaled when the delay expires. If the delay is
// zero, |alarm_fd_| will never be signaled. This overrides the previous
// delay, if any.
itimerspec alarm_time = {};
alarm_time.it_value.tv_sec = delay.InSeconds();
alarm_time.it_value.tv_nsec =
(delay.InMicroseconds() % base::Time::kMicrosecondsPerSecond) *
base::Time::kNanosecondsPerMicrosecond;
if (timerfd_settime(alarm_fd_.get(), 0, &alarm_time, NULL) < 0)
PLOG(ERROR) << "Error while setting alarm time. Timer will not fire";
// The timer is running.
is_running_ = true;
// If the delay is zero, post the task now.
if (delay.is_zero()) {
origin_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SimpleAlarmTimer::OnTimerFired,
weak_factory_.GetWeakPtr()));
} else {
// Otherwise, if the delay is not zero, generate a tracing event to indicate
// that the task was posted and watch |alarm_fd_|.
base::TaskAnnotator().WillQueueTask("SimpleAlarmTimer::Reset",
pending_task_.get());
alarm_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
alarm_fd_.get(),
base::BindRepeating(&SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking,
weak_factory_.GetWeakPtr()));
}
}
void SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking() {
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
DCHECK(IsRunning());
// Read from |alarm_fd_| to ack the event.
char val[sizeof(uint64_t)];
if (!base::ReadFromFD(alarm_fd_.get(), val, sizeof(uint64_t)))
PLOG(DFATAL) << "Unable to read from timer file descriptor.";
OnTimerFired();
}
void SimpleAlarmTimer::OnTimerFired() {
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
DCHECK(IsRunning());
DCHECK(pending_task_.get());
// Take ownership of the PendingTask to prevent it from being deleted if the
// SimpleAlarmTimer is deleted.
const auto pending_user_task = std::move(pending_task_);
base::WeakPtr<SimpleAlarmTimer> weak_ptr = weak_factory_.GetWeakPtr();
// Run the task.
base::TaskAnnotator().RunTask("SimpleAlarmTimer::Reset", *pending_user_task);
// If the timer wasn't deleted, stopped or reset by the callback, stop it.
if (weak_ptr)
Stop();
}
bool SimpleAlarmTimer::IsRunning() const {
return is_running_;
}
} // namespace timers
} // namespace brillo