|  | // 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 <err.h> | 
|  | #include <platform.h> | 
|  |  | 
|  | #include <kernel/thread.h> | 
|  |  | 
|  | #include <fbl/alloc_checker.h> | 
|  | #include <fbl/auto_lock.h> | 
|  | #include <zircon/compiler.h> | 
|  | #include <zircon/rights.h> | 
|  | #include <zircon/types.h> | 
|  |  | 
|  | using fbl::AutoLock; | 
|  |  | 
|  | 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. | 
|  | dpc_queue(reinterpret_cast<dpc_t*>(arg), true); | 
|  | } | 
|  |  | 
|  | static void dpc_callback(dpc_t* d) { | 
|  | reinterpret_cast<TimerDispatcher*>(d->arg)->OnTimerFired(); | 
|  | } | 
|  |  | 
|  | zx_status_t TimerDispatcher::Create(uint32_t options, | 
|  | fbl::RefPtr<Dispatcher>* dispatcher, | 
|  | zx_rights_t* rights) { | 
|  | if (options > ZX_TIMER_SLACK_LATE) | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  |  | 
|  | slack_mode slack_mode; | 
|  |  | 
|  | 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: | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  | }; | 
|  |  | 
|  | fbl::AllocChecker ac; | 
|  | auto disp = new (&ac) TimerDispatcher(slack_mode); | 
|  | if (!ac.check()) | 
|  | return ZX_ERR_NO_MEMORY; | 
|  |  | 
|  | *rights = ZX_DEFAULT_TIMERS_RIGHTS; | 
|  | *dispatcher = fbl::AdoptRef<Dispatcher>(disp); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | TimerDispatcher::TimerDispatcher(slack_mode slack_mode) | 
|  | : slack_mode_(slack_mode), | 
|  | timer_dpc_({LIST_INITIAL_CLEARED_VALUE, &dpc_callback, this}), | 
|  | deadline_(0u), slack_(0u), cancel_pending_(false), | 
|  | timer_(TIMER_INITIAL_VALUE(timer_)) { | 
|  | } | 
|  |  | 
|  | TimerDispatcher::~TimerDispatcher() { | 
|  | DEBUG_ASSERT(deadline_ == 0u); | 
|  | } | 
|  |  | 
|  | 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. | 
|  | AutoLock al(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(&timer_); | 
|  | } | 
|  |  | 
|  | zx_status_t TimerDispatcher::Set(zx_time_t deadline, zx_duration_t slack) { | 
|  | canary_.Assert(); | 
|  |  | 
|  | AutoLock al(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_ = slack; | 
|  |  | 
|  | // If we're imminently awaiting a timer callback due to a prior cancelation request, | 
|  | // let the callback take care of restarting the timer too so everthing 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 cancelation 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(); | 
|  | AutoLock al(get_lock()); | 
|  | CancelTimerLocked(); | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | void TimerDispatcher::SetTimerLocked(bool cancel_first) { | 
|  | if (cancel_first) | 
|  | timer_cancel(&timer_); | 
|  | timer_set(&timer_, deadline_, slack_mode_, slack_, | 
|  | &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_ = 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(&timer_); | 
|  | 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(); | 
|  |  | 
|  | { | 
|  | AutoLock al(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; | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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; | 
|  | } |