Skip to content

Commit f16a403

Browse files
pabigotdleach02
authored andcommitted
drivers: timer: SysTick: rework late overflow check
The previous solution depended on a magic number and was inefficient (entered the second-wrap conditional even when a second wrap hadn't been observed). Replace with an algorithm that is deterministic. Signed-off-by: Peter Bigot <[email protected]>
1 parent f7aa227 commit f16a403

File tree

1 file changed

+24
-37
lines changed

1 file changed

+24
-37
lines changed

drivers/timer/cortex_m_systick.c

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ void z_arm_exc_exit(void);
3131

3232
#define TICKLESS (IS_ENABLED(CONFIG_TICKLESS_KERNEL))
3333

34-
/* VAL value above which we assume that a subsequent COUNTFLAG
35-
* overflow seen in CTRL is real and not an artifact of wraparound
36-
* timing.
37-
*/
38-
#define VAL_ABOUT_TO_WRAP 8
39-
4034
static struct k_spinlock lock;
4135

4236
static u32_t last_load;
@@ -90,41 +84,34 @@ static volatile u32_t overflow_cyc;
9084
*/
9185
static u32_t elapsed(void)
9286
{
93-
u32_t val, ctrl1, ctrl2;
94-
95-
/* SysTick is infuriatingly racy. The counter wraps at zero
96-
* automatically, setting a 1 in the COUNTFLAG bit of the CTRL
97-
* register when it does. But reading the control register
98-
* automatically resets that bit, so we need to save it for
99-
* future calls. And ordering is critical and race-prone: if
100-
* we read CTRL first, then it is possible for VAL to wrap
101-
* after that read but before we read VAL and we'll miss the
102-
* overflow. If we read VAL first, then it can wrap after we
103-
* read it and we'll see an "extra" overflow in CTRL. And we
104-
* want to handle multiple overflows, so we effectively must
105-
* read CTRL first otherwise there will be no way to detect
106-
* the double-overflow if called at the end of a cycle. There
107-
* is no safe algorithm here, so we split the difference by
108-
* reading CTRL twice, suppressing the second overflow bit if
109-
* VAL was "about to overflow".
110-
*/
111-
ctrl1 = SysTick->CTRL;
112-
val = SysTick->VAL;
113-
ctrl2 = SysTick->CTRL;
114-
115-
/* overflow_cyc is reset to zero by
116-
* - _init()
117-
* - _isr()
118-
* - _set_timeout()
87+
u32_t val1 = SysTick->VAL; /* A */
88+
u32_t ctrl = SysTick->CTRL; /* B */
89+
u32_t val2 = SysTick->VAL; /* C */
90+
91+
/* SysTick behavior: The counter wraps at zero automatically,
92+
* setting the COUNTFLAG field of the CTRL register when it
93+
* does. Reading the control register automatically clears
94+
* that field.
95+
*
96+
* If the count wrapped...
97+
* 1) Before A then COUNTFLAG will be set and val1 >= val2
98+
* 2) Between A and B then COUNTFLAG will be set and val1 < val2
99+
* 3) Between B and C then COUNTFLAG will be clear and val1 < val2
100+
* 4) After C we'll see it next time
101+
*
102+
* So the count in val2 is post-wrap and last_load needs to be
103+
* added if and only if COUNTFLAG is set or val1 < val2.
119104
*/
120-
overflow_cyc += (ctrl1 & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
121-
if (val > VAL_ABOUT_TO_WRAP) {
122-
int wrap = ctrl2 & SysTick_CTRL_COUNTFLAG_Msk;
105+
if ((ctrl & SysTick_CTRL_COUNTFLAG_Msk)
106+
|| (val1 < val2)) {
107+
overflow_cyc += last_load;
123108

124-
overflow_cyc += (wrap != 0) ? last_load : 0;
109+
/* We know there was a wrap, but we might not have
110+
* seen it in CTRL, so clear it. */
111+
(void)SysTick->CTRL;
125112
}
126113

127-
return (last_load - val) + overflow_cyc;
114+
return (last_load - val2) + overflow_cyc;
128115
}
129116

130117
/* Callout out of platform assembly, not hooked via IRQ_CONNECT... */

0 commit comments

Comments
 (0)