1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <object/timer_dispatcher.h>
8
9#include <assert.h>
10#include <err.h>
11#include <platform.h>
12
13#include <kernel/thread.h>
14
15#include <fbl/alloc_checker.h>
16#include <fbl/auto_lock.h>
17#include <zircon/compiler.h>
18#include <zircon/rights.h>
19#include <zircon/types.h>
20
21static void timer_irq_callback(timer* timer, zx_time_t now, void* arg) {
22    // We are in IRQ context and cannot touch the timer state_tracker, so we
23    // schedule a DPC to do so. TODO(cpu): figure out ways to reduce the lag.
24    dpc_queue(reinterpret_cast<dpc_t*>(arg), true);
25}
26
27static void dpc_callback(dpc_t* d) {
28    reinterpret_cast<TimerDispatcher*>(d->arg)->OnTimerFired();
29}
30
31zx_status_t TimerDispatcher::Create(uint32_t options,
32                                    fbl::RefPtr<Dispatcher>* dispatcher,
33                                    zx_rights_t* rights) {
34    if (options > ZX_TIMER_SLACK_LATE)
35        return ZX_ERR_INVALID_ARGS;
36
37    slack_mode slack_mode;
38
39    switch (options) {
40    case ZX_TIMER_SLACK_CENTER: slack_mode = TIMER_SLACK_CENTER;
41        break;
42    case ZX_TIMER_SLACK_EARLY: slack_mode = TIMER_SLACK_EARLY;
43        break;
44    case ZX_TIMER_SLACK_LATE: slack_mode = TIMER_SLACK_LATE;
45        break;
46    default:
47        return ZX_ERR_INVALID_ARGS;
48    };
49
50    fbl::AllocChecker ac;
51    auto disp = new (&ac) TimerDispatcher(slack_mode);
52    if (!ac.check())
53        return ZX_ERR_NO_MEMORY;
54
55    *rights = ZX_DEFAULT_TIMERS_RIGHTS;
56    *dispatcher = fbl::AdoptRef<Dispatcher>(disp);
57    return ZX_OK;
58}
59
60TimerDispatcher::TimerDispatcher(slack_mode slack_mode)
61    : slack_mode_(slack_mode),
62      timer_dpc_({LIST_INITIAL_CLEARED_VALUE, &dpc_callback, this}),
63      deadline_(0u), slack_(0u), cancel_pending_(false),
64      timer_(TIMER_INITIAL_VALUE(timer_)) {
65}
66
67TimerDispatcher::~TimerDispatcher() {
68    DEBUG_ASSERT(deadline_ == 0u);
69}
70
71void TimerDispatcher::on_zero_handles() {
72    // The timers can be kept alive indefinitely by the callbacks, so
73    // we need to cancel when there are no more user-mode clients.
74    Guard<fbl::Mutex> guard{get_lock()};
75
76    // We must ensure that the timer callback (running in interrupt context,
77    // possibly on a different CPU) has completed before possibly destroy
78    // the timer.  So cancel the timer if we haven't already.
79    if (!CancelTimerLocked())
80        timer_cancel(&timer_);
81}
82
83zx_status_t TimerDispatcher::Set(zx_time_t deadline, zx_duration_t slack) {
84    canary_.Assert();
85
86    Guard<fbl::Mutex> guard{get_lock()};
87
88    bool did_cancel = CancelTimerLocked();
89
90    // If the timer is already due, then we can set the signal immediately without
91    // starting the timer.
92    if ((deadline == 0u) || (deadline <= current_time())) {
93        UpdateStateLocked(0u, ZX_TIMER_SIGNALED);
94        return ZX_OK;
95    }
96
97    deadline_ = deadline;
98    slack_ = slack;
99
100    // If we're imminently awaiting a timer callback due to a prior cancelation request,
101    // let the callback take care of restarting the timer too so everthing happens in the
102    // right sequence.
103    if (cancel_pending_)
104        return ZX_OK;
105
106    // We need to ref-up because the timer and the dpc don't understand
107    // refcounted objects. The Release() is called either in OnTimerFired()
108    // or in the complicated cancelation path above.
109    AddRef();
110
111    // We must ensure that the timer callback (running in interrupt context,
112    // possibly on a different CPU) has completed before set try to set the
113    // timer again.  So cancel the timer if we haven't already.
114    SetTimerLocked(!did_cancel);
115
116    return ZX_OK;
117}
118
119zx_status_t TimerDispatcher::Cancel() {
120    canary_.Assert();
121    Guard<fbl::Mutex> guard{get_lock()};
122    CancelTimerLocked();
123    return ZX_OK;
124}
125
126void TimerDispatcher::SetTimerLocked(bool cancel_first) {
127    if (cancel_first)
128        timer_cancel(&timer_);
129    timer_set(&timer_, deadline_, slack_mode_, slack_,
130        &timer_irq_callback, &timer_dpc_);
131}
132
133bool TimerDispatcher::CancelTimerLocked() {
134    // Always clear the signal bit.
135    UpdateStateLocked(ZX_TIMER_SIGNALED, 0u);
136
137    // If the timer isn't pending then we're done.
138    if (!deadline_)
139        return false; // didn't call timer_cancel
140    deadline_ = 0u;
141    slack_ = 0;
142
143    // If we're already waiting for the timer to be canceled, then we don't need
144    // to cancel it again.
145    if (cancel_pending_)
146        return false; // didn't call timer_cancel
147
148    // The timer is active and needs to be canceled.
149    // Refcount is at least 2 because there is a pending timer that we need to cancel.
150    bool timer_canceled = timer_cancel(&timer_);
151    if (timer_canceled) {
152        // Managed to cancel before OnTimerFired() ran. So we need to decrement the
153        // ref count here.
154        ASSERT(!Release());
155    } else {
156        // The DPC thread is about to run the callback! Yet we are holding the lock.
157        // We'll let the timer callback take care of cleanup.
158        cancel_pending_ = true;
159    }
160    return true; // did call timer_cancel
161}
162
163void TimerDispatcher::OnTimerFired() {
164    canary_.Assert();
165
166    {
167        Guard<fbl::Mutex> guard{get_lock()};
168
169        if (cancel_pending_) {
170            // We previously attempted to cancel the timer but the dpc had already
171            // been queued.  Suppress handling of this callback but take care to
172            // restart the timer if its deadline was set in the meantime.
173            cancel_pending_ = false;
174            if (deadline_ != 0u) {
175                // We must ensure that the timer callback (running in interrupt context,
176                // possibly on a different CPU) has completed before set try to set the
177                // timer again.
178                SetTimerLocked(true  /* cancel first*/);
179                return;
180            }
181        } else {
182            // The timer is firing.
183            UpdateStateLocked(0u, ZX_TIMER_SIGNALED);
184            deadline_ = 0u;
185        }
186    }
187
188    // Drop the RefCounted reference that was added in Set(). If this was the
189    // last reference, the RefCounted contract requires that we delete
190    // ourselves.
191    if (Release())
192        delete this;
193}
194