From 316b6563261163ced58d2c1e01824a274bb90ab7 Mon Sep 17 00:00:00 2001 From: Jeff Tenney Date: Wed, 20 May 2020 17:03:58 -0700 Subject: [PATCH] Fix tickless idle with alternate systick clocking Prior to this commit, in configurations using the alternate SysTick clocking, vPortSuppressTicksAndSleep() might cause xTickCount to jump ahead as much as the entire expected idle time or fall behind as much as one full tick compared to time as measured by the SysTick. SysTick ------- The SysTick is the hardware timer that provides the OS tick interrupt in the official ports for Cortex M. SysTick starts counting down from the value stored in its reload register. When SysTick reaches zero, it requests an interrupt. On the next SysTick clock cycle, it loads the counter again from the reload register. The SysTick has a configuration option to be clocked by an alternate clock besides the core clock. This alternate clock is MCU dependent. Scenarios Fixed --------------- The new code in this commit handles the following scenarios that were not handled correctly prior to this commit. 1. Before the sleep, vPortSuppressTicksAndSleep() stops the SysTick on zero, long after SysTick reached zero. Prior to this commit, this scenario caused xTickCount to jump ahead one full tick for the same reason documented here: https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/59/commits/0c7b04bd3a745c52151abebc882eed3f811c4c81 2. After the sleep, vPortSuppressTicksAndSleep() stops the SysTick before it loads the counter from the reload register. Prior to this commit, this scenario caused xTickCount to jump ahead by the entire expected idle time (xExpectedIdleTime) because the current-count register is zero before it loads from the reload register. 3. Prior to return, vPortSuppressTicksAndSleep() attempts to start a short SysTick period when the current SysTick clock cycle has a lot of time remaining. Prior to this commit, this scenario could cause xTickCount to fall behind by as much as nearly one full tick because the short SysTick cycle never started. Note that #3 is partially fixed by https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/59/commits/967acc9b200d3d4beeb289d9da9e88798074b431 even though that commit addresses a different issue. So this commit completes the partial fix. --- portable/GCC/ARM_CM4F/port.c | 64 ++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/portable/GCC/ARM_CM4F/port.c b/portable/GCC/ARM_CM4F/port.c index fec0a2714..1afc04f79 100644 --- a/portable/GCC/ARM_CM4F/port.c +++ b/portable/GCC/ARM_CM4F/port.c @@ -515,7 +515,7 @@ void xPortSysTickHandler( void ) __attribute__((weak)) void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { - uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements; + uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickDecrementsLeft; TickType_t xModifiableIdleTime; /* Make sure the SysTick reload value does not overflow the counter. */ @@ -546,26 +546,27 @@ void xPortSysTickHandler( void ) kernel with respect to calendar time. */ portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT ); + /* Use the SysTick current-value register to determine the number of + SysTick decrements remaining until the next tick interrupt. If the + current-value register is zero, then there are actually + ulTimerCountsForOneTick decrements remaining, not zero. */ + ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG; + if( ulSysTickDecrementsLeft == 0 ) + { + ulSysTickDecrementsLeft = ulTimerCountsForOneTick; + } + /* Calculate the reload value required to wait xExpectedIdleTime tick periods. -1 is used because this code normally executes part way through the first tick period. But if the SysTick IRQ is now pending, then clear the IRQ, suppressing the first tick, and correct the reload value to reflect that the second tick period is already underway. The expected idle time is always at least two ticks. */ - ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * (xExpectedIdleTime - 1UL) ); + ulReloadValue = ulSysTickDecrementsLeft + ( ulTimerCountsForOneTick * (xExpectedIdleTime - 1UL) ); if( ( portNVIC_ICSR_REG & portNVIC_PEND_SYSTICK_SET_BIT ) != 0 ) { portNVIC_ICSR_REG = portNVIC_PEND_SYSTICK_CLEAR_BIT; - - /* Skip the correction described above if SysTick happened to - stop on zero. In that case, there are still - ulTimerCountsForOneTick ticks left in the second tick period, - not zero, so the value in portNVIC_SYSTICK_CURRENT_VALUE_REG - already includes the correction normally done here. */ - if( portNVIC_SYSTICK_CURRENT_VALUE_REG != 0 ) - { - ulReloadValue -= ulTimerCountsForOneTick; - } + ulReloadValue -= ulTimerCountsForOneTick; } if( ulReloadValue > ulStoppedTimerCompensation ) { @@ -621,19 +622,14 @@ void xPortSysTickHandler( void ) time*/ portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT ); - /* Determine if the SysTick clock has already counted to zero and - been set back to the current reload value (the reload back being - correct for the entire expected idle time) or if the SysTick is yet - to count to zero (in which case an interrupt other than the SysTick - must have brought the system out of sleep mode). */ + /* Determine whether the SysTick has already counted to zero. */ if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ) { uint32_t ulCalculatedLoadValue; - /* The tick interrupt is already pending, and the SysTick count - reloaded with ulReloadValue. Reset the - portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick - period. */ + /* The tick interrupt ended the sleep (or is now pending), and + a new tick period has started. Reset portNVIC_SYSTICK_LOAD_REG + with whatever remains of the new tick period. */ ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG ); /* Don't allow a tiny value, or values that have somehow @@ -654,11 +650,29 @@ void xPortSysTickHandler( void ) } else { - /* Something other than the tick interrupt ended the sleep. - Work out how long the sleep lasted rounded to complete tick + /* Something other than the tick interrupt ended the sleep. */ + + /* Use the SysTick current-value register to determine the + number of SysTick decrements remaining until the expected idle + time would have ended. */ + ulSysTickDecrementsLeft = portNVIC_SYSTICK_CURRENT_VALUE_REG; + #if( portNVIC_SYSTICK_CLK_BIT_SETTING != portNVIC_SYSTICK_CLK_BIT ) + { + /* If the SysTick is not using the core clock, the current- + value register might still be zero here. In that case, the + SysTick didn't load from the reload register, and there are + ulReloadValue + 1 decrements remaining, not zero. */ + if( ulSysTickDecrementsLeft == 0 ) + { + ulSysTickDecrementsLeft = ulReloadValue + 1UL; + } + } + #endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */ + + /* Work out how long the sleep lasted rounded to complete tick periods (not the ulReload value which accounted for part ticks). */ - ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG; + ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - ulSysTickDecrementsLeft; /* How many complete tick periods passed while the processor was waiting? */ @@ -678,12 +692,12 @@ void xPortSysTickHandler( void ) to receive the standard value immediately. */ portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT; + portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; #if( portNVIC_SYSTICK_CLK_BIT_SETTING != portNVIC_SYSTICK_CLK_BIT ) { portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT; } #endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */ - portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; /* Step the tick to account for any tick periods that elapsed. */ vTaskStepTick( ulCompleteTickPeriods );