mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-09-12 00:57:44 -04:00
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.
This commit is contained in:
parent
0c7b04bd3a
commit
967acc9b20
1 changed files with 32 additions and 17 deletions
|
@ -37,16 +37,6 @@
|
|||
#error This port can only be used when the project options are configured to enable hardware floating point support.
|
||||
#endif
|
||||
|
||||
#ifndef configSYSTICK_CLOCK_HZ
|
||||
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
|
||||
/* Ensure the SysTick is clocked at the same frequency as the core. */
|
||||
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
|
||||
#else
|
||||
/* The way the SysTick is clocked is not modified in case it is not the same
|
||||
as the core. */
|
||||
#define portNVIC_SYSTICK_CLK_BIT ( 0 )
|
||||
#endif
|
||||
|
||||
/* Constants required to manipulate the core. Registers first... */
|
||||
#define portNVIC_SYSTICK_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000e010 ) )
|
||||
#define portNVIC_SYSTICK_LOAD_REG ( * ( ( volatile uint32_t * ) 0xe000e014 ) )
|
||||
|
@ -54,6 +44,7 @@
|
|||
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
|
||||
#define portNVIC_ICSR_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
|
||||
/* ...then bits in the registers. */
|
||||
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
|
||||
#define portNVIC_SYSTICK_INT_BIT ( 1UL << 1UL )
|
||||
#define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL )
|
||||
#define portNVIC_SYSTICK_COUNT_FLAG_BIT ( 1UL << 16UL )
|
||||
|
@ -103,6 +94,19 @@ occurred while the SysTick counter is stopped during tickless idle
|
|||
calculations. */
|
||||
#define portMISSED_COUNTS_FACTOR ( 45UL )
|
||||
|
||||
/* Let the user override the default SysTick clock rate. If defined by the
|
||||
user, this symbol must equal the SysTick clock rate when the CLK bit is 0 in the
|
||||
configuration register. */
|
||||
#ifndef configSYSTICK_CLOCK_HZ
|
||||
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
|
||||
/* Ensure the SysTick is clocked at the same frequency as the core. */
|
||||
#define portNVIC_SYSTICK_CLK_BIT_SETTING ( portNVIC_SYSTICK_CLK_BIT )
|
||||
#else
|
||||
/* Select the option to clock SysTick not at the same frequency as the core.
|
||||
The clock used is often a divided version of the core clock. */
|
||||
#define portNVIC_SYSTICK_CLK_BIT_SETTING ( 0 )
|
||||
#endif
|
||||
|
||||
/* Let the user override the pre-loading of the initial LR with the address of
|
||||
prvTaskExitError() in case it messes up unwinding of the stack in the
|
||||
debugger. */
|
||||
|
@ -615,7 +619,7 @@ void xPortSysTickHandler( void )
|
|||
be, but using the tickless mode will inevitably result in some tiny
|
||||
drift of the time maintained by the kernel with respect to calendar
|
||||
time*/
|
||||
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | 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
|
||||
been set back to the current reload value (the reload back being
|
||||
|
@ -665,14 +669,25 @@ void xPortSysTickHandler( void )
|
|||
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
|
||||
}
|
||||
|
||||
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
|
||||
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
|
||||
value. */
|
||||
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG again,
|
||||
then set portNVIC_SYSTICK_LOAD_REG back to its standard value. If
|
||||
the SysTick is not using the core clock, temporarily configure it to
|
||||
use the core clock. This configuration forces the SysTick to load
|
||||
from portNVIC_SYSTICK_LOAD_REG immediately instead of at the next
|
||||
cycle of the other clock. Then portNVIC_SYSTICK_LOAD_REG is ready
|
||||
to receive the standard value immediately. */
|
||||
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
|
||||
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
|
||||
vTaskStepTick( ulCompleteTickPeriods );
|
||||
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_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;
|
||||
}
|
||||
#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 );
|
||||
|
||||
/* Exit with interrupts enabled. */
|
||||
__asm volatile( "cpsie i" ::: "memory" );
|
||||
}
|
||||
|
@ -702,7 +717,7 @@ __attribute__(( weak )) void vPortSetupTimerInterrupt( void )
|
|||
|
||||
/* Configure SysTick to interrupt at the requested rate. */
|
||||
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
|
||||
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | 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 );
|
||||
}
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue