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 a90e7e20ba413ef5564d1641a5eeac352b71d7db
Author: Max Kriegleder <[email protected]>
AuthorDate: Mon Oct 26 20:20:34 2026 +0100

    arch/arm/nrf91: Fix the tickless RTC alarm and the time across an overflow.
    
    nrf91_tickless_rtc.c is a copy of nrf52_tickless_rtc.c with the same
    three bugs: alarms early by 1.6 % beyond ~8.4 s into the RTC period
    (USEC_TO_COUNTER() divides by 31), a deadline already passed across an
    overflow programmed up to a period (512 s) late, which stalls the
    scheduler, and the time read 512 s early between an overflow and its
    interrupt. Apply the nRF52 changes of this series to it: CC0 programmed
    relative to one COUNTER read and checked modulo 24 bits, and the time
    read in one place that counts a pending overflow.
    
    Tested on an nRF9160-DK, nrf9160-dk:ostest_tickless with a check app
    (RTC0 COMPARE0 counted by DPPI -> TIMER2, the overflow forced with
    TASKS_TRIGOVRFLW). Before: 49019 alarms for 100 sleeps of 20 ms, the
    time across the overflow -511998535 us, and a deadline passed across
    the overflow never fired (timers stalled). After: 100 alarms, +1465 us,
    fired after 91 us. ostest passes 5 runs in a row.
    
    Fixes #13883
    
    Assisted-by: Claude:claude-opus-5-5
    Signed-off-by: Max Kriegleder <[email protected]>
---
 arch/arm/src/nrf91/nrf91_tickless_rtc.c | 142 +++++++++++++++++---------------
 1 file changed, 75 insertions(+), 67 deletions(-)

diff --git a/arch/arm/src/nrf91/nrf91_tickless_rtc.c 
b/arch/arm/src/nrf91/nrf91_tickless_rtc.c
index fa87d5b4144..1f6b4bc8fc0 100644
--- a/arch/arm/src/nrf91/nrf91_tickless_rtc.c
+++ b/arch/arm/src/nrf91/nrf91_tickless_rtc.c
@@ -58,18 +58,13 @@
 #endif
 
 #define NRF91_RTC_PERIOD   (512)
-#define NRF91_RTC_MAX      (0x00ffffff)
-#define NRF91_RTC_MAX_TIME (NRF91_RTC_MAX * 31)
-
-/* Convert uS to timer count for f = 32768Hz, using more precision
- * when possible:
- * (1 / 32768) s ~ 30.51 uS ~ 31 uS
- * 512 * (1 / 32768) s = 0.015625 s = 15625 uS
- * So, instead of always dividing by 31, if t * 512 < (2**32 - 1), we can do:
- * (t * 512) / 15625 ~ t / 30.51
+#define NRF91_RTC_FREQ     (32768)
+
+/* A COMPARE event is guaranteed only for a CC value at least two counts
+ * ahead of the COUNTER (nRF52832_PS_v1.4, p. 245; the nRF9160 RTC alike).
  */
 
-#define USEC_TO_COUNTER(t) (t > 0x7fffff ? (t / 31) : ((t * 512) / 15625))
+#define NRF91_RTC_MIN_DELAY (2)
 
 /* To convert from counter to uS we split the counter into one second worth
  * of counts (32768) and a fractional part we can safely multiply first
@@ -107,84 +102,103 @@ struct nrf91_tickless_dev_s g_tickless_dev;
  * Private Functions
  ****************************************************************************/
 
-static inline void rtc_counter_to_ts(uint32_t counter, struct timespec *now)
+/* Read the COUNTER and the absolute time it stands for.  Called with
+ * interrupts disabled, so an overflow may not have been counted in
+ * 'periods' yet: its OVRFLW event is then still set.  A COUNTER in its
+ * lower half was read after that overflow, one in its upper half before.
+ */
+
+static uint32_t rtc_now(struct timespec *now)
 {
+  const uint32_t rtc_base = NRF91_RTC_GETBASE(g_tickless_dev.rtc);
+  uint32_t periods = g_tickless_dev.periods;
+  uint32_t counter;
   uint32_t usec;
 
+  counter = NRF91_RTC_GETCOUNTER_REG(rtc_base);
+  if (getreg32(rtc_base + NRF91_RTC_EVENTS_OVRFLW_OFFSET) != 0 &&
+      counter <= RTC_COUNTER_MASK / 2)
+    {
+      periods++;
+    }
+
   usec = COUNTER_TO_USEC(counter);
-  now->tv_sec  = usec / USEC_PER_SEC;
+  now->tv_sec  = usec / USEC_PER_SEC + periods * NRF91_RTC_PERIOD;
   now->tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
 
-  now->tv_sec += g_tickless_dev.periods * NRF91_RTC_PERIOD;
+  return counter;
+}
+
+/* Convert a time shorter than one RTC period to counts, rounding up so
+ * that an alarm never fires early.  32768 / 1000000 = 512 / 15625.
+ */
+
+static inline uint32_t rtc_ts_to_counts(const struct timespec *ts)
+{
+  uint32_t usec = (ts->tv_nsec + NSEC_PER_USEC - 1) / NSEC_PER_USEC;
+
+  return (uint32_t)ts->tv_sec * NRF91_RTC_FREQ +
+         (usec * 512 + 15624) / 15625;
 }
 
 static void rtc_prepare_alarm(void)
 {
   struct timespec now;
   struct timespec delta;
-  uint32_t usec;
   uint32_t counter;
-  uint32_t target_counter;
+  uint32_t elapsed;
+  uint32_t counts;
   const uint32_t rtc_base = NRF91_RTC_GETBASE(g_tickless_dev.rtc);
 
-  /* Get current absolute time */
-
-  counter = NRF91_RTC_GETCOUNTER_REG(rtc_base);
-  rtc_counter_to_ts(counter, &now);
-
-  /* Obtain relative time to alarm */
+  /* Time left until the alarm, zero if it has passed */
 
+  counter = rtc_now(&now);
   clock_timespec_subtract(&g_tickless_dev.alarm, &now, &delta);
-  usec = delta.tv_sec * USEC_PER_SEC + delta.tv_nsec / NSEC_PER_USEC;
 
-  /* Check if the alarm is to expire within one RTC period, if so we can set
-   * the CC.
-   */
+  /* An alarm further away is prepared again on a later overflow */
 
-  if (usec < NRF91_RTC_PERIOD * USEC_PER_SEC)
+  if (delta.tv_sec >= NRF91_RTC_PERIOD)
     {
-      /* Obtain absolute number of microseconds of alarm within current
-       * RTC period.
-       */
-
-      usec = (g_tickless_dev.alarm.tv_sec % NRF91_RTC_PERIOD) *
-             USEC_PER_SEC + g_tickless_dev.alarm.tv_nsec / NSEC_PER_USEC;
-
-      /* Compute counter value for that point in time */
-
-      target_counter = USEC_TO_COUNTER(usec);
-
-      /* Enable interrupt. First set CC to distant value to ensure
-       * no match will be generated. Doing things this way we now that
-       * once we write a CC value it should be ready to match.
-       */
+      return;
+    }
 
-      NRF91_RTC_SETCC_REG(rtc_base, 0, counter - 1);
-      NRF91_RTC_ENABLEINT(g_tickless_dev.rtc, NRF91_RTC_EVT_COMPARE0);
+  counts = rtc_ts_to_counts(&delta);
+  if (counts < NRF91_RTC_MIN_DELAY)
+    {
+      counts = NRF91_RTC_MIN_DELAY;
+    }
 
-      /* Set CC to desired value */
+  /* Move CC0 as far ahead as it goes before enabling its interrupt */
 
-      NRF91_RTC_SETCC_REG(rtc_base, 0, target_counter);
+  NRF91_RTC_SETCC_REG(rtc_base, 0, counter - 1);
+  NRF91_RTC_ENABLEINT(g_tickless_dev.rtc, NRF91_RTC_EVT_COMPARE0);
 
-      /* Ensure counter fires: from nrf91832_PS_v1.4 (p. 245) we know that
-       * "If the COUNTER is N, writing N+2 to a CC register is
-       * guaranteed to trigger a COMPARE event at N+2.", so anything
-       * less than that is not guaranteed and it may not ever match.
-       *
-       * To ensure this, we check if CC < N + 2, and if so we set CC = N+2.
-       * We repeat this until this is satisfied (as the counter may change
-       * in between calculations).
-       */
+  /* Set CC0 'counts' after 'counter'.  The COUNTER has kept running since
+   * it was read: if it is now less than NRF91_RTC_MIN_DELAY short of CC0,
+   * move CC0 to the earliest guaranteed match and check again.  Both
+   * distances are taken modulo the 24-bit COUNTER, so this holds across
+   * an overflow.
+   */
 
-      while (NRF91_RTC_GETCC_REG(rtc_base, 0) <
-             NRF91_RTC_GETCOUNTER_REG(rtc_base) + 2)
+  for (; ; )
+    {
+      NRF91_RTC_SETCC_REG(rtc_base, 0,
+                          (counter + counts) & RTC_COUNTER_MASK);
+      elapsed = (NRF91_RTC_GETCOUNTER_REG(rtc_base) - counter) &
+                RTC_COUNTER_MASK;
+      if (elapsed + NRF91_RTC_MIN_DELAY <= counts)
         {
-          NRF91_RTC_SETCC_REG(rtc_base, 0,
-                              NRF91_RTC_GETCOUNTER_REG(rtc_base) + 2);
+          break;
         }
+
+      counts = elapsed + NRF91_RTC_MIN_DELAY;
     }
 }
 
+/****************************************************************************
+ * Name: rtc_cancel_ack
+ ****************************************************************************/
+
 static void rtc_cancel_ack(void)
 {
   irqstate_t flags;
@@ -256,21 +270,18 @@ static int rtc_handler(int irq, void *context, void *arg)
 
 int up_alarm_cancel(struct timespec *ts)
 {
-  uint32_t counter;
   irqstate_t flags;
 
   flags = enter_critical_section();
 
   NRF91_RTC_DISABLEINT(g_tickless_dev.rtc, NRF91_RTC_EVT_COMPARE0);
-  NRF91_RTC_GETCOUNTER(g_tickless_dev.rtc, &counter);
+  rtc_now(ts);
 
   NRF91_RTC_ACKINT(g_tickless_dev.rtc, NRF91_RTC_EVT_COMPARE0);
   g_tickless_dev.alarm_set = false;
 
   leave_critical_section(flags);
 
-  rtc_counter_to_ts(counter, ts);
-
   return OK;
 }
 
@@ -281,6 +292,7 @@ int up_alarm_cancel(struct timespec *ts)
 int up_alarm_start(const struct timespec *ts)
 {
   irqstate_t flags;
+
   flags = enter_critical_section();
 
   /* remember the alarm time */
@@ -301,14 +313,10 @@ int up_alarm_start(const struct timespec *ts)
 
 int up_timer_gettime(struct timespec *ts)
 {
-  uint32_t counter;
   irqstate_t flags;
 
   flags = enter_critical_section();
-
-  NRF91_RTC_GETCOUNTER(g_tickless_dev.rtc, &counter);
-  rtc_counter_to_ts(counter, ts);
-
+  rtc_now(ts);
   leave_critical_section(flags);
 
   return OK;

Reply via email to