/** * @file SingleTimerExecutor.cpp * * This class combines the powers of a 1MHz hardware timer from microsecond_timer.c * and pending events queue event_queue.cpp * * As of version 2.6.x, ChibiOS tick-based kernel is not capable of scheduling events * with the level of precision we need, and realistically it should not. * * http://sourceforge.net/p/rusefi/tickets/24/ * * @date: Apr 18, 2014 * @author Andrey Belomutskiy, (c) 2012-2015 */ #include "SingleTimerExecutor.h" #include "efitime.h" #include "efilib2.h" #if EFI_PROD_CODE #include "microsecond_timer.h" #endif #if (EFI_SIGNAL_EXECUTOR_ONE_TIMER && EFI_PROD_CODE )|| defined(__DOXYGEN__) #include "rfiutil.h" static Executor instance; extern schfunc_t globalTimerCallback; //static int timerIsLate = 0; //static efitime_t callbackTime = 0; /** * these fields are global in order to facilitate debugging */ static efitime_t nextEventTimeNt = 0; uint32_t beforeHwSetTimer; uint32_t hwSetTimerTime; uint32_t lastExecutionCount; static void executorCallback(void *arg) { (void)arg; efiAssertVoid(getRemainingStack(chThdSelf()) > 256, "lowstck#2y"); // callbackTime = getTimeNowNt(); // if((callbackTime > nextEventTimeNt) && (callbackTime - nextEventTimeNt > US2NT(5000))) { // timerIsLate++; // } instance.onTimerCallback(); } Executor::Executor() { reentrantFlag = false; /** * todo: a good comment */ queue.setLateDelay(US2NT(100)); } void Executor::scheduleByTime(scheduling_s *scheduling, efitimeus_t timeUs, schfunc_t callback, void *param) { // if (delayUs < 0) { // firmwareError("Negative delayUs %s: %d", prefix, delayUs); // return; // } // if (delayUs == 0) { // callback(param); // return; // } if (!reentrantFlag) { // this would guard the queue and disable interrupts lockAnyContext(); } bool_t needToResetTimer = queue.insertTask(scheduling, US2NT(timeUs), callback, param); if (!reentrantFlag) { doExecute(); if (needToResetTimer) { scheduleTimerCallback(); } unlockAnyContext(); } } void Executor::schedule(scheduling_s *scheduling, efitime_t nowUs, int delayUs, schfunc_t callback, void *param) { scheduleByTime(scheduling, nowUs + delayUs, callback, param); } void Executor::onTimerCallback() { lockAnyContext(); doExecute(); scheduleTimerCallback(); unlockAnyContext(); } /* * this private method is executed under lock */ void Executor::doExecute() { /** * Let's execute actions we should execute at this point. * reentrantFlag takes care of the use case where the actions we are executing are scheduling * further invocations */ reentrantFlag = true; int shouldExecute = 1; /** * in real life it could be that while we executing listeners time passes and it's already time to execute * next listeners. * TODO: add a counter & figure out a limit of iterations? */ int totalExecuted = 0; while (shouldExecute > 0) { /** * It's worth noting that that the actions might be adding new actions into the queue */ efitick_t nowNt = getTimeNowNt(); shouldExecute = queue.executeAll(nowNt); totalExecuted += shouldExecute; } lastExecutionCount = totalExecuted; if (!isLocked()) { firmwareError("Someone has stolen my lock"); return; } reentrantFlag = false; } /** * This method is always invoked under a lock */ void Executor::scheduleTimerCallback() { /** * Let's grab fresh time value */ efitick_t nowNt = getTimeNowNt(); nextEventTimeNt = queue.getNextEventTime(nowNt); efiAssertVoid(nextEventTimeNt > nowNt, "setTimer constraint"); if (nextEventTimeNt == EMPTY_QUEUE) return; // no pending events in the queue int32_t hwAlarmTime = NT2US((int32_t)nextEventTimeNt - (int32_t)nowNt); beforeHwSetTimer = GET_TIMESTAMP(); setHardwareUsTimer(hwAlarmTime == 0 ? 1 : hwAlarmTime); hwSetTimerTime = GET_TIMESTAMP() - beforeHwSetTimer; } /** * @brief Schedule an event * * Invokes event callback after the specified amount of time. * * @param [in, out] scheduling Data structure to keep this event in the collection. * @param [in] delayUs the number of microseconds before the output signal immediate output if delay is zero. * @param [in] dwell the number of ticks of output duration. */ void scheduleTask(const char *prefix, scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) { // scheduling->name = prefix; instance.scheduleByTime(scheduling, getTimeNowUs() + delayUs, callback, param); } void scheduleByTime(const char *prefix, scheduling_s *scheduling, efitimeus_t time, schfunc_t callback, void *param) { // scheduling->name = prefix; instance.scheduleByTime(scheduling, time, callback, param); } void initSignalExecutorImpl(void) { globalTimerCallback = executorCallback; #if EFI_PROD_CODE initMicrosecondTimer(); #endif /* EFI_PROD_CODE */ } #endif