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
Describe the bug
On a Cortex-M where one kernel tick is longer than the 24-bit SysTick counter
can express,
cortex_m_systicksilently falls back to its minimum delay andinterrupts continuously, instead of programming as far ahead as it can.
On
frdm_mcxn947/mcxn947/cpu0(150 MHz) withCONFIG_SYS_CLOCK_TICKS_PER_SEC=1and a tickless kernel, one tick is150,000,000 cycles against a counter maximum of 16,777,215. The driver ends up
programming
SysTick->LOAD = 1499, which isSYSTICK_MIN_DELAY_US(10 us) at150 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/cpu0with:Reading the peripheral at run time shows the reload:
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):
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_SECSysTick->LOADAt 100 the driver programs 11 ticks ahead,
floor(0xFFFFFF / 1500000) = 11,so
11 * 1500000 = 16500000, exactly what is measured. At 1 it cannot placeeven a single tick and collapses to the floor.
Expected behavior
Either of:
to, or
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), theinterrupt 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.halready has the right assertion:It does not fire here because
drivers/timer/cortex_m_systick.cdeclaresThe driver synthesises a 32-bit cycle count in software from the 24-bit
hardware counter, and narrows only the arming limit:
So
TIMER_CORE_COUNTER_SAFE_SPANis derived from a 32-bit mask (~2^31) and theassertion 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_CYCLEShere, notthe width of the synthesised counter.
I have not traced why the fallback lands exactly on
SYSTICK_MIN_DELAY_USrather than on
TIMER_CORE_ALARM_MAX_CYCLES; the numbers above are measured,the analysis of the guard is from reading the source.
Environment
v4.4.0-12764-gc5cbb6d09bffzephyr/gnufrdm_mcxn947/mcxn947/cpu0at 150 MHz