Skip to content

Commit 110cab8

Browse files
Andy Rossnashif
authored andcommitted
drivers/timer/systick: Improve clock slippage under irq_lock load
The SysTick logic looked logically sound, but it was allowing us to set a LOAD value as low as 512 cycles. On other platforms, that minimum future interrupt delay is there to protect the "read, compute, write, unmask" cycle that sets the new interrupt from trying to set one sooner than it can handle. But with SysTick, that value then becomes the value of the LOAD register, which is effectively the frequency with which timer interrupts arrive. This has two side effects: 1. It opens up the possibility that future code that masks interrupts longer than 512 cycles will miss more than one overflow, slipping the clock backward as viewed by z_clock_announce(). 2. The original code only understood one overflow cycle, so in the event we do set one of these very near timeouts and then mask interrupts, we'll only add at most one overflow to the "elapsed()" time, slipping the CURRENT time backward (actually turning it into a non-monotonic sawtooth which would slip every LOAD cycle) and thus messing up future scheduled interrupts, slipping those forward relative to what the ISR was counting. This patch simplifies the logic for reading SysTick VAL/CTRL (the loop wasn't needed), handles the case where we see more than one overflow, and increases the MIN_DELAY cycles from 512 to 1/16th of a tick. Fixes #15216 Signed-off-by: Andy Ross <[email protected]>
1 parent 100a2bb commit 110cab8

File tree

1 file changed

+50
-13
lines changed

1 file changed

+50
-13
lines changed

drivers/timer/cortex_m_systick.c

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010

1111
void z_ExcExit(void);
1212

13-
/* Minimum cycles in the future to try to program. */
14-
#define MIN_DELAY 512
15-
1613
#define COUNTER_MAX 0x00ffffff
1714
#define TIMER_STOPPED 0xff000000
1815

@@ -21,9 +18,26 @@ void z_ExcExit(void);
2118
#define MAX_TICKS ((COUNTER_MAX / CYC_PER_TICK) - 1)
2219
#define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK)
2320

21+
/* Minimum cycles in the future to try to program. Note that this is
22+
* NOT simply "enough cycles to get the counter read and reprogrammed
23+
* reliably" -- it becomes the minimum value of the LOAD register, and
24+
* thus reflects how much time we can reliably see expire between
25+
* calls to elapsed() to read the COUNTFLAG bit. So it needs to be
26+
* set to be larger than the maximum time the interrupt might be
27+
* masked. Choosing a fraction of a tick is probably a good enough
28+
* default, with an absolute minimum of 1k cyc.
29+
*/
30+
#define MIN_DELAY MAX(1024, (CYC_PER_TICK/16))
31+
2432
#define TICKLESS (IS_ENABLED(CONFIG_TICKLESS_KERNEL) && \
2533
!IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND))
2634

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

2943
static u32_t last_load;
@@ -32,19 +46,40 @@ static u32_t cycle_count;
3246

3347
static u32_t announced_cycles;
3448

35-
static volatile u32_t ctrl_cache; /* overflow bit clears on read! */
49+
static volatile u32_t overflow_cyc;
3650

3751
static u32_t elapsed(void)
3852
{
39-
u32_t val, ov;
53+
u32_t val, ctrl1, ctrl2;
54+
55+
/* SysTick is infuriatingly racy. The counter wraps at zero
56+
* automatically, setting a 1 in the COUNTFLAG bit of the CTRL
57+
* register when it does. But reading the control register
58+
* automatically resets that bit, so we need to save it for
59+
* future calls. And ordering is critical and race-prone: if
60+
* we read CTRL first, then it is possible for VAL to wrap
61+
* after that read but before we read VAL and we'll miss the
62+
* overflow. If we read VAL first, then it can wrap after we
63+
* read it and we'll see an "extra" overflow in CTRL. And we
64+
* want to handle multiple overflows, so we effectively must
65+
* read CTRL first otherwise there will be no way to detect
66+
* the double-overflow if called at the end of a cycle. There
67+
* is no safe algorithm here, so we split the difference by
68+
* reading CTRL twice, suppressing the second overflow bit if
69+
* VAL was "about to overflow".
70+
*/
71+
ctrl1 = SysTick->CTRL;
72+
val = SysTick->VAL & COUNTER_MAX;
73+
ctrl2 = SysTick->CTRL;
4074

41-
do {
42-
val = SysTick->VAL & COUNTER_MAX;
43-
ctrl_cache |= SysTick->CTRL;
44-
} while (SysTick->VAL > val);
75+
overflow_cyc += (ctrl1 & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
76+
if (val > VAL_ABOUT_TO_WRAP) {
77+
int wrap = ctrl2 & SysTick_CTRL_COUNTFLAG_Msk;
78+
79+
overflow_cyc += (wrap != 0) ? last_load : 0;
80+
}
4581

46-
ov = (ctrl_cache & SysTick_CTRL_COUNTFLAG_Msk) ? last_load : 0;
47-
return (last_load - val) + ov;
82+
return (last_load - val) + overflow_cyc;
4883
}
4984

5085
/* Callout out of platform assembly, not hooked via IRQ_CONNECT... */
@@ -57,8 +92,8 @@ void z_clock_isr(void *arg)
5792
dticks = (cycle_count - announced_cycles) / CYC_PER_TICK;
5893
announced_cycles += dticks * CYC_PER_TICK;
5994

60-
ctrl_cache = SysTick->CTRL; /* Reset overflow flag */
61-
ctrl_cache = 0U;
95+
overflow_cyc = SysTick->CTRL; /* Reset overflow flag */
96+
overflow_cyc = 0U;
6297

6398
z_clock_announce(TICKLESS ? dticks : 1);
6499
z_ExcExit();
@@ -68,6 +103,7 @@ int z_clock_driver_init(struct device *device)
68103
{
69104
NVIC_SetPriority(SysTick_IRQn, _IRQ_PRIO_OFFSET);
70105
last_load = CYC_PER_TICK;
106+
overflow_cyc = 0U;
71107
SysTick->LOAD = last_load;
72108
SysTick->VAL = 0; /* resets timer to last_load */
73109
SysTick->CTRL |= (SysTick_CTRL_ENABLE_Msk |
@@ -107,6 +143,7 @@ void z_clock_set_timeout(s32_t ticks, bool idle)
107143
delay = ((delay + CYC_PER_TICK - 1) / CYC_PER_TICK) * CYC_PER_TICK;
108144
last_load = delay - (cycle_count - announced_cycles);
109145

146+
overflow_cyc = 0U;
110147
SysTick->LOAD = last_load;
111148
SysTick->VAL = 0; /* resets timer to last_load */
112149

0 commit comments

Comments
 (0)