arch/arm/stm32: fix compare-match race and zero-period hang in tickless

This patch addresses two issues in the single-timer capture/compare
tickless OS drivers for STM32 families (common m3m4 v1 for F1/F2/F3/F4/G4,
F7, H7, and WB):

1. Zero-period handling: when up_timer_start() is called with a zero or
   negative duration (or period converts to 0 ticks), the driver now
   enables the compare match interrupt and immediately fires an event
   via EGR (CCxG), avoiding missed events or unexpected counter behavior.

2. Compare-match race condition: after programming CCR and enabling the
   compare interrupt, a post-check validates whether the free-running
   counter already reached or passed count + period during register
   configuration. If elapsed, the interrupt is forced immediately via EGR,
   preventing the counter from missing the match and hanging until a full
   32-bit rollover (approx. 71 minutes at 1 MHz).

Verified on real hardware:
- STM32H743ZI (IED R550): validated with ping, sleep, and usleep.
- STM32G431KB (Nucleo-G431KB): validated with uptime, sleep, and usleep.

Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
This commit is contained in:
Daniel P. Carvalho 2026-09-17 09:39:31 -03:00 • committed by Xiang Xiao
parent 877d1537df
commit 09423194bf
4 changed files with 253 additions and 78 deletions

View file

@ -207,6 +207,15 @@ static inline void stm32_tickless_ackint(int channel)
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
}
/****************************************************************************
* Name: stm32_tickless_trigint
****************************************************************************/
static inline void stm32_tickless_trigint(int channel)
{
stm32_putreg16(STM32_ATIM_EGR_OFFSET, 1 << channel);
}
/****************************************************************************
* Name: stm32_tickless_getint
****************************************************************************/
@ -835,15 +844,9 @@ int up_timer_cancel(struct timespec *ts)
(unsigned long)period, (unsigned long)count);
#ifndef HAVE_32BIT_TICKLESS
if (count > period)
{
/* Handle rollover */
period += UINT16_MAX;
}
else if (count == period)
if ((int16_t)(period - count) <= 0)
#else
if (count >= period)
if ((int32_t)(period - count) <= 0)
#endif
{
/* No time remaining */
@ -861,8 +864,13 @@ int up_timer_cancel(struct timespec *ts)
* usecs = (ticks * USEC_PER_SEC) / frequency;
*/
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
#ifndef HAVE_32BIT_TICKLESS
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#else
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#endif
/* Return the time remaining in the correct form */
@ -929,18 +937,35 @@ int up_timer_start(const struct timespec *ts)
/* Express the delay in microseconds */
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
{
period = 0;
}
else
{
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
/* Get the timer counter frequency and determine the number of counts need
* to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
/* Get the timer counter frequency and determine the number of counts
* need to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
}
if (period == 0)
{
stm32_tickless_enableint(g_tickless.channel);
stm32_tickless_trigint(g_tickless.channel);
g_tickless.pending = true;
leave_critical_section(flags);
return OK;
}
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
tmrinfo("usec=%llu period=%08llx\n", usec, period);
@ -968,6 +993,24 @@ int up_timer_start(const struct timespec *ts)
stm32_tickless_enableint(g_tickless.channel);
g_tickless.pending = true;
/* Check if the counter already reached or passed the compare target
* while we were configuring the registers.
*/
#ifdef HAVE_32BIT_TICKLESS
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
(uint32_t)period)
#else
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
(uint16_t)period)
#endif
{
/* Target time already elapsed; force the interrupt immediately */
stm32_tickless_trigint(g_tickless.channel);
}
leave_critical_section(flags);
return OK;
}

View file

@ -218,6 +218,15 @@ static inline void stm32_tickless_ackint(int channel)
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
}
/****************************************************************************
* Name: stm32_tickless_trigint
****************************************************************************/
static inline void stm32_tickless_trigint(int channel)
{
stm32_putreg16(STM32_GTIM_EGR_OFFSET, 1 << channel);
}
/****************************************************************************
* Name: stm32_tickless_getint
****************************************************************************/
@ -379,9 +388,9 @@ static int stm32_tickless_handler(int irq, void *context, void *arg)
return OK;
}
#ifdef CONFIG_SCHED_TICKLESS_ALARM
/****************************************************************************
* Name: stm32_get_counter
*
****************************************************************************/
static uint64_t stm32_get_counter(void)
@ -394,6 +403,7 @@ static uint64_t stm32_get_counter(void)
STM32_TIM_GETCOUNTER(g_tickless.tch);
#endif
}
#endif
/****************************************************************************
* Public Functions
@ -877,15 +887,9 @@ int up_timer_cancel(struct timespec *ts)
(unsigned long)period, (unsigned long)count);
#ifndef HAVE_32BIT_TICKLESS
if (count > period)
{
/* Handle rollover */
period += UINT16_MAX;
}
else if (count == period)
if ((int16_t)(period - count) <= 0)
#else
if (count >= period)
if ((int32_t)(period - count) <= 0)
#endif
{
/* No time remaining */
@ -903,8 +907,13 @@ int up_timer_cancel(struct timespec *ts)
* usecs = (ticks * USEC_PER_SEC) / frequency;
*/
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
#ifndef HAVE_32BIT_TICKLESS
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#else
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#endif
/* Return the time remaining in the correct form */
@ -973,18 +982,35 @@ int up_timer_start(const struct timespec *ts)
/* Express the delay in microseconds */
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
{
period = 0;
}
else
{
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
/* Get the timer counter frequency and determine the number of counts need
* to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
/* Get the timer counter frequency and determine the number of counts
* need to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
}
if (period == 0)
{
stm32_tickless_enableint(g_tickless.channel);
stm32_tickless_trigint(g_tickless.channel);
g_tickless.pending = true;
leave_critical_section(flags);
return OK;
}
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
tmrinfo("usec=%llu period=%08llx\n", usec, period);
@ -1012,6 +1038,24 @@ int up_timer_start(const struct timespec *ts)
stm32_tickless_enableint(g_tickless.channel);
g_tickless.pending = true;
/* Check if the counter already reached or passed the compare target
* while we were configuring the registers.
*/
#ifdef HAVE_32BIT_TICKLESS
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
(uint32_t)period)
#else
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
(uint16_t)period)
#endif
{
/* Target time already elapsed; force the interrupt immediately */
stm32_tickless_trigint(g_tickless.channel);
}
leave_critical_section(flags);
return OK;
}

View file

@ -205,6 +205,15 @@ static inline void stm32_tickless_ackint(int channel)
stm32_putreg16(STM32_BTIM_SR_OFFSET, ~(1 << channel));
}
/****************************************************************************
* Name: stm32_tickless_trigint
****************************************************************************/
static inline void stm32_tickless_trigint(int channel)
{
stm32_putreg16(STM32_GTIM_EGR_OFFSET, 1 << channel);
}
/****************************************************************************
* Name: stm32_tickless_getint
****************************************************************************/
@ -366,9 +375,9 @@ static int stm32_tickless_handler(int irq, void *context, void *arg)
return OK;
}
#ifdef CONFIG_SCHED_TICKLESS_ALARM
/****************************************************************************
* Name: stm32_get_counter
*
****************************************************************************/
static uint64_t stm32_get_counter(void)
@ -381,6 +390,7 @@ static uint64_t stm32_get_counter(void)
STM32_TIM_GETCOUNTER(g_tickless.tch);
#endif
}
#endif
/****************************************************************************
* Public Functions
@ -851,15 +861,9 @@ int up_timer_cancel(struct timespec *ts)
(unsigned long)period, (unsigned long)count);
#ifndef HAVE_32BIT_TICKLESS
if (count > period)
{
/* Handle rollover */
period += UINT16_MAX;
}
else if (count == period)
if ((int16_t)(period - count) <= 0)
#else
if (count >= period)
if ((int32_t)(period - count) <= 0)
#endif
{
/* No time remaining */
@ -877,8 +881,13 @@ int up_timer_cancel(struct timespec *ts)
* usecs = (ticks * USEC_PER_SEC) / frequency;
*/
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
#ifndef HAVE_32BIT_TICKLESS
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#else
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#endif
/* Return the time remaining in the correct form */
@ -947,18 +956,35 @@ int up_timer_start(const struct timespec *ts)
/* Express the delay in microseconds */
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
{
period = 0;
}
else
{
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
/* Get the timer counter frequency and determine the number of counts need
* to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
/* Get the timer counter frequency and determine the number of counts
* need to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
}
if (period == 0)
{
stm32_tickless_enableint(g_tickless.channel);
stm32_tickless_trigint(g_tickless.channel);
g_tickless.pending = true;
leave_critical_section(flags);
return OK;
}
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
tmrinfo("usec=%llu period=%08llx\n", usec, period);
@ -986,6 +1012,24 @@ int up_timer_start(const struct timespec *ts)
stm32_tickless_enableint(g_tickless.channel);
g_tickless.pending = true;
/* Check if the counter already reached or passed the compare target
* while we were configuring the registers.
*/
#ifdef HAVE_32BIT_TICKLESS
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
(uint32_t)period)
#else
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
(uint16_t)period)
#endif
{
/* Target time already elapsed; force the interrupt immediately */
stm32_tickless_trigint(g_tickless.channel);
}
leave_critical_section(flags);
return OK;
}

View file

@ -187,6 +187,15 @@ static inline void stm32_tickless_ackint(int channel)
stm32_putreg16(STM32_TIM_SR_OFFSET, ~(1 << channel));
}
/****************************************************************************
* Name: stm32_tickless_trigint
****************************************************************************/
static inline void stm32_tickless_trigint(int channel)
{
stm32_putreg16(STM32_TIM_EGR_OFFSET, 1 << channel);
}
/****************************************************************************
* Name: stm32_tickless_getint
****************************************************************************/
@ -701,15 +710,9 @@ int up_timer_cancel(struct timespec *ts)
(unsigned long)period, (unsigned long)count);
#ifndef HAVE_32BIT_TICKLESS
if (count > period)
{
/* Handle rollover */
period += UINT16_MAX;
}
else if (count == period)
if ((int16_t)(period - count) <= 0)
#else
if (count >= period)
if ((int32_t)(period - count) <= 0)
#endif
{
/* No time remaining */
@ -727,8 +730,13 @@ int up_timer_cancel(struct timespec *ts)
* usecs = (ticks * USEC_PER_SEC) / frequency;
*/
usec = (((uint64_t)(period - count)) * USEC_PER_SEC) /
#ifndef HAVE_32BIT_TICKLESS
usec = (((uint64_t)(uint16_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#else
usec = (((uint64_t)(uint32_t)(period - count)) * USEC_PER_SEC) /
g_tickless.frequency;
#endif
/* Return the time remaining in the correct form */
@ -795,18 +803,35 @@ int up_timer_start(const struct timespec *ts)
/* Express the delay in microseconds */
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
if (ts->tv_sec < 0 || (ts->tv_sec == 0 && ts->tv_nsec <= 0))
{
period = 0;
}
else
{
usec = ts->tv_sec * USEC_PER_SEC +
(ts->tv_nsec / NSEC_PER_USEC);
/* Get the timer counter frequency and determine the number of counts need
* to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
/* Get the timer counter frequency and determine the number of counts
* need to achieve the requested delay.
*
* frequency = ticks / second
* ticks = seconds * frequency
* = (usecs * frequency) / USEC_PER_SEC;
*/
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
}
if (period == 0)
{
stm32_tickless_enableint(g_tickless.channel);
stm32_tickless_trigint(g_tickless.channel);
g_tickless.pending = true;
leave_critical_section(flags);
return OK;
}
period = (usec * (uint64_t)g_tickless.frequency) / USEC_PER_SEC;
count = STM32_TIM_GETCOUNTER(g_tickless.tch);
tmrinfo("usec=%llu period=%08llx\n", usec, period);
@ -814,6 +839,7 @@ int up_timer_start(const struct timespec *ts)
/* Set interval compare value. Rollover is fine,
* channel will trigger on the next period.
*/
#ifdef HAVE_32BIT_TICKLESS
DEBUGASSERT(period <= UINT32_MAX);
g_tickless.period = (uint32_t)(period + count);
@ -833,6 +859,24 @@ int up_timer_start(const struct timespec *ts)
stm32_tickless_enableint(g_tickless.channel);
g_tickless.pending = true;
/* Check if the counter already reached or passed the compare target
* while we were configuring the registers.
*/
#ifdef HAVE_32BIT_TICKLESS
if ((uint32_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - count) >=
(uint32_t)period)
#else
if ((uint16_t)(STM32_TIM_GETCOUNTER(g_tickless.tch) - (uint16_t)count) >=
(uint16_t)period)
#endif
{
/* Target time already elapsed; force the interrupt immediately */
stm32_tickless_trigint(g_tickless.channel);
}
leave_critical_section(flags);
return OK;
}