| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2008-2014 Travis Geiselbrecht |
| // |
| // 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 |
| |
| /** |
| * @file |
| * @brief Kernel timer subsystem |
| * @defgroup timer Timers |
| * |
| * The timer subsystem allows functions to be scheduled for later |
| * execution. Each timer object is used to cause one function to |
| * be executed at a later time. |
| * |
| * Timer callback functions are called in interrupt context. |
| * |
| * @{ |
| */ |
| #include "kernel/timer.h" |
| |
| #include <assert.h> |
| #include <debug.h> |
| #include <inttypes.h> |
| #include <lib/affine/ratio.h> |
| #include <lib/arch/intrin.h> |
| #include <lib/counters.h> |
| #include <lib/zircon-internal/macros.h> |
| #include <platform.h> |
| #include <stdlib.h> |
| #include <trace.h> |
| #include <zircon/compiler.h> |
| #include <zircon/errors.h> |
| #include <zircon/listnode.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include <kernel/align.h> |
| #include <kernel/lockdep.h> |
| #include <kernel/mp.h> |
| #include <kernel/percpu.h> |
| #include <kernel/scheduler.h> |
| #include <kernel/spinlock.h> |
| #include <kernel/stats.h> |
| #include <kernel/thread.h> |
| #include <platform/timer.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| // Total number of timers set. Always increasing. |
| KCOUNTER(timer_created_counter, "timer.created") |
| |
| // Number of timers merged into an existing timer because of slack. |
| KCOUNTER(timer_coalesced_counter, "timer.coalesced") |
| |
| // Number of timers that have fired (i.e. callback was invoked). |
| KCOUNTER(timer_fired_counter, "timer.fired") |
| |
| // Number of timers that were successfully canceled. Attempts to cancel a timer that is currently |
| // firing are not counted. |
| KCOUNTER(timer_canceled_counter, "timer.canceled") |
| |
| namespace { |
| |
| MonitoredSpinLock timer_lock __CPU_ALIGN_EXCLUSIVE; |
| DECLARE_SINGLETON_LOCK_WRAPPER(TimerLock, timer_lock); |
| |
| affine::Ratio gTicksToTime; |
| uint64_t gTicksPerSecond; |
| |
| } // anonymous namespace |
| |
| void platform_set_ticks_to_time_ratio(const affine::Ratio& ticks_to_time) { |
| // ASSERT that we are not calling this function twice. Once set, this ratio |
| // may not change. |
| DEBUG_ASSERT(gTicksPerSecond == 0); |
| DEBUG_ASSERT(ticks_to_time.numerator() != 0); |
| DEBUG_ASSERT(ticks_to_time.denominator() != 0); |
| gTicksToTime = ticks_to_time; |
| gTicksPerSecond = gTicksToTime.Inverse().Scale(ZX_SEC(1)); |
| } |
| |
| const affine::Ratio& platform_get_ticks_to_time_ratio(void) { return gTicksToTime; } |
| |
| zx_time_t current_time(void) { return gTicksToTime.Scale(current_ticks()); } |
| |
| zx_ticks_t ticks_per_second(void) { return gTicksPerSecond; } |
| |
| void TimerQueue::UpdatePlatformTimer(zx_time_t new_deadline) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| if (new_deadline < next_timer_deadline_) { |
| LTRACEF("rescheduling timer for %" PRIi64 " nsecs\n", new_deadline); |
| platform_set_oneshot_timer(new_deadline); |
| next_timer_deadline_ = new_deadline; |
| } |
| } |
| |
| void TimerQueue::Insert(Timer* timer, zx_time_t earliest_deadline, zx_time_t latest_deadline) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| cpu_num_t cpu = arch_curr_cpu_num(); |
| LTRACEF("timer %p, cpu %u, scheduled %" PRIi64 "\n", timer, cpu, timer->scheduled_time_); |
| |
| // For inserting the timer we consider several cases. In general we |
| // want to coalesce with the current timer unless we can prove that |
| // either that: |
| // 1- there is no slack overlap with current timer OR |
| // 2- the next timer is a better fit. |
| // |
| // In diagrams that follow |
| // - Let |e| be the current (existing) timer deadline |
| // - Let |t| be the deadline of the timer we are inserting |
| // - Let |n| be the next timer deadline if any |
| // - Let |x| be the end of the list (not a timer) |
| // - Let |(| and |)| the earliest_deadline and latest_deadline. |
| |
| for (Timer& entry : timer_list_) { |
| if (entry.scheduled_time_ > latest_deadline) { |
| // New timer latest is earlier than the current timer. |
| // Just add upfront as is, without slack. |
| // |
| // ---------t---)--e-------------------------------> time |
| timer->slack_ = 0ll; |
| timer_list_.insert(entry, timer); |
| return; |
| } |
| |
| if (entry.scheduled_time_ >= timer->scheduled_time_) { |
| // New timer slack overlaps and is to the left (or equal). We |
| // coalesce with current by scheduling late. |
| // |
| // --------(----t---e-)----------------------------> time |
| timer->slack_ = zx_time_sub_time(entry.scheduled_time_, timer->scheduled_time_); |
| timer->scheduled_time_ = entry.scheduled_time_; |
| kcounter_add(timer_coalesced_counter, 1); |
| timer_list_.insert_after(timer_list_.make_iterator(entry), timer); |
| return; |
| } |
| |
| if (entry.scheduled_time_ < earliest_deadline) { |
| // new timer earliest is later than the current timer. This case |
| // is handled in a future iteration. |
| // |
| // ----------------e--(---t-----------------------> time |
| continue; |
| } |
| |
| // New timer is to the right of current timer and there is overlap |
| // with the current timer, but could the next timer (if any) be |
| // a better fit? |
| // |
| // -------------(--e---t-----?-------------------> time |
| |
| auto iter = timer_list_.make_iterator(entry); |
| ++iter; |
| if (iter != timer_list_.end()) { |
| const Timer& next = *iter; |
| if (next.scheduled_time_ <= timer->scheduled_time_) { |
| // The new timer is to the right of the next timer. There is no |
| // chance the current timer is a better fit. |
| // |
| // -------------(--e---n---t----------------------> time |
| continue; |
| } |
| |
| if (next.scheduled_time_ < latest_deadline) { |
| // There is slack overlap with the next timer, and also with the |
| // current timer. Which coalescing is a better match? |
| // |
| // --------------(-e---t---n-)-----------------------> time |
| zx_duration_t delta_entry = zx_time_sub_time(timer->scheduled_time_, entry.scheduled_time_); |
| zx_duration_t delta_next = zx_time_sub_time(next.scheduled_time_, timer->scheduled_time_); |
| if (delta_next < delta_entry) { |
| // New timer is closer to the next timer, handle it in the |
| // next iteration. |
| continue; |
| } |
| } |
| } |
| |
| // Handles the remaining cases, note that there is overlap with |
| // the current timer. |
| // |
| // 1- this is the last timer (next == NULL) or |
| // 2- there is no overlap with the next timer, or |
| // 3- there is overlap with both current and next but |
| // current is closer. |
| // |
| // So we coalesce by scheduling early. |
| timer->slack_ = zx_time_sub_time(entry.scheduled_time_, timer->scheduled_time_); |
| timer->scheduled_time_ = entry.scheduled_time_; |
| kcounter_add(timer_coalesced_counter, 1); |
| timer_list_.insert_after(timer_list_.make_iterator(entry), timer); |
| return; |
| } |
| |
| // Walked off the end of the list and there was no overlap. |
| timer->slack_ = 0; |
| timer_list_.push_back(timer); |
| } |
| |
| Timer::~Timer() { |
| // Ensure that we are not on any TimerQueue's list. |
| ZX_DEBUG_ASSERT(!InContainer()); |
| // Ensure that we are not active on some cpu. |
| ZX_DEBUG_ASSERT(active_cpu_.load(ktl::memory_order_relaxed) == INVALID_CPU); |
| } |
| |
| void Timer::Set(const Deadline& deadline, Callback callback, void* arg) { |
| LTRACEF("timer %p deadline.when %" PRIi64 " deadline.slack.amount %" PRIi64 |
| " deadline.slack.mode %u callback %p arg %p\n", |
| this, deadline.when(), deadline.slack().amount(), deadline.slack().mode(), callback, arg); |
| |
| DEBUG_ASSERT(magic_ == kMagic); |
| DEBUG_ASSERT(deadline.slack().mode() <= TIMER_SLACK_LATE); |
| DEBUG_ASSERT(deadline.slack().amount() >= 0); |
| |
| if (InContainer()) { |
| panic("timer %p already in list\n", this); |
| } |
| |
| const zx_time_t latest_deadline = deadline.latest(); |
| const zx_time_t earliest_deadline = deadline.earliest(); |
| |
| Guard<MonitoredSpinLock, IrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| |
| cpu_num_t cpu = arch_curr_cpu_num(); |
| cpu_num_t active_cpu = active_cpu_.load(ktl::memory_order_relaxed); |
| |
| bool currently_active = (active_cpu == cpu); |
| if (unlikely(currently_active)) { |
| // The timer is active on our own cpu, we must be inside the callback. |
| if (cancel_.load(ktl::memory_order_relaxed)) { |
| return; |
| } |
| } else if (unlikely(active_cpu != INVALID_CPU)) { |
| panic("timer %p currently active on a different cpu %u\n", this, active_cpu); |
| } |
| |
| // Set up the structure. |
| scheduled_time_ = deadline.when(); |
| callback_ = callback; |
| arg_ = arg; |
| cancel_.store(false, ktl::memory_order_relaxed); |
| // We don't need to modify active_cpu_ because it is managed by timer_tick(). |
| |
| LTRACEF("scheduled time %" PRIi64 "\n", scheduled_time_); |
| |
| TimerQueue& timer_queue = percpu::Get(cpu).timer_queue; |
| |
| timer_queue.Insert(this, earliest_deadline, latest_deadline); |
| kcounter_add(timer_created_counter, 1); |
| |
| if (!timer_queue.timer_list_.is_empty() && &timer_queue.timer_list_.front() == this) { |
| // We just modified the head of the timer queue. |
| timer_queue.UpdatePlatformTimer(deadline.when()); |
| } |
| } |
| |
| void TimerQueue::PreemptReset(zx_time_t deadline) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| LTRACEF("preempt timer cpu %u deadline %" PRIi64 "\n", arch_curr_cpu_num(), deadline); |
| preempt_timer_deadline_ = deadline; |
| UpdatePlatformTimer(deadline); |
| } |
| |
| bool Timer::Cancel() { |
| DEBUG_ASSERT(magic_ == kMagic); |
| |
| Guard<MonitoredSpinLock, IrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| |
| cpu_num_t cpu = arch_curr_cpu_num(); |
| |
| // mark the timer as canceled |
| cancel_.store(true, ktl::memory_order_relaxed); |
| // TODO(fxbug.dev/64092): Consider whether this DeviceMemoryBarrier is required |
| arch::DeviceMemoryBarrier(); |
| |
| // see if we're trying to cancel the timer we're currently in the middle of handling |
| if (unlikely(active_cpu_.load(ktl::memory_order_relaxed) == cpu)) { |
| // zero it out |
| callback_ = nullptr; |
| arg_ = nullptr; |
| |
| // we're done, so return back to the callback |
| return false; |
| } |
| |
| bool callback_not_running; |
| |
| // If this Timer is in a queue, remove it and adjust hardware timers if needed. |
| if (InContainer()) { |
| callback_not_running = true; |
| |
| TimerQueue& timer_queue = percpu::Get(cpu).timer_queue; |
| |
| // Save a copy of the old head of the queue so later we can see if we modified the head. |
| const Timer* oldhead = nullptr; |
| if (!timer_queue.timer_list_.is_empty()) { |
| oldhead = &timer_queue.timer_list_.front(); |
| } |
| |
| // Remove this Timer from this whatever TimerQueue it's on. |
| RemoveFromContainer(); |
| kcounter_add(timer_canceled_counter, 1); |
| |
| // TODO(cpu): If, after removing |timer| there is one other single Timer with |
| // the same scheduled_time_ and slack_ non-zero, then it is possible to return |
| // that timer to the ideal scheduled_time_. |
| |
| // See if we've just modified the head of this TimerQueue. |
| // |
| // If Timer was on another cpu's queue, we'll just let it fire and sort itself out. |
| if (unlikely(oldhead == this)) { |
| // The Timer we're canceling was at head of this queue, so see if we should update platform |
| // timer. |
| if (!timer_queue.timer_list_.is_empty()) { |
| timer_queue.UpdatePlatformTimer(timer_queue.timer_list_.front().scheduled_time_); |
| } else if (timer_queue.next_timer_deadline_ == ZX_TIME_INFINITE) { |
| LTRACEF("clearing old hw timer, preempt timer not set, nothing in the queue\n"); |
| platform_stop_timer(); |
| } |
| } |
| } else { |
| callback_not_running = false; |
| } |
| |
| guard.Release(); |
| |
| // wait for the timer to become un-busy in case a callback is currently active on another cpu |
| while (active_cpu_.load(ktl::memory_order_relaxed) != INVALID_CPU) { |
| arch::Yield(); |
| } |
| |
| // zero it out |
| callback_ = nullptr; |
| arg_ = nullptr; |
| |
| return callback_not_running; |
| } |
| |
| // called at interrupt time to process any pending timers |
| void timer_tick(zx_time_t now) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| |
| CPU_STATS_INC(timer_ints); |
| |
| cpu_num_t cpu = arch_curr_cpu_num(); |
| |
| LTRACEF("cpu %u now %" PRIi64 ", sp %p\n", cpu, now, __GET_FRAME()); |
| |
| percpu::Get(cpu).timer_queue.Tick(now, cpu); |
| } |
| |
| void TimerQueue::Tick(zx_time_t now, cpu_num_t cpu) { |
| // The platform timer has fired, so no deadline is set. |
| next_timer_deadline_ = ZX_TIME_INFINITE; |
| |
| // Service the preemption timer before acquiring the timer lock. |
| if (now >= preempt_timer_deadline_) { |
| preempt_timer_deadline_ = ZX_TIME_INFINITE; |
| Scheduler::TimerTick(SchedTime{now}); |
| } |
| |
| Guard<MonitoredSpinLock, NoIrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| |
| for (;;) { |
| // See if there's an event to process. |
| if (timer_list_.is_empty()) { |
| break; |
| } |
| |
| Timer& timer = timer_list_.front(); |
| |
| LTRACEF("next item on timer queue %p at %" PRIi64 " now %" PRIi64 " (%p, arg %p)\n", &timer, |
| timer.scheduled_time_, now, timer.callback_, timer.arg_); |
| if (likely(now < timer.scheduled_time_)) { |
| break; |
| } |
| |
| // Process it. |
| LTRACEF("timer %p\n", &timer); |
| DEBUG_ASSERT_MSG(timer.magic_ == Timer::kMagic, |
| "ASSERT: timer failed magic check: timer %p, magic 0x%x\n", &timer, |
| (uint)timer.magic_); |
| timer_list_.erase(timer); |
| |
| // Mark the timer busy. |
| timer.active_cpu_.store(cpu, ktl::memory_order_relaxed); |
| // Unlocking the spinlock in CallUnlocked acts as a release fence. |
| |
| // Now that the timer is off of the list, release the spinlock to handle |
| // the callback, then re-acquire in case it is requeued. |
| guard.CallUnlocked([&timer, now]() { |
| LTRACEF("dequeued timer %p, scheduled %" PRIi64 "\n", &timer, timer.scheduled_time_); |
| |
| CPU_STATS_INC(timers); |
| kcounter_add(timer_fired_counter, 1); |
| |
| LTRACEF("timer %p firing callback %p, arg %p\n", &timer, timer.callback_, timer.arg_); |
| timer.callback_(&timer, now, timer.arg_); |
| |
| DEBUG_ASSERT(arch_ints_disabled()); |
| }); |
| |
| // Mark it not busy. |
| timer.active_cpu_.store(INVALID_CPU, ktl::memory_order_relaxed); |
| // TODO(fxbug.dev/64092): Consider whether this DeviceMemoryBarrier is required |
| arch::DeviceMemoryBarrier(); |
| } |
| |
| // Get the deadline of the event at the head of the queue (if any). |
| zx_time_t deadline = ZX_TIME_INFINITE; |
| if (!timer_list_.is_empty()) { |
| deadline = timer_list_.front().scheduled_time_; |
| // This has to be the case or it would have fired already. |
| DEBUG_ASSERT(deadline > now); |
| } |
| |
| // We're done manipulating the timer queue. |
| guard.Release(); |
| |
| // Set the platform timer to the *soonest* of queue event and preemption timer. |
| if (preempt_timer_deadline_ < deadline) { |
| deadline = preempt_timer_deadline_; |
| } |
| UpdatePlatformTimer(deadline); |
| } |
| |
| zx_status_t Timer::TrylockOrCancel(MonitoredSpinLock* lock) { |
| // spin trylocking on the passed in spinlock either waiting for it |
| // to grab or the passed in timer to be canceled. |
| while (unlikely(lock->TryAcquire(SOURCE_TAG))) { |
| // we failed to grab it, check for cancel |
| if (cancel_.load(ktl::memory_order_relaxed)) { |
| // we were canceled, so bail immediately |
| return ZX_ERR_TIMED_OUT; |
| } |
| // tell the arch to wait |
| arch::Yield(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void TimerQueue::TransitionOffCpu(TimerQueue& source) { |
| Guard<MonitoredSpinLock, IrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| |
| Timer* old_head = nullptr; |
| if (!timer_list_.is_empty()) { |
| old_head = &timer_list_.front(); |
| } |
| |
| // Move all timers from |source| to this TimerQueue. |
| Timer* timer; |
| while ((timer = source.timer_list_.pop_front()) != nullptr) { |
| // We lost the original asymmetric slack information so when we combine them |
| // with the other timer queue they are not coalesced again. |
| // TODO(cpu): figure how important this case is. |
| Insert(timer, timer->scheduled_time_, timer->scheduled_time_); |
| // Note, we do not increment the "created" counter here because we are simply moving these |
| // timers from one queue to another and we already counted them when they were first |
| // created. |
| } |
| |
| Timer* new_head = nullptr; |
| if (!timer_list_.is_empty()) { |
| new_head = &timer_list_.front(); |
| } |
| |
| if (new_head != nullptr && new_head != old_head) { |
| // We just modified the head of the timer queue. |
| UpdatePlatformTimer(new_head->scheduled_time_); |
| } |
| |
| // The old TimerQueue has no tasks left, so reset the deadlines. |
| source.preempt_timer_deadline_ = ZX_TIME_INFINITE; |
| source.next_timer_deadline_ = ZX_TIME_INFINITE; |
| } |
| |
| void TimerQueue::ThawPercpu(void) { |
| DEBUG_ASSERT(arch_ints_disabled()); |
| Guard<MonitoredSpinLock, NoIrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| |
| // Reset next_timer_deadline_ so that UpdatePlatformTimer will reconfigure the timer. |
| next_timer_deadline_ = ZX_TIME_INFINITE; |
| zx_time_t deadline = preempt_timer_deadline_; |
| |
| if (!timer_list_.is_empty()) { |
| Timer& t = timer_list_.front(); |
| if (t.scheduled_time_ < deadline) { |
| deadline = t.scheduled_time_; |
| } |
| } |
| |
| guard.Release(); |
| |
| UpdatePlatformTimer(deadline); |
| } |
| |
| void TimerQueue::PrintTimerQueues(char* buf, size_t len) { |
| size_t ptr = 0; |
| zx_time_t now = current_time(); |
| |
| Guard<MonitoredSpinLock, IrqSave> guard{TimerLock::Get(), SOURCE_TAG}; |
| for (cpu_num_t i = 0; i < percpu::processor_count(); i++) { |
| if (mp_is_cpu_online(i)) { |
| ptr += snprintf(buf + ptr, len - ptr, "cpu %u:\n", i); |
| if (ptr >= len) { |
| return; |
| } |
| zx_time_t last = now; |
| for (Timer& t : percpu::Get(i).timer_queue.timer_list_) { |
| zx_duration_t delta_now = zx_time_sub_time(t.scheduled_time_, now); |
| zx_duration_t delta_last = zx_time_sub_time(t.scheduled_time_, last); |
| ptr += snprintf(buf + ptr, len - ptr, |
| "\ttime %" PRIi64 " delta_now %" PRIi64 " delta_last %" PRIi64 |
| " func %p arg %p\n", |
| t.scheduled_time_, delta_now, delta_last, t.callback_, t.arg_); |
| if (ptr >= len) { |
| return; |
| } |
| last = t.scheduled_time_; |
| } |
| } |
| } |
| } |
| |
| #include <lib/console.h> |
| |
| static int cmd_timers(int argc, const cmd_args* argv, uint32_t flags) { |
| const size_t timer_buffer_size = PAGE_SIZE; |
| |
| // allocate a buffer to dump the timer queue into to avoid reentrancy issues with the |
| // timer spinlock |
| char* buf = static_cast<char*>(malloc(timer_buffer_size)); |
| if (!buf) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| TimerQueue::PrintTimerQueues(buf, timer_buffer_size); |
| |
| printf("%s", buf); |
| |
| free(buf); |
| |
| return 0; |
| } |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND_MASKED("timers", "dump the current kernel timer queues", &cmd_timers, |
| CMD_AVAIL_NORMAL) |
| STATIC_COMMAND_END(kernel) |