You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2024/06/29 14:37:56 UTC

(nuttx) 02/03: arch/intel64: add SMP support

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

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

commit 8220b169f371fc7d82a38be52e5950be06c27e83
Author: p-szafonimateusz <p-...@xiaomi.com>
AuthorDate: Wed Apr 3 11:53:46 2024 +0200

    arch/intel64: add SMP support
    
    modified various intel64 files to support SMP
    
    Signed-off-by: p-szafonimateusz <p-...@xiaomi.com>
---
 arch/x86_64/include/irq.h                          |  25 ++--
 arch/x86_64/src/common/x86_64_exit.c               |  10 ++
 arch/x86_64/src/common/x86_64_switchcontext.c      |  13 ++
 arch/x86_64/src/intel64/intel64_handlers.c         |   4 +
 .../x86_64/src/intel64/intel64_schedulesigaction.c | 158 ++++++++++++++++++++-
 arch/x86_64/src/intel64/intel64_sigdeliver.c       |  60 ++++++--
 6 files changed, 250 insertions(+), 20 deletions(-)

diff --git a/arch/x86_64/include/irq.h b/arch/x86_64/include/irq.h
index 8cddf2a3e3..dfbbe15c35 100644
--- a/arch/x86_64/include/irq.h
+++ b/arch/x86_64/include/irq.h
@@ -45,14 +45,6 @@
 #  include <arch/intel64/irq.h>
 #endif
 
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
-
-/****************************************************************************
- * Public Types
- ****************************************************************************/
-
 /****************************************************************************
  * Public Data
  ****************************************************************************/
@@ -103,7 +95,22 @@ extern "C"
  *
  ****************************************************************************/
 
-#define up_cpu_index() (0)
+#ifdef CONFIG_SMP
+static inline_function int up_cpu_index(void)
+{
+  unsigned long cpu;
+
+  asm volatile(
+    "\tmovq %%gs:(%c1), %0\n"
+    : "=rm" (cpu)
+    : "i" (offsetof(struct intel64_cpu_s, id))
+    :);
+
+  return cpu;
+}
+#else
+#  define up_cpu_index() (0)
+#endif
 
 /****************************************************************************
  * Inline functions
diff --git a/arch/x86_64/src/common/x86_64_exit.c b/arch/x86_64/src/common/x86_64_exit.c
index 43f0dcc66d..867937ec3c 100644
--- a/arch/x86_64/src/common/x86_64_exit.c
+++ b/arch/x86_64/src/common/x86_64_exit.c
@@ -92,7 +92,17 @@ void up_exit(int status)
   addrenv_switch(tcb);
 #endif
 
+  /* Restore the cpu lock */
+
+  restore_critical_section();
+
   /* Then switch contexts */
 
   x86_64_fullcontextrestore(tcb->xcp.regs);
+
+  /* x86_64_fullcontextrestore() should not return but could if the software
+   * interrupts are disabled.
+   */
+
+  PANIC();
 }
diff --git a/arch/x86_64/src/common/x86_64_switchcontext.c b/arch/x86_64/src/common/x86_64_switchcontext.c
index 3397d2d493..c87a71339c 100644
--- a/arch/x86_64/src/common/x86_64_switchcontext.c
+++ b/arch/x86_64/src/common/x86_64_switchcontext.c
@@ -57,6 +57,8 @@
 
 void up_switch_context(struct tcb_s *tcb, struct tcb_s *rtcb)
 {
+  int cpu;
+
   /* Update scheduler parameters */
 
   nxsched_suspend_scheduler(rtcb);
@@ -107,6 +109,17 @@ void up_switch_context(struct tcb_s *tcb, struct tcb_s *rtcb)
 
       nxsched_resume_scheduler(tcb);
 
+      /* Restore the cpu lock */
+
+      restore_critical_section();
+
+      /* Record the new "running" task.  g_running_tasks[] is only used by
+       * assertion logic for reporting crashes.
+       */
+
+      cpu = this_cpu();
+      g_running_tasks[cpu] = current_task(cpu);
+
       /* Then switch contexts */
 
       x86_64_fullcontextrestore(tcb->xcp.regs);
diff --git a/arch/x86_64/src/intel64/intel64_handlers.c b/arch/x86_64/src/intel64/intel64_handlers.c
index 8bf5843d6b..9bbf2d2e60 100644
--- a/arch/x86_64/src/intel64/intel64_handlers.c
+++ b/arch/x86_64/src/intel64/intel64_handlers.c
@@ -100,6 +100,10 @@ static uint64_t *common_handler(int irq, uint64_t *regs)
        */
 
       g_running_tasks[this_cpu()] = this_task();
+
+      /* Restore the cpu lock */
+
+      restore_critical_section();
     }
 
   /* If a context switch occurred while processing the interrupt then
diff --git a/arch/x86_64/src/intel64/intel64_schedulesigaction.c b/arch/x86_64/src/intel64/intel64_schedulesigaction.c
index 28f51334eb..589d955fce 100644
--- a/arch/x86_64/src/intel64/intel64_schedulesigaction.c
+++ b/arch/x86_64/src/intel64/intel64_schedulesigaction.c
@@ -70,6 +70,7 @@
  *
  ****************************************************************************/
 
+#ifndef CONFIG_SMP
 void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
 {
   sinfo("tcb=%p sigdeliver=%p\n", tcb, sigdeliver);
@@ -109,7 +110,7 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
            * Hmmm... there looks like a latent bug here: The following logic
            * would fail in the strange case where we are in an interrupt
            * handler, the thread is signalling itself, but a context switch
-           * to another task has occurred so that g_current_regs does not
+           * to another task has occurred so that CURRENT_REGS does not
            * refer to the thread of this_task()!
            */
 
@@ -121,7 +122,7 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
                */
 
               tcb->xcp.saved_rip         = up_current_regs()[REG_RIP];
-              tcb->xcp.saved_rsp         = tcb->xcp.regs[REG_RSP];
+              tcb->xcp.saved_rsp         = up_current_regs()[REG_RSP];
               tcb->xcp.saved_rflags      = up_current_regs()[REG_RFLAGS];
 
               /* Then set up to vector to the trampoline with interrupts
@@ -165,3 +166,156 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
         }
     }
 }
+#else  /* !CONFIG_SMP */
+void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
+{
+  int cpu;
+  int me;
+
+  sinfo("tcb=0x%p sigdeliver=0x%p\n", tcb, sigdeliver);
+
+  /* Refuse to handle nested signal actions */
+
+  if (tcb->xcp.sigdeliver == NULL)
+    {
+      tcb->xcp.sigdeliver = sigdeliver;
+
+      /* First, handle some special cases when the signal is being delivered
+       * to task that is currently executing on any CPU.
+       */
+
+      sinfo("rtcb=0x%p CURRENT_REGS=0x%p\n", this_task(),
+            up_current_regs());
+
+      if (tcb->task_state == TSTATE_TASK_RUNNING)
+        {
+          me  = this_cpu();
+          cpu = tcb->cpu;
+
+          /* CASE 1:  We are not in an interrupt handler and a task is
+           * signaling itself for some reason.
+           */
+
+          if (cpu == me && !up_current_regs())
+            {
+              /* In this case just deliver the signal now.
+               * REVISIT:  Signal handler will run in a critical section!
+               */
+
+              sigdeliver(tcb);
+              tcb->xcp.sigdeliver = NULL;
+            }
+
+          /* CASE 2:  The task that needs to receive the signal is running.
+           * This could happen if the task is running on another CPU OR if
+           * we are in an interrupt handler and the task is running on this
+           * CPU.  In the former case, we will have to PAUSE the other CPU
+           * first.  But in either case, we will have to modify the return
+           * state as well as the state in the TCB.
+           */
+
+          else
+            {
+              /* If we signaling a task running on the other CPU, we have
+               * to PAUSE the other CPU.
+               */
+
+              if (cpu != me)
+                {
+                  /* Pause the CPU */
+
+                  up_cpu_pause(cpu);
+
+                  /* Wait while the pause request is pending */
+
+                  while (up_cpu_pausereq(cpu))
+                    {
+                    }
+
+                  /* Now tcb on the other CPU can be accessed safely */
+
+                  /* Copy tcb->xcp.regs to tcp.xcp.saved. These will be
+                   * restored by the signal trampoline after the signal has
+                   * been delivered.
+                   */
+
+                  tcb->xcp.saved_rip        = tcb->xcp.regs[REG_RIP];
+                  tcb->xcp.saved_rsp        = tcb->xcp.regs[REG_RSP];
+                  tcb->xcp.saved_rflags     = tcb->xcp.regs[REG_RFLAGS];
+
+                  /* Then set up to vector to the trampoline with interrupts
+                   * disabled
+                   */
+
+                  tcb->xcp.regs[REG_RIP]    = (uint64_t)x86_64_sigdeliver;
+                  tcb->xcp.regs[REG_RFLAGS] = 0;
+                }
+              else
+                {
+                  /* tcb is running on the same CPU */
+
+                  /* Save the return lr and cpsr and one scratch register.
+                   * These will be restored by the signal trampoline after
+                   * the signals have been delivered.
+                   */
+
+                  tcb->xcp.saved_rip         = up_current_regs()[REG_RIP];
+                  tcb->xcp.saved_rsp         = up_current_regs()[REG_RSP];
+                  tcb->xcp.saved_rflags      = up_current_regs()[REG_RFLAGS];
+
+                  /* Then set up to vector to the trampoline with interrupts
+                   * disabled
+                   */
+
+                  up_current_regs()[REG_RIP]  = (uint64_t)x86_64_sigdeliver;
+                  up_current_regs()[REG_RFLAGS] = 0;
+
+                  /* And make sure that the saved context in the TCB
+                   * is the same as the interrupt return context.
+                   */
+
+                  x86_64_savestate(tcb->xcp.regs);
+                }
+
+              /* NOTE: If the task runs on another CPU(cpu), adjusting
+               * global IRQ controls will be done in the pause handler
+               * on the CPU(cpu) by taking a critical section.
+               * If the task is scheduled on this CPU(me), do nothing
+               * because this CPU already took a critical section
+               */
+
+              /* RESUME the other CPU if it was PAUSED */
+
+              if (cpu != me)
+                {
+                  up_cpu_resume(cpu);
+                }
+            }
+        }
+
+      /* Otherwise, we are (1) signaling a task is not running from an
+       * interrupt handler or (2) we are not in an interrupt handler and the
+       * running task is signaling some other non-running task.
+       */
+
+      else
+        {
+          /* Save the return lr and cpsr and one scratch register
+           * These will be restored by the signal trampoline after
+           * the signals have been delivered.
+           */
+
+          tcb->xcp.saved_rip        = tcb->xcp.regs[REG_RIP];
+          tcb->xcp.saved_rsp        = tcb->xcp.regs[REG_RSP];
+          tcb->xcp.saved_rflags     = tcb->xcp.regs[REG_RFLAGS];
+
+          /* Then set up to vector to the trampoline with interrupts
+           * disabled
+           */
+
+          tcb->xcp.regs[REG_RIP]    = (uint64_t)x86_64_sigdeliver;
+          tcb->xcp.regs[REG_RFLAGS] = 0;
+        }
+    }
+}
+#endif /* CONFIG_SMP */
diff --git a/arch/x86_64/src/intel64/intel64_sigdeliver.c b/arch/x86_64/src/intel64/intel64_sigdeliver.c
index 0369480a4a..819d689c87 100644
--- a/arch/x86_64/src/intel64/intel64_sigdeliver.c
+++ b/arch/x86_64/src/intel64/intel64_sigdeliver.c
@@ -54,10 +54,24 @@
 void x86_64_sigdeliver(void)
 {
   struct tcb_s *rtcb = this_task();
-  sig_deliver_t sighandler;
   uint64_t regs_area[XCPTCONTEXT_REGS + 2];
   uint64_t *regs;
 
+#ifdef CONFIG_SMP
+  /* In the SMP case, we must terminate the critical section while the signal
+   * handler executes, but we also need to restore the irqcount when the
+   * we resume the main thread of the task.
+   */
+
+  int16_t saved_irqcount;
+#endif
+
+  board_autoled_on(LED_SIGNAL);
+
+  sinfo("rtcb=%p sigdeliver=%p sigpendactionq.head=%p\n",
+        rtcb, rtcb->xcp.sigdeliver, rtcb->sigpendactionq.head);
+  DEBUGASSERT(rtcb->xcp.sigdeliver != NULL);
+
   /* Align regs to 16 byte boundary for SSE instructions. */
 
   regs = (uint64_t *)(((uint64_t)(regs_area) + 15) & (~(uint64_t)15));
@@ -70,17 +84,24 @@ void x86_64_sigdeliver(void)
 
   x86_64_copystate(regs, rtcb->xcp.regs);
 
-  /* grab on a copy of the signal hander function pointer before any
-   * possibility to be switched out.
+#ifdef CONFIG_SMP
+  /* In the SMP case, up_schedule_sigaction(0) will have incremented
+   * 'irqcount' in order to force us into a critical section.  Save the
+   * pre-incremented irqcount.
    */
 
-  ASSERT(rtcb->xcp.sigdeliver != NULL);
-  sighandler = rtcb->xcp.sigdeliver;
+  saved_irqcount = rtcb->irqcount;
+  DEBUGASSERT(saved_irqcount >= 0);
 
-  board_autoled_on(LED_SIGNAL);
+  /* Now we need call leave_critical_section() repeatedly to get the irqcount
+   * to zero, freeing all global spinlocks that enforce the critical section.
+   */
 
-  sinfo("rtcb=%p sigdeliver=%p sigpendactionq.head=%p\n",
-        rtcb, rtcb->xcp.sigdeliver, rtcb->sigpendactionq.head);
+  while (rtcb->irqcount > 0)
+    {
+      leave_critical_section((uint8_t)regs[REG_RFLAGS]);
+    }
+#endif /* CONFIG_SMP */
 
 #ifndef CONFIG_SUPPRESS_INTERRUPTS
   /* Then make sure that interrupts are enabled.  Signal handlers must always
@@ -92,7 +113,7 @@ void x86_64_sigdeliver(void)
 
   /* Deliver the signals */
 
-  sighandler(rtcb);
+  ((sig_deliver_t)rtcb->xcp.sigdeliver)(rtcb);
 
   /* Output any debug messages BEFORE restoring errno (because they may
    * alter errno), then disable interrupts again and restore the original
@@ -100,7 +121,12 @@ void x86_64_sigdeliver(void)
    */
 
   sinfo("Resuming\n");
+
+#ifdef CONFIG_SMP
+  enter_critical_section();
+#else
   up_irq_save();
+#endif
 
   /* Modify the saved return state with the actual saved values in the
    * TCB.  This depends on the fact that nested signal handling is
@@ -117,6 +143,22 @@ void x86_64_sigdeliver(void)
   regs[REG_RFLAGS]     = rtcb->xcp.saved_rflags;
   rtcb->xcp.sigdeliver = NULL;  /* Allows next handler to be scheduled */
 
+#ifdef CONFIG_SMP
+  /* Restore the saved 'irqcount' and recover the critical section
+   * spinlocks.
+   *
+   * REVISIT:  irqcount should be one from the above call to
+   * enter_critical_section().  Could the saved_irqcount be zero?  That
+   * would be a problem.
+   */
+
+  DEBUGASSERT(rtcb->irqcount == 1);
+  while (rtcb->irqcount < saved_irqcount)
+    {
+      enter_critical_section();
+    }
+#endif
+
   /* Then restore the correct state for this thread of execution. */
 
   board_autoled_off(LED_SIGNAL);