Skip to content

drivers: timer: cortex_m_systick: a tick longer than the 24-bit counter degrades to a 10us interrupt storm #117169

Description

@nashif

Describe the bug

On a Cortex-M where one kernel tick is longer than the 24-bit SysTick counter
can express, cortex_m_systick silently falls back to its minimum delay and
interrupts continuously, instead of programming as far ahead as it can.

On frdm_mcxn947/mcxn947/cpu0 (150 MHz) with
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1 and a tickless kernel, one tick is
150,000,000 cycles against a counter maximum of 16,777,215. The driver ends up
programming SysTick->LOAD = 1499, which is SYSTICK_MIN_DELAY_US (10 us) at
150 MHz, and the timer interrupt then fires roughly 78,000 times a second.

Asking for fewer ticks produces vastly more interrupts, which is the opposite
of what the option says.

To Reproduce

Any application on frdm_mcxn947/mcxn947/cpu0 with:

CONFIG_SYS_CLOCK_TICKS_PER_SEC=1
CONFIG_TICKLESS_KERNEL=y

Reading the peripheral at run time shows the reload:

SysTick CTRL=0x00000007 LOAD=1499

Watching DWT_EXCCNT change with nothing else running gives the arrival rate
(collected into an array and printed afterwards, so the print itself does not
perturb it):

probe  0: exc +19 after 1804 cycles
probe  1: exc +19 after 1920 cycles
probe  2: exc +19 after 1919 cycles
probe  3: exc +19 after 1926 cycles
   ... every entry identical

An interrupt every ~1920 cycles: 1500 counts of reload plus the ISR's own
reprogramming. Changing only the tick rate to 100 gives the correct behaviour
on the same board:

CONFIG_SYS_CLOCK_TICKS_PER_SEC cycles/tick SysTick->LOAD interrupt period
100 1,500,000 16,499,517 16.5M cycles (110 ms)
1 150,000,000 1,499 ~1,920 cycles (12.8 us)

At 100 the driver programs 11 ticks ahead, floor(0xFFFFFF / 1500000) = 11,
so 11 * 1500000 = 16500000, exactly what is measured. At 1 it cannot place
even a single tick and collapses to the floor.

Expected behavior

Either of:

  • refuse the configuration at build time, the way the driver already intends
    to, or
  • program the counter as far ahead as it will reach and account for the
    remainder across several interrupts, as it does for any other timeout longer
    than the arming register.

Silently interrupting 78,000 times a second when asked for once a second is the
worst of the three.

Impact

Measured on the same board with tests/benchmarks/interrupt_latency (#117170), the
interrupt costs the interrupted code about 485 cycles each time it lands in a
measurement. Interrupt entry latency goes from a clean 18.0 cycles
(sd 0.21, 1 outlier in 10000) to a mean of 35.8 (sd 100.7, 463 outliers in
10000, p99 498.6). Roughly a quarter of the CPU is spent in a timer ISR that
announces no ticks, and any latency-sensitive work sees a 3.2 us disturbance at
78 kHz.

It is silent: nothing warns at build or boot, and the symptom (a fixed-size
disturbance at high frequency) looks like external interference rather than the
tick configuration.

Why the existing build guard does not catch it

drivers/timer/system_timer_generic.h already has the right assertion:

BUILD_ASSERT(TIMER_CORE_COUNTER_SAFE_SPAN >= TIMER_CORE_CYC_PER_TICK,
	     "a tick is longer than the counter can span: raise "
	     "CONFIG_SYS_CLOCK_TICKS_PER_SEC, or slow the counter");

It does not fire here because drivers/timer/cortex_m_systick.c declares

#define TIMER_CORE_COUNTER_WIDTH 32

The driver synthesises a 32-bit cycle count in software from the 24-bit
hardware counter, and narrows only the arming limit:

#define TIMER_CORE_ALARM_MAX_CYCLES MAX_CYCLES   /* 0x00ffffff */

So TIMER_CORE_COUNTER_SAFE_SPAN is derived from a 32-bit mask (~2^31) and the
assertion passes, even though the value that actually bounds how far ahead the
hardware can be armed is 24 bits. The condition the assertion is meant to catch
is about what can be armed, which is TIMER_CORE_ALARM_MAX_CYCLES here, not
the width of the synthesised counter.

I have not traced why the fallback lands exactly on SYSTICK_MIN_DELAY_US
rather than on TIMER_CORE_ALARM_MAX_CYCLES; the numbers above are measured,
the analysis of the guard is from reading the source.

Environment

  • Zephyr: v4.4.0-12764-gc5cbb6d09bff
  • Toolchain: Zephyr SDK 1.0.1, zephyr/gnu
  • Board: frdm_mcxn947/mcxn947/cpu0 at 150 MHz
  • Host: Linux

Metadata

Metadata

Assignees

Labels

area: TimerTimerbugThe issue is a bug, or the PR is fixing a bugpriority: highHigh impact/importance bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions