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.
This commit is contained in:
Jeff Tenney 2020-05-20 17:03:58 -07:00
parent 967acc9b20
commit 316b656326

View file

@ -515,7 +515,7 @@ void xPortSysTickHandler( void )
__attribute__((weak)) void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) __attribute__((weak)) void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{ {
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements; uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickDecrementsLeft;
TickType_t xModifiableIdleTime; TickType_t xModifiableIdleTime;
/* Make sure the SysTick reload value does not overflow the counter. */ /* Make sure the SysTick reload value does not overflow the counter. */
@ -546,26 +546,27 @@ void xPortSysTickHandler( void )
kernel with respect to calendar time. */ kernel with respect to calendar time. */
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT ); 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 /* Calculate the reload value required to wait xExpectedIdleTime
tick periods. -1 is used because this code normally executes part tick periods. -1 is used because this code normally executes part
way through the first tick period. But if the SysTick IRQ is now way through the first tick period. But if the SysTick IRQ is now
pending, then clear the IRQ, suppressing the first tick, and correct pending, then clear the IRQ, suppressing the first tick, and correct
the reload value to reflect that the second tick period is already the reload value to reflect that the second tick period is already
underway. The expected idle time is always at least two ticks. */ 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 ) if( ( portNVIC_ICSR_REG & portNVIC_PEND_SYSTICK_SET_BIT ) != 0 )
{ {
portNVIC_ICSR_REG = portNVIC_PEND_SYSTICK_CLEAR_BIT; portNVIC_ICSR_REG = portNVIC_PEND_SYSTICK_CLEAR_BIT;
ulReloadValue -= ulTimerCountsForOneTick;
/* 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;
}
} }
if( ulReloadValue > ulStoppedTimerCompensation ) if( ulReloadValue > ulStoppedTimerCompensation )
{ {
@ -621,19 +622,14 @@ void xPortSysTickHandler( void )
time*/ time*/
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT ); portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT );
/* Determine if the SysTick clock has already counted to zero and /* Determine whether the SysTick has already counted to zero. */
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). */
if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 ) if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
{ {
uint32_t ulCalculatedLoadValue; uint32_t ulCalculatedLoadValue;
/* The tick interrupt is already pending, and the SysTick count /* The tick interrupt ended the sleep (or is now pending), and
reloaded with ulReloadValue. Reset the a new tick period has started. Reset portNVIC_SYSTICK_LOAD_REG
portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick with whatever remains of the new tick period. */
period. */
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG ); ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
/* Don't allow a tiny value, or values that have somehow /* Don't allow a tiny value, or values that have somehow
@ -654,11 +650,29 @@ void xPortSysTickHandler( void )
} }
else else
{ {
/* Something other than the tick interrupt ended the sleep. /* Something other than the tick interrupt ended the sleep. */
Work out how long the sleep lasted rounded to complete tick
/* 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 periods (not the ulReload value which accounted for part
ticks). */ ticks). */
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG; ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - ulSysTickDecrementsLeft;
/* How many complete tick periods passed while the processor /* How many complete tick periods passed while the processor
was waiting? */ was waiting? */
@ -678,12 +692,12 @@ void xPortSysTickHandler( void )
to receive the standard value immediately. */ to receive the standard value immediately. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT; 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 ) #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; portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
} }
#endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */ #endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
/* Step the tick to account for any tick periods that elapsed. */ /* Step the tick to account for any tick periods that elapsed. */
vTaskStepTick( ulCompleteTickPeriods ); vTaskStepTick( ulCompleteTickPeriods );