This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new b9d3013cc54 sched/clock: Keep the perf overflow state per CPU.
b9d3013cc54 is described below

commit b9d3013cc54d5a59efe2a3a5363164520125e15f
Author: Marco Casaroli <[email protected]>
AuthorDate: Wed Nov 4 14:22:31 2026 +0100

    sched/clock: Keep the perf overflow state per CPU.
    
    With CONFIG_PERF_OVERFLOW_CORRECTION, perf_gettime() compares the counter
    with one global "last" value.  On SMP each CPU has its own counter (for
    example PMCCNTR on ARMv7-A), and these counters do not hold the same
    value.  A read on a CPU with a lower counter than the last read on another
    CPU counts as an overflow.  Each such read adds 2^32 cycles to all later
    values: on qemu-armv7a:smp a thread that runs 500 ms shows 4795 ms of CPU
    time.
    
    Keep "last" and "overflow" for each CPU.  The state belongs to one CPU, so
    disabling the local interrupts is sufficient and the spinlock goes away.
    This also removes the wd_start() call with a spinlock held.
    
    The watchdog runs on one CPU only, so it cannot read the counter of another
    CPU.  Use a watchdog for each CPU that sends a SMP call to the CPU of its
    state.  A CPU that runs one thread for a long time then does not miss an
    overflow.
    
    With SMP off, the array has one element and the size of the code does not
    change: 304 bytes of text and 48 of bss on qemu-armv7a:nsh, before and 
after.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 sched/clock/clock_perf.c | 40 ++++++++++++++++++++++++++++++++++------
 1 file changed, 34 insertions(+), 6 deletions(-)

diff --git a/sched/clock/clock_perf.c b/sched/clock/clock_perf.c
index 8d0a6347073..711f0c9d9e5 100644
--- a/sched/clock/clock_perf.c
+++ b/sched/clock/clock_perf.c
@@ -28,7 +28,8 @@
 
 #include <nuttx/arch.h>
 #include <nuttx/clock.h>
-#include <nuttx/spinlock.h>
+#include <nuttx/irq.h>
+#include <nuttx/sched.h>
 #include <nuttx/wdog.h>
 
 #ifndef CONFIG_ARCH_PERF_EVENTS_USER_ACCESS
@@ -46,7 +47,9 @@
 struct perf_s
 {
   struct wdog_s wdog;
-  spinlock_t lock;
+#ifdef CONFIG_SMP
+  struct smp_call_data_s call;
+#endif
   unsigned long last;
   clock_t overflow;
   clock_t timeout;
@@ -56,12 +59,26 @@ struct perf_s
  * Private Data
  ****************************************************************************/
 
-static struct perf_s g_perf;
+/* Each CPU has its own counter, so each CPU has its own overflow state */
+
+static struct perf_s g_perf[CONFIG_SMP_NCPUS];
 
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
 
+/****************************************************************************
+ * perf_update_cpu
+ ****************************************************************************/
+
+#ifdef CONFIG_SMP
+static int perf_update_cpu(FAR void *arg)
+{
+  perf_gettime();
+  return OK;
+}
+#endif
+
 /****************************************************************************
  * perf_update
  ****************************************************************************/
@@ -70,7 +87,14 @@ static void perf_update(wdparm_t arg)
 {
   FAR struct perf_s *perf = (FAR struct perf_s *)arg;
 
+#ifdef CONFIG_SMP
+  /* The watchdog can run on any CPU: read the counter of the state's CPU */
+
+  nxsched_smp_call_single_async(perf - g_perf, &perf->call);
+#else
   perf_gettime();
+#endif
+
   wd_start_next(&perf->wdog, perf->timeout, perf_update, arg);
 }
 
@@ -84,8 +108,8 @@ static void perf_update(wdparm_t arg)
 
 clock_t perf_gettime(void)
 {
-  FAR struct perf_s *perf = &g_perf;
-  irqstate_t flags = spin_lock_irqsave(&perf->lock);
+  irqstate_t flags = up_irq_save();
+  FAR struct perf_s *perf = &g_perf[this_cpu()];
   clock_t now = up_perf_gettime();
   clock_t result;
 
@@ -95,6 +119,10 @@ clock_t perf_gettime(void)
         ((clock_t)1 << (CONFIG_ARCH_PERF_COUNT_BITWIDTH - 1)) *
         TICK_PER_SEC / up_perf_getfreq();
 
+#ifdef CONFIG_SMP
+      nxsched_smp_call_init(&perf->call, perf_update_cpu, NULL);
+#endif
+
       /* Periodic check for overflow */
 
       wd_start(&perf->wdog, perf->timeout, perf_update, (wdparm_t)perf);
@@ -106,7 +134,7 @@ clock_t perf_gettime(void)
 
   perf->last = now;
   result = now | (perf->overflow << CONFIG_ARCH_PERF_COUNT_BITWIDTH);
-  spin_unlock_irqrestore(&perf->lock, flags);
+  up_irq_restore(flags);
   return result;
 }
 

Reply via email to