This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 09423194bf8ba6e3551edd6df0802d3964e200cd Author: Daniel P. Carvalho <[email protected]> AuthorDate: Thu Sep 17 09:39:31 2026 -0300 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 <[email protected]> --- arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c | 81 ++++++++++++++++----- arch/arm/src/stm32f7/stm32_tickless.c | 84 ++++++++++++++++------ arch/arm/src/stm32h7/stm32_tickless.c | 84 ++++++++++++++++------ arch/arm/src/stm32wb/stm32wb_tickless.c | 82 ++++++++++++++++----- 4 files changed, 253 insertions(+), 78 deletions(-) diff --git a/arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c b/arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c index 1e49c2a3cb3..fffce70928c 100644 --- a/arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c +++ b/arch/arm/src/common/stm32/stm32_tickless_m3m4_v1.c @@ -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; } diff --git a/arch/arm/src/stm32f7/stm32_tickless.c b/arch/arm/src/stm32f7/stm32_tickless.c index d348d93cadb..48c2a986055 100644 --- a/arch/arm/src/stm32f7/stm32_tickless.c +++ b/arch/arm/src/stm32f7/stm32_tickless.c @@ -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; } diff --git a/arch/arm/src/stm32h7/stm32_tickless.c b/arch/arm/src/stm32h7/stm32_tickless.c index 7fb140248cb..241bf886cc5 100644 --- a/arch/arm/src/stm32h7/stm32_tickless.c +++ b/arch/arm/src/stm32h7/stm32_tickless.c @@ -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; } diff --git a/arch/arm/src/stm32wb/stm32wb_tickless.c b/arch/arm/src/stm32wb/stm32wb_tickless.c index a8ae84dc54e..801c6cdea9e 100644 --- a/arch/arm/src/stm32wb/stm32wb_tickless.c +++ b/arch/arm/src/stm32wb/stm32wb_tickless.c @@ -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; }
