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;
 }

Reply via email to