Tickless idle not resuming tasks immediately

I believe there is a problem with the recommended tickless idle implementation i.e. here: http://freertos.org/low-power-tickless-rtos.html, or with the supporting tick update calls. I have had timing issues with a project on a Nordic nRF52832 (Arm cortex M4), and I have tracked it down to the tickless idle. The project uses Nordic’s tickless idle implementation, but they appear to have followed the implementation recommendations (from the link above). The problem is that upon waking from a tickless idle sleep, it does not immediately execute the tasks that should now be pending (i.e. the ones that scheduled the wakeup time in the first place). I have spent days trying to track down the exact cause, and I think I have found the main problem. Normally (without tickless idle), a tick interrupt happens on every time tick, and does the following: – (Tick ISR) Calls xTaskIncrementTick(), which: – Increments time by exactly 1 tick. – Moves tasks from the delayed list to the ready list (if task time is up) – (Tick ISR) Requests a context switch (if the result of xTaskIncrementTick() indicates something was unblocked) The key here is that tasks get moved from the delayed list to the ready list, and the context switch executes the ready list (if higher priority than the current task) The sequence with the tickless idle implementation (both freeRTOS’s recommendation, and Nordic’s implementation) is: – (Idle task) Suspends scheduler – (Idle task) Checks if delayed time is long enough to sleep – (Idle task) Calls portSUPPRESSTICKSAND_SLEEP(), which (among a few other things): – Sets up a wake interrupt – Sleeps – Wakes Up – Calculates time difference – Corrects time by calling vTaskStepTick() which: – Increments time by the specified time difference – (Idle task) Resumes scheduler So the problem seems to be the fact that incrementing time with vTaskStepTick() fixes the system time, but does not unblock tasks (i.e. the task that scheduled the wakeup). So the tasks wait until the next time tick that processes normally, and then calls xTaskIncrementTick() (in the Tick ISR) which will then unblock the waiting task (now a tick delayed). In digging deeper, I have found examples of tickless idle implementations that use vTaskStepTick() to increment by one less tick than the time elapsed, and then rely on the normal tick interrupt to do the final tick. This seems to be an ok workaround for some implementations, but the Nordic implementation suppresses the normal tick interrupt during tickless idle, and so there is no pending interrupt upon waking. I have found a similar workaround, by calling vTaskStepTick(timeDiff-1), then calling xTaskIncrementTick() (inside of portSUPPRESSTICKSAND_SLEEP()). Technically this causes the final tick to be pended (since the scheduler is still suspended), but it seems to work. Ideally, I feel like vTaskStepTick() (freeRTOS non-portable code) should be updated to also handle unblocking of tasks, in the same way that xTaskIncrementTick() does. The main difference still being that vTaskStepTick() would not do this for every discrete time step, but only once at the end. It could be implemented very simply using the same diff-1 and xTaskIncrementTick() combination in my workaround, or the unblocking code could separated from xTaskIncrementTick() and called separately by each function. It makes most sense to me that whether you are stepping time by 1 tick or multiple ticks, the freeRTOS kernel should then decide if the time change should unblock anything. Obviously some people have figured this problem out (hence SOME implementations account for it), but the recommended implementation (the link above) does not show anything to handle it, and Nordic’s implementation follows it. Is there a reason the kernel doesn’t handle unblocking the tasks in vTaskStepTick()? It seems odd to me that there seems to be so much confusion on how to handle it, and that even a company like Nordic, with a large user base and many iterations of code using FreeRTOS, still doesn’t seem to do it right.

Tickless idle not resuming tasks immediately

This will require a deeper study, from recollection this should be the sequence inside the ‘suppress ticks and sleep’ function: 1) Calculate a new timer reload value that will wake the system at the time the next task should unblock. 2) Disable interrupts (necessary to prevent races on exit sleep mode). 3) Timer generates an interrupt at the appropriate time, but the timer interrupt does not execute because interrupts are disabled. 4) System comes out of sleep, calculates how long it was asleep for – if it was the timer that generated the interrupt then it slept for the entire period. 5) Call vTaskStepTick() to adjust tick count. 6) Enable interrupts, at which point….. 7) ….the ISR for the timer interrupt that brought the system out of sleep but was not able to execute until interrupts were enabled executes as a normal tick interrupt – which does all the normal tick interrupt things, including unblocking tasks that have an expired block time. Looking at the code now though I see there was an update that enables interrupts before the tick count was incremented ( https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c#l591 ), which means if the interrupt was the clock interrupt then the tick interrupt will only increment the tick count by one and the task will not be unblocked. I think the change was made to ensure interrupts from sources other than the clock execute at the earliest opportunity – which is desirable. Another undesirable consequence might be that the tick gets incremented one too many times, but I’ve not determined that is actually the case. That would mean however that the issue (to be determined if there is one) would not be fixed simply by pending a yield interrupt before exiting if the entire time had elapsed.

Tickless idle not resuming tasks immediately

I have investigated this further and at this time can’t see an issue in the default tickless idle mode: 1) If the CPU is brought out of sleep mode by the sleep time expiring, which is going to be a timer interrupt, then the timer interrupt (tick interrupt) will execute when interrupts are first re-enabled, which is before the tick count is stepped forward. https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c#l529 However, at that time the scheduler is suspended, so although the tick interrupt executes, the actually processing (incrementing the tick) is held pending until the scheduler is unsuspended. 2) Interrupts are then disabled again. https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c#l537 3) As a timer (tick) interrupt has already executed the number that will be stepped is adjusted accordingly. https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c#l578 4) The tick count is then stepped forward. https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c#l602 5) After the function exits, the scheduler is unsuspended, at which time the tick that is held pending is executed – so it is executed immediately the function exits and before the scheduler is actually unsuspended. https://sourceforge.net/p/freertos/code/HEAD/tree/tags/V10.2.1/FreeRTOS/Source/tasks.c#l3439

Tickless idle not resuming tasks immediately

Thanks for looking into this. I have been meaning to post some better clarification, but I have been too busy. I can see that the default implementations have done some things to work around the problem. I think part of the confusion is that your tickless idle guide (http://freertos.org/low-power-tickless-rtos.html) does not show these workarounds. Specifically the time is only incremented by the time difference before and after sleep (not the diff-1 in the implementations). If this is done, and the tick interrupt ALSO runs, then the time gets incremented one too many times. If the tick interrupt does not also run, then time gets incremented properly, but tasks do not get unblocked on time. It appears that Nordic may have followed this (instead of the default implementations), but instead of running the tick interrupt afterwards, they disable and clear the tick interrupt. This means that time gets corrected for the sleep time, but nothing processes until the next tick, since vTaskStepTick() does not unblock tasks, and xTaskIncrementTick does not get called until the next tick. Here is Nordic’s implementation: ~~~ void vPortSuppressTicksAndSleep( TickTypet xExpectedIdleTime ) { /* * Implementation note: * * To help debugging the option configUSETICKLESSIDLESIMPLEDEBUG was presented. * This option would make sure that even if program execution was stopped inside * this function no more than expected number of ticks would be skipped. * * Normally RTC works all the time even if firmware execution was stopped * and that may lead to skipping too much of ticks. */ TickTypet enterTime;
/* Make sure the SysTick reload value does not overflow the counter. */
if ( xExpectedIdleTime > portNRF_RTC_MAXTICKS - configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{
    xExpectedIdleTime = portNRF_RTC_MAXTICKS - configEXPECTED_IDLE_TIME_BEFORE_SLEEP;
}
/* Block all the interrupts globally */

ifdef SOFTDEVICE_PRESENT

do{
    uint8_t dummy = 0;
    uint32_t err_code = sd_nvic_critical_region_enter(&dummy);
    APP_ERROR_CHECK(err_code);
}while (0);

else

__disable_irq();

endif

enterTime = nrf_rtc_counter_get(portNRF_RTC_REG);

if ( eTaskConfirmSleepModeStatus() != eAbortSleep )
{
    TickType_t xModifiableIdleTime;
    TickType_t wakeupTime = (enterTime + xExpectedIdleTime) & portNRF_RTC_MAXTICKS;

    /* Stop tick events */
    nrf_rtc_int_disable(portNRF_RTC_REG, NRF_RTC_INT_TICK_MASK);

    /* Configure CTC interrupt */
    nrf_rtc_cc_set(portNRF_RTC_REG, 0, wakeupTime);
    nrf_rtc_event_clear(portNRF_RTC_REG, NRF_RTC_EVENT_COMPARE_0);
    nrf_rtc_int_enable(portNRF_RTC_REG, NRF_RTC_INT_COMPARE0_MASK);

    __DSB();

    /* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
     * set its parameter to 0 to indicate that its implementation contains
     * its own wait for interrupt or wait for event instruction, and so wfi
     * should not be executed again.  However, the original expected idle
     * time variable must remain unmodified, so a copy is taken. */
    xModifiableIdleTime = xExpectedIdleTime;
    configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
    if ( xModifiableIdleTime > 0 )
    {

if 1 // With FreeRTOS sdappevt_wait increases power consumption with FreeRTOS compared to _WFE (NRFFOSDK-11174)

ifdef SOFTDEVICE_PRESENT

        if (nrf_sdh_is_enabled())
        {
            uint32_t err_code = sd_app_evt_wait();
            APP_ERROR_CHECK(err_code);
        }
        else

endif

endif // (NRFFOSDK-11174)

        {
            /* No SD -  we would just block interrupts globally.
            * BASEPRI cannot be used for that because it would prevent WFE from wake up.
            */
            do{
                __WFE();
            } while (0 == (NVIC->ISPR[0] | NVIC->ISPR[1]));
        }
    }
    configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

    nrf_rtc_int_disable(portNRF_RTC_REG, NRF_RTC_INT_COMPARE0_MASK);
    nrf_rtc_event_clear(portNRF_RTC_REG, NRF_RTC_EVENT_COMPARE_0);

    /* Correct the system ticks */
    {
        TickType_t diff;
        TickType_t exitTime;

        nrf_rtc_event_clear(portNRF_RTC_REG, NRF_RTC_EVENT_TICK);
        nrf_rtc_int_enable (portNRF_RTC_REG, NRF_RTC_INT_TICK_MASK);

        exitTime = nrf_rtc_counter_get(portNRF_RTC_REG);
        diff =  (exitTime - enterTime) & portNRF_RTC_MAXTICKS;

        /* It is important that we clear pending here so that our corrections are latest and in sync with tick_interrupt handler */
        NVIC_ClearPendingIRQ(portNRF_RTC_IRQn);

        if ((configUSE_TICKLESS_IDLE_SIMPLE_DEBUG) && (diff > xExpectedIdleTime))
        {
            diff = xExpectedIdleTime;
        }

        if (diff > 0)
        {
            vTaskStepTick(diff);
        }
    }
}

ifdef SOFTDEVICE_PRESENT

uint32_t err_code = sd_nvic_critical_region_exit(0);
APP_ERROR_CHECK(err_code);

else

__enable_irq();

endif

} ~~~ Obviously part of the problem is their specific implementation (which I will bring up with them separately once I better understand it). But I am still trying to figure out why a FreeRTOS time increment function (i.e. vTaskStepTick) would not automatically unblock tasks? It instead relies on a sort of hybrid mode where BOTH time increment functions must be used. And this interdependency does not seem to be very well explained.

Tickless idle not resuming tasks immediately

Also it seems confusing, because the whole point of the tickless idle is to NOT run the tick interrupt every time (and not have it as a source of wakeup). So relying on it to be pending after a tickless sleep doesn’t make sense. Some implementations (including Nordic’s) use a completely different interrupt source to wake up from tickless idle (a compare event, instead of a tick event), so there is no reason for a tick to be pending. Even the function name (portSUPPRESSTICKSAND_SLEEP) suggests that the ticks should be supressed and should have no dependency.

Tickless idle not resuming tasks immediately

The tick interrupt doesn’t run all the time. The clock is programmed to generate an interrupt at whatever time a task next needs to leave the blocked state, an in the default implementation the same clock is used for the tick interrupt.