Skip to content

Commit 967acc9

Browse files
committed
Fix imminent tick rescheduled after tickless idle
Prior to this commit, if something other than systick wakes the CPU from tickless idle, vPortSuppressTicksAndSleep() might cause xTickCount to increment once too many times. See "bug 2" in this forum post: https://forums.freertos.org/t/ultasknotifytake-timeout-accuracy/9629/40 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. To get periodic interrupts every N SysTick clock cycles, the reload register must be N - 1. Bug Example ----------- - CPU is sleeping in vPortSuppressTicksAndSleep() - Something other than the SysTick wakes the CPU. - vPortSuppressTicksAndSleep() calculates the number of SysTick counts until the next tick. The bug occurs only if this number is small. - vPortSuppressTicksAndSleep() puts this small number into the SysTick reload register, and starts SysTick. - vPortSuppressTicksAndSleep() calls vTaskStepTick() - While vTaskStepTick() executes, the SysTick expires. The ISR pends because interrupts are masked, and SysTick starts a 2nd period still based on the small number of counts in its reload register. This 2nd period is undesirable and is likely to cause the error noted below. - vPortSuppressTicksAndSleep() puts the normal tick duration into the SysTick's reload register. - vPortSuppressTicksAndSleep() unmasks interrupts before the SysTick starts a new period based on the new value in the reload register. [This is a race condition that can go either way, but for the bug to occur, the race must play out this way.] - The pending SysTick ISR executes and increments xPendedTicks. - The SysTick expires again, finishing the second very small period, and starts a new period this time based on the full tick duration. - The SysTick ISR increments xPendedTicks (or xTickCount) even though only a tiny fraction of a tick period has elapsed since the previous tick. The bug occurs when *two* consecutive small periods of the SysTick are both counted as ticks. The root cause is a race caused by the small SysTick period. If vPortSuppressTicksAndSleep() unmasks interrupts *after* the small period expires but *before* the SysTick starts a period based on the full tick period, then two small periods are counted as ticks when only one should be counted. The end result is xTickCount advancing nearly one full tick more than time actually elapsed as measured by the SysTick. This is not the kind of time slippage normally associated with tickless idle. After this commit the code starts the SysTick and then immediately modifies the reload register to ensure the very short cycle (if any) is conducted only once. This strategy requires special consideration for the build option that configures SysTick to use a divided clock. To avoid waiting around for the SysTick to load value from the reload register, the new code temporarily configures the SysTick to use the undivided clock. The resulting timing error is typical for tickless idle. The error (commonly known as drift or slippage in kernel time) caused by this strategy is equivalent to one or two counts in ulStoppedTimerCompensation. This commit also updates comments and #define symbols related to the SysTick clock option. The SysTick can optionally be clocked by a divided version of the CPU clock (commonly divide-by-8). The new code in this commit adjusts these comments and symbols to make them clearer and more useful in configurations that use the divided clock. The fix made in this commit requires the use of these symbols, as noted in the code comments.
1 parent 0c7b04b commit 967acc9

File tree

1 file changed

+32
-17
lines changed

1 file changed

+32
-17
lines changed

portable/GCC/ARM_CM4F/port.c

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,14 @@
3737
#error This port can only be used when the project options are configured to enable hardware floating point support.
3838
#endif
3939

40-
#ifndef configSYSTICK_CLOCK_HZ
41-
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
42-
/* Ensure the SysTick is clocked at the same frequency as the core. */
43-
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
44-
#else
45-
/* The way the SysTick is clocked is not modified in case it is not the same
46-
as the core. */
47-
#define portNVIC_SYSTICK_CLK_BIT ( 0 )
48-
#endif
49-
5040
/* Constants required to manipulate the core. Registers first... */
5141
#define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
5242
#define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
5343
#define portNVIC_SYSTICK_CURRENT_VALUE_REG ( * ( ( volatile uint32_t * ) 0xe000e018 ) )
5444
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
5545
#define portNVIC_ICSR_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
5646
/* ...then bits in the registers. */
47+
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
5748
#define portNVIC_SYSTICK_INT_BIT ( 1UL << 1UL )
5849
#define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL )
5950
#define portNVIC_SYSTICK_COUNT_FLAG_BIT ( 1UL << 16UL )
@@ -103,6 +94,19 @@ occurred while the SysTick counter is stopped during tickless idle
10394
calculations. */
10495
#define portMISSED_COUNTS_FACTOR ( 45UL )
10596

97+
/* Let the user override the default SysTick clock rate. If defined by the
98+
user, this symbol must equal the SysTick clock rate when the CLK bit is 0 in the
99+
configuration register. */
100+
#ifndef configSYSTICK_CLOCK_HZ
101+
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
102+
/* Ensure the SysTick is clocked at the same frequency as the core. */
103+
#define portNVIC_SYSTICK_CLK_BIT_SETTING ( portNVIC_SYSTICK_CLK_BIT )
104+
#else
105+
/* Select the option to clock SysTick not at the same frequency as the core.
106+
The clock used is often a divided version of the core clock. */
107+
#define portNVIC_SYSTICK_CLK_BIT_SETTING ( 0 )
108+
#endif
109+
106110
/* Let the user override the pre-loading of the initial LR with the address of
107111
prvTaskExitError() in case it messes up unwinding of the stack in the
108112
debugger. */
@@ -615,7 +619,7 @@ void xPortSysTickHandler( void )
615619
be, but using the tickless mode will inevitably result in some tiny
616620
drift of the time maintained by the kernel with respect to calendar
617621
time*/
618-
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );
622+
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT );
619623

620624
/* Determine if the SysTick clock has already counted to zero and
621625
been set back to the current reload value (the reload back being
@@ -665,13 +669,24 @@ void xPortSysTickHandler( void )
665669
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
666670
}
667671

668-
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
669-
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
670-
value. */
672+
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG again,
673+
then set portNVIC_SYSTICK_LOAD_REG back to its standard value. If
674+
the SysTick is not using the core clock, temporarily configure it to
675+
use the core clock. This configuration forces the SysTick to load
676+
from portNVIC_SYSTICK_LOAD_REG immediately instead of at the next
677+
cycle of the other clock. Then portNVIC_SYSTICK_LOAD_REG is ready
678+
to receive the standard value immediately. */
671679
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
672-
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
673-
vTaskStepTick( ulCompleteTickPeriods );
680+
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
681+
#if( portNVIC_SYSTICK_CLK_BIT_SETTING != portNVIC_SYSTICK_CLK_BIT )
682+
{
683+
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
684+
}
685+
#endif /* portNVIC_SYSTICK_CLK_BIT_SETTING */
674686
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
687+
688+
/* Step the tick to account for any tick periods that elapsed. */
689+
vTaskStepTick( ulCompleteTickPeriods );
675690

676691
/* Exit with interrupts enabled. */
677692
__asm volatile( "cpsie i" ::: "memory" );
@@ -702,7 +717,7 @@ __attribute__(( weak )) void vPortSetupTimerInterrupt( void )
702717

703718
/* Configure SysTick to interrupt at the requested rate. */
704719
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
705-
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
720+
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_SETTING | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
706721
}
707722
/*-----------------------------------------------------------*/
708723

0 commit comments

Comments
 (0)