/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Timer module for Chrome EC operating system */ #include "atomic.h" #include "common.h" #include "console.h" #include "hooks.h" #include "hwtimer.h" #include "system.h" #include "util.h" #include "task.h" #include "timer.h" #include "watchdog.h" #ifdef CONFIG_ZEPHYR #include /* For k_usleep() */ #else extern __error("k_usleep() should only be called from Zephyr code") int32_t k_usleep(int32_t); #endif /* CONFIG_ZEPHYR */ #ifdef CONFIG_COMMON_RUNTIME #define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) #define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args) #else #define CPRINTS(format, args...) #define CPRINTF(format, args...) #endif #define TIMER_SYSJUMP_TAG 0x4d54 /* "TM" */ /* High 32-bits of the 64-bit timestamp counter. */ STATIC_IF_NOT(CONFIG_HWTIMER_64BIT) uint32_t clksrc_high; /* Bitmap of currently running timers */ static uint32_t timer_running; /* Deadlines of all timers */ static timestamp_t timer_deadline[TASK_ID_COUNT]; static uint32_t next_deadline = 0xffffffff; /* Hardware timer routine IRQ number */ static int timer_irq; static void expire_timer(task_id_t tskid) { /* we are done with this timer */ atomic_clear_bits(&timer_running, 1 << tskid); /* wake up the taks waiting for this timer */ task_set_event(tskid, TASK_EVENT_TIMER); } int timestamp_expired(timestamp_t deadline, const timestamp_t *now) { timestamp_t now_val; if (!now) { now_val = get_time(); now = &now_val; } return ((int64_t)(now->val - deadline.val) >= 0); } void process_timers(int overflow) { uint32_t check_timer, running_t0; timestamp_t next; timestamp_t now; if (!IS_ENABLED(CONFIG_HWTIMER_64BIT) && overflow) clksrc_high++; do { next.val = -1ull; now = get_time(); do { /* read atomically the current state of timer running */ check_timer = running_t0 = timer_running; while (check_timer) { int tskid = __fls(check_timer); /* timer has expired ? */ if (timer_deadline[tskid].val <= now.val) expire_timer(tskid); else if ((timer_deadline[tskid].le.hi == now.le.hi) && (timer_deadline[tskid].le.lo < next.le.lo)) next.val = timer_deadline[tskid].val; check_timer &= ~BIT(tskid); } /* if there is a new timer, let's retry */ } while (timer_running & ~running_t0); if (next.le.hi == 0xffffffff) { /* no deadline to set */ __hw_clock_event_clear(); next_deadline = 0xffffffff; return; } __hw_clock_event_set(next.le.lo); next_deadline = next.le.lo; } while (next.val <= get_time().val); } #ifndef CONFIG_HW_SPECIFIC_UDELAY void udelay(unsigned us) { unsigned t0 = __hw_clock_source_read(); /* * udelay() may be called with interrupts disabled, so we can't rely on * process_timers() updating the top 32 bits. So handle wraparound * ourselves rather than calling get_time() and comparing with a * deadline. * * This may fail for delays close to 2^32 us (~4000 sec), because the * subtraction below can overflow. That's acceptable, because the * watchdog timer would have tripped long before that anyway. */ while (__hw_clock_source_read() - t0 <= us) ; } #endif /* Zephyr provides its own implementation in task shim */ #ifndef CONFIG_ZEPHYR int timer_arm(timestamp_t event, task_id_t tskid) { timestamp_t now = get_time(); ASSERT(tskid < TASK_ID_COUNT); if (timer_running & BIT(tskid)) return EC_ERROR_BUSY; timer_deadline[tskid] = event; atomic_or(&timer_running, BIT(tskid)); /* Modify the next event if needed */ if ((event.le.hi < now.le.hi) || ((event.le.hi == now.le.hi) && (event.le.lo <= next_deadline))) task_trigger_irq(timer_irq); return EC_SUCCESS; } void timer_cancel(task_id_t tskid) { ASSERT(tskid < TASK_ID_COUNT); atomic_clear_bits(&timer_running, BIT(tskid)); /* * Don't need to cancel the hardware timer interrupt, instead do * timer-related housekeeping when the next timer interrupt fires. */ } #endif /* * For us < (2^31 - task scheduling latency)(~ 2147 sec), this function will * sleep for at least us, and no more than 2*us. As us approaches 2^32-1, the * probability of delay longer than 2*us (and possibly infinite delay) * increases. */ void usleep(unsigned us) { uint32_t evt = 0; uint32_t t0; /* If a wait is 0, return immediately. */ if (!us) return; if (IS_ENABLED(CONFIG_ZEPHYR)) { while (us) us = k_usleep(us); return; } t0 = __hw_clock_source_read(); /* If task scheduling has not started, just delay */ if (!task_start_called()) { udelay(us); return; } /* If in interrupt context or interrupts are disabled, use udelay() */ if (!is_interrupt_enabled() || in_interrupt_context()) { CPRINTS("Sleeping not allowed"); udelay(us); return; } do { evt |= task_wait_event(us); } while (!(evt & TASK_EVENT_TIMER) && ((__hw_clock_source_read() - t0) < us)); /* Re-queue other events which happened in the meanwhile */ if (evt) atomic_or(task_get_event_bitmap(task_get_current()), evt & ~TASK_EVENT_TIMER); } timestamp_t get_time(void) { timestamp_t ts; if (IS_ENABLED(CONFIG_HWTIMER_64BIT)) { ts.val = __hw_clock_source_read64(); } else { ts.le.hi = clksrc_high; ts.le.lo = __hw_clock_source_read(); if (ts.le.hi != clksrc_high) { ts.le.hi = clksrc_high; ts.le.lo = __hw_clock_source_read(); } } return ts; } clock_t clock(void) { /* __hw_clock_source_read() returns a microsecond resolution timer.*/ return (clock_t) __hw_clock_source_read() / 1000; } void force_time(timestamp_t ts) { if (IS_ENABLED(CONFIG_HWTIMER_64BIT)) { __hw_clock_source_set64(ts.val); } else { clksrc_high = ts.le.hi; __hw_clock_source_set(ts.le.lo); } /* some timers might be already expired : process them */ task_trigger_irq(timer_irq); } /* * Define versions of __hw_clock_source_read and __hw_clock_source_set * that wrap the 64-bit versions for chips with CONFIG_HWTIMER_64BIT. */ #ifdef CONFIG_HWTIMER_64BIT __overridable uint32_t __hw_clock_source_read(void) { return (uint32_t)__hw_clock_source_read64(); } void __hw_clock_source_set(uint32_t ts) { uint64_t current = __hw_clock_source_read64(); __hw_clock_source_set64(((current >> 32) << 32) | ts); } #endif /* CONFIG_HWTIMER_64BIT */ void timer_print_info(void) { timestamp_t t = get_time(); uint64_t deadline = (uint64_t)t.le.hi << 32 | __hw_clock_event_get(); int tskid; ccprintf("Time: 0x%016llx us, %11.6lld s\n" "Deadline: 0x%016llx -> %11.6lld s from now\n" "Active timers:\n", t.val, t.val, deadline, deadline - t.val); cflush(); for (tskid = 0; tskid < TASK_ID_COUNT; tskid++) { if (timer_running & BIT(tskid)) { ccprintf(" Tsk %2d 0x%016llx -> %11.6lld\n", tskid, timer_deadline[tskid].val, timer_deadline[tskid].val - t.val); cflush(); } } } void timer_init(void) { const timestamp_t *ts; int size, version; BUILD_ASSERT(TASK_ID_COUNT < sizeof(timer_running) * 8); /* Restore time from before sysjump */ ts = (const timestamp_t *)system_get_jump_tag(TIMER_SYSJUMP_TAG, &version, &size); if (ts && version == 1 && size == sizeof(timestamp_t)) { if (IS_ENABLED(CONFIG_HWTIMER_64BIT)) { timer_irq = __hw_clock_source_init64(ts->val); } else { clksrc_high = ts->le.hi; timer_irq = __hw_clock_source_init(ts->le.lo); } } else { if (IS_ENABLED(CONFIG_HWTIMER_64BIT)) timer_irq = __hw_clock_source_init64(0); else timer_irq = __hw_clock_source_init(0); } } /* Preserve time across a sysjump */ static void timer_sysjump(void) { timestamp_t ts = get_time(); system_add_jump_tag(TIMER_SYSJUMP_TAG, 1, sizeof(ts), &ts); } DECLARE_HOOK(HOOK_SYSJUMP, timer_sysjump, HOOK_PRIO_DEFAULT); #ifdef CONFIG_CMD_WAITMS static int command_wait(int argc, char **argv) { char *e; int i; if (argc < 2) return EC_ERROR_PARAM_COUNT; i = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM1; /* * Reload the watchdog so that issuing multiple small waitms commands * quickly one after the other will not cause a reset. * * Reloading before waiting also allows for testing watchdog. */ watchdog_reload(); /* * Waiting for too long (e.g. 3s) will cause the EC to reset due to a * watchdog timeout. This is intended behaviour and is in fact used by * a FAFT test to check that the watchdog timer is working. */ udelay(i * 1000); return EC_SUCCESS; } /* Typically a large delay (e.g. 3s) will cause a reset */ DECLARE_CONSOLE_COMMAND(waitms, command_wait, "msec", "Busy-wait for msec (large delays will reset)"); #endif #ifdef CONFIG_CMD_FORCETIME /* * Force the hwtimer to a given time. This may have undesired consequences, * especially when going "backward" in time, because task deadlines are * left un-adjusted. */ static int command_force_time(int argc, char **argv) { char *e; timestamp_t new; if (argc < 3) return EC_ERROR_PARAM_COUNT; new.le.hi = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM1; new.le.lo = strtoi(argv[2], &e, 0); if (*e) return EC_ERROR_PARAM2; ccprintf("Time: 0x%016llx = %.6lld s\n", new.val, new.val); force_time(new); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(forcetime, command_force_time, "hi lo", "Force current time"); #endif #ifdef CONFIG_CMD_GETTIME static int command_get_time(int argc, char **argv) { timestamp_t ts = get_time(); ccprintf("Time: 0x%016llx = %.6lld s\n", ts.val, ts.val); return EC_SUCCESS; } DECLARE_SAFE_CONSOLE_COMMAND(gettime, command_get_time, NULL, "Print current time"); #endif #ifdef CONFIG_CMD_TIMERINFO int command_timer_info(int argc, char **argv) { timer_print_info(); return EC_SUCCESS; } DECLARE_SAFE_CONSOLE_COMMAND(timerinfo, command_timer_info, NULL, "Print timer info"); #endif