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