blob: 0dcd7a641d1eb07c72f9eea8266b031f39967948 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "object/timer_dispatcher.h"
#include <assert.h>
#include <lib/counters.h>
#include <platform.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <kernel/thread.h>
KCOUNTER(dispatcher_timer_create_count, "dispatcher.timer.create")
KCOUNTER(dispatcher_timer_destroy_count, "dispatcher.timer.destroy")
static void timer_irq_callback(Timer* timer, zx_time_t now, void* arg) {
// We are in IRQ context and cannot touch the timer state_tracker, so we
// schedule a DPC to do so. TODO(cpu): figure out ways to reduce the lag.
auto dpc = reinterpret_cast<Dpc*>(arg);
dpc->Queue(true);
}
static void dpc_callback(Dpc* d) { d->arg<TimerDispatcher>()->OnTimerFired(); }
zx_status_t TimerDispatcher::Create(uint32_t options, KernelHandle<TimerDispatcher>* handle,
zx_rights_t* rights) {
if (options > ZX_TIMER_SLACK_LATE)
return ZX_ERR_INVALID_ARGS;
switch (options) {
case ZX_TIMER_SLACK_CENTER:
case ZX_TIMER_SLACK_EARLY:
case ZX_TIMER_SLACK_LATE:
break;
default:
return ZX_ERR_INVALID_ARGS;
};
fbl::AllocChecker ac;
KernelHandle new_handle(fbl::AdoptRef(new (&ac) TimerDispatcher(options)));
if (!ac.check())
return ZX_ERR_NO_MEMORY;
*rights = default_rights();
*handle = ktl::move(new_handle);
return ZX_OK;
}
TimerDispatcher::TimerDispatcher(uint32_t options)
: options_(options),
timer_dpc_(&dpc_callback, this),
deadline_(0u),
slack_amount_(0u),
cancel_pending_(false) {
kcounter_add(dispatcher_timer_create_count, 1);
}
TimerDispatcher::~TimerDispatcher() {
DEBUG_ASSERT(deadline_ == 0u);
DEBUG_ASSERT(slack_amount_ == 0u);
kcounter_add(dispatcher_timer_destroy_count, 1);
}
void TimerDispatcher::on_zero_handles() {
// The timers can be kept alive indefinitely by the callbacks, so
// we need to cancel when there are no more user-mode clients.
Guard<Mutex> guard{get_lock()};
// We must ensure that the timer callback (running in interrupt context,
// possibly on a different CPU) has completed before possibly destroy
// the timer. So cancel the timer if we haven't already.
if (!CancelTimerLocked())
timer_.Cancel();
}
zx_status_t TimerDispatcher::Set(zx_time_t deadline, zx_duration_t slack_amount) {
canary_.Assert();
Guard<Mutex> guard{get_lock()};
bool did_cancel = CancelTimerLocked();
// If the timer is already due, then we can set the signal immediately without
// starting the timer.
if ((deadline == 0u) || (deadline <= current_time())) {
UpdateStateLocked(0u, ZX_TIMER_SIGNALED);
return ZX_OK;
}
deadline_ = deadline;
slack_amount_ = slack_amount;
// If we're imminently awaiting a timer callback due to a prior cancellation request,
// let the callback take care of restarting the timer too so everything happens in the
// right sequence.
if (cancel_pending_)
return ZX_OK;
// We need to ref-up because the timer and the dpc don't understand
// refcounted objects. The Release() is called either in OnTimerFired()
// or in the complicated cancellation path above.
AddRef();
// We must ensure that the timer callback (running in interrupt context,
// possibly on a different CPU) has completed before set try to set the
// timer again. So cancel the timer if we haven't already.
SetTimerLocked(!did_cancel);
return ZX_OK;
}
zx_status_t TimerDispatcher::Cancel() {
canary_.Assert();
Guard<Mutex> guard{get_lock()};
CancelTimerLocked();
return ZX_OK;
}
void TimerDispatcher::SetTimerLocked(bool cancel_first) {
if (cancel_first)
timer_.Cancel();
slack_mode slack_mode = TIMER_SLACK_CENTER;
switch (options_) {
case ZX_TIMER_SLACK_CENTER:
slack_mode = TIMER_SLACK_CENTER;
break;
case ZX_TIMER_SLACK_EARLY:
slack_mode = TIMER_SLACK_EARLY;
break;
case ZX_TIMER_SLACK_LATE:
slack_mode = TIMER_SLACK_LATE;
break;
default:
panic("Unknown options: %x", options_);
};
const TimerSlack slack{slack_amount_, slack_mode};
const Deadline slackDeadline(deadline_, slack);
timer_.Set(slackDeadline, &timer_irq_callback, &timer_dpc_);
}
bool TimerDispatcher::CancelTimerLocked() {
// Always clear the signal bit.
UpdateStateLocked(ZX_TIMER_SIGNALED, 0u);
// If the timer isn't pending then we're done.
if (!deadline_)
return false; // didn't call timer_cancel
deadline_ = 0u;
slack_amount_ = 0;
// If we're already waiting for the timer to be canceled, then we don't need
// to cancel it again.
if (cancel_pending_)
return false; // didn't call timer_cancel
// The timer is active and needs to be canceled.
// Refcount is at least 2 because there is a pending timer that we need to cancel.
bool timer_canceled = timer_.Cancel();
if (timer_canceled) {
// Managed to cancel before OnTimerFired() ran. So we need to decrement the
// ref count here.
ASSERT(!Release());
} else {
// The DPC thread is about to run the callback! Yet we are holding the lock.
// We'll let the timer callback take care of cleanup.
cancel_pending_ = true;
}
return true; // did call timer_cancel
}
void TimerDispatcher::OnTimerFired() {
canary_.Assert();
{
Guard<Mutex> guard{get_lock()};
if (cancel_pending_) {
// We previously attempted to cancel the timer but the dpc had already
// been queued. Suppress handling of this callback but take care to
// restart the timer if its deadline was set in the meantime.
cancel_pending_ = false;
if (deadline_ != 0u) {
// We must ensure that the timer callback (running in interrupt context,
// possibly on a different CPU) has completed before set try to set the
// timer again.
SetTimerLocked(true /* cancel first*/);
return;
}
} else {
// The timer is firing.
UpdateStateLocked(0u, ZX_TIMER_SIGNALED);
deadline_ = 0u;
slack_amount_ = 0u;
}
}
// Drop the RefCounted reference that was added in Set(). If this was the
// last reference, the RefCounted contract requires that we delete
// ourselves.
if (Release())
delete this;
}
void TimerDispatcher::GetInfo(zx_info_timer_t* info) const {
canary_.Assert();
Guard<Mutex> guard{get_lock()};
info->options = options_;
info->deadline = deadline_;
info->slack = slack_amount_;
}