Assert in tasks.c: 2611 on xTickCount wrap around

Hi all,
I use FreeRTOS v 10.0.1. and I have some issues when xTickCount wraps around.
It seemd that pxCallbackFunction from xTimer stopped to be called on timer expiration.
It happens sometimes when xTickCount wraps around. I didn’t manage to find exact conditions at which it would reproduce every time.
I’ve been tracking down this problem and when I’ve turned on RTOS assertions I got cought by assert in tasks.c: 2611.
It seems that it is
configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) ) from taskSWITCH_DELAYED_LISTS() which is called in xTaskIncrementTick().

I’ve looked for some fixes for such issue in newer freeRTOS versions but I couldn’t find anything matching in a changlog (https://www.freertos.org/History.txt).
Maybe someone will know if it is known issue, or if any fix for that was applied since 10.0.1 (maybe I’ve overlooked something).

Are you using tickless idle mode or any other power saving features?

Yes, I use tickless idle.
I have configUSE_TICKLESS_IDLE set to 1 to use built in implementation of tickless idle for nrf52 port (delivered by Nordic’s nrf5 sdk). It looks like:

void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
    /*
     * Implementation note:
     *
     * To help debugging the option configUSE_TICKLESS_IDLE_SIMPLE_DEBUG 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.
     */
    TickType_t 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();
        
        /*
         * Clear FPU exceptions.
         * Without this step, the FPU interrupt is marked as pending,
         * preventing system from sleeping. Exceptions cleared:
         * - IOC - Invalid Operation cumulative exception bit.
         * - DZC - Division by Zero cumulative exception bit.
         * - OFC - Overflow cumulative exception bit.
         * - UFC - Underflow cumulative exception bit.
         * - IXC - Inexact cumulative exception bit.
         * - IDC - Input Denormal cumulative exception bit.
         */
        uint32_t fpscr;
        fpscr = __get_FPSCR();
        __set_FPSCR(fpscr & ~0x9Fu);
        __DMB();
        NVIC_ClearPendingIRQ(FPU_IRQn);
        
        /* 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 )
        {
#ifdef SOFTDEVICE_PRESENT
            if (nrf_sdh_is_enabled())
            {
                uint32_t err_code = sd_app_evt_wait();
                APP_ERROR_CHECK(err_code);
            }
            else
#endif
            {
                /* 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

There does appear to be a small error in the Nordic code, and I think it would cause the assertion failure you are experiencing under specific circumstances. Function vTaskStepTick() doesn’t process ticks normally, so it should approach but never reach xNextTaskUnblockTime.

You could try a small fix like this:

Current Code:

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

Proposed Code:

            if (diff > 0)
            {
                vTaskStepTick(diff - 1);
                NVIC_SetPendingIRQ(portNRF_RTC_IRQn);
            }

But I don’t know anything about Nordic’s use of the RTC or its ISR, so for all I know this code could totally blow up the system tick. :wink:

1 Like

On second thought, adding support in FreeRTOS for this implementation of tickless idle is also an option.

1 Like

I’ve verified proposed changes on my setup and they both work.
Thank you very much for your support.:slight_smile:

1 Like