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 5866c4e3af66c0167b0996a527678fffbfa0711e
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Aug 10 15:17:24 2026 +0200

    xtensa: Support BUILD_KERNEL.
    
    Add what a kernel build needs on Xtensa:  a crt0 for a user process, the
    kernel stack allocation that a system call switches to, the syscall entry 
and
    return path for an unprivileged caller, and the initial register state that
    starts a user task at EL0 with its save area on the kernel stack.
    
    On the ESP32-S3 the arch code that runs while the flash mapping is in flux
    moves to IRAM, and the kernel heap is placed above the user .bss so that
    up_allocate_kheap() and the user address environment do not overlap.
    
    Assisted-by: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Marco Casaroli <[email protected]>
---
 arch/xtensa/include/irq.h                      |  17 +++
 arch/xtensa/src/Makefile                       |  14 ++-
 arch/xtensa/src/common/Make.defs               |   6 +-
 arch/xtensa/src/common/crt0.c                  |  48 ++++++++
 arch/xtensa/src/common/xtensa_addrenv_kstack.c | 129 ++++++++++++++++++++++
 arch/xtensa/src/common/xtensa_initialstate.c   |  13 +++
 arch/xtensa/src/common/xtensa_swint.c          | 145 +++++++++++++++++++++++++
 arch/xtensa/src/lx7/Toolchain.defs             |  11 +-
 8 files changed, 379 insertions(+), 4 deletions(-)

diff --git a/arch/xtensa/include/irq.h b/arch/xtensa/include/irq.h
index 5b2ee5551da..d2005dcbcad 100644
--- a/arch/xtensa/include/irq.h
+++ b/arch/xtensa/include/irq.h
@@ -216,6 +216,23 @@ struct xcptcontext
 
   uint32_t *regs;
 
+#ifdef CONFIG_ARCH_KERNEL_STACK
+  /* In a kernel build the kernel cannot run on the stack of the process it
+   * is working for.  That stack lives in a cache-MMU window which
+   * up_addrenv_select() reprograms, so it would move out from under the
+   * kernel the moment it touched another process's address environment --
+   * taking the exception frame and every spilled register window with it.
+   * Each thread therefore gets a small stack of its own in kernel memory,
+   * which no address environment change can disturb.
+   */
+
+  uint32_t *kstack;    /* Allocated base of the kernel stack */
+  uint32_t *ktopstk;   /* Top of the kernel stack (initial stack pointer) */
+  uint32_t *ustkptr;   /* Saved user stack pointer, while in a system call */
+  uint32_t *kstkptr;   /* Saved kernel stack pointer, while a user signal
+                        * handler runs on the user stack */
+#endif
+
 #ifdef CONFIG_LIB_SYSCALL
   /* The exception frame of the system call currently in progress, i.e. the
    * caller's register context as the vector saved it.  A system call body
diff --git a/arch/xtensa/src/Makefile b/arch/xtensa/src/Makefile
index 31425e38fa3..522e622be0c 100644
--- a/arch/xtensa/src/Makefile
+++ b/arch/xtensa/src/Makefile
@@ -199,10 +199,20 @@ ifneq ($(CONFIG_WINDOWS_NATIVE),y)
 endif
 
 # This is part of the top-level export target
+#
+# A kernel build links its user programs outside this tree, against the
+# export package, so crt0 has to travel with it:  apps/import expects to
+# find it as startup/crt0.o.
+
+ifeq ($(CONFIG_BUILD_KERNEL),y)
+EXPORT_STARTUP_OBJS = $(STARTUP_OBJS) $(STARTUP_ELF_OBJS)
+else
+EXPORT_STARTUP_OBJS = $(STARTUP_OBJS)
+endif
 
-export_startup: $(STARTUP_OBJS)
+export_startup: $(EXPORT_STARTUP_OBJS)
        $(Q) if [ -d "$(EXPORT_DIR)/startup" ]; then \
-               cp -f $(STARTUP_OBJS) "$(EXPORT_DIR)/startup"; \
+               cp -f $(EXPORT_STARTUP_OBJS) "$(EXPORT_DIR)/startup"; \
         else \
                echo "$(EXPORT_DIR)/startup does not exist"; \
                exit 1; \
diff --git a/arch/xtensa/src/common/Make.defs b/arch/xtensa/src/common/Make.defs
index e91fc78b5e6..82be3de9de4 100644
--- a/arch/xtensa/src/common/Make.defs
+++ b/arch/xtensa/src/common/Make.defs
@@ -86,7 +86,11 @@ ifeq ($(CONFIG_XTENSA_SEMIHOSTING_HOSTFS),y)
   CMN_CSRCS += xtensa_hostfs.c
 endif
 
-ifeq ($(CONFIG_BUILD_PROTECTED),y)
+ifeq ($(CONFIG_ARCH_KERNEL_STACK),y)
+  CMN_CSRCS += xtensa_addrenv_kstack.c
+endif
+
+ifneq ($(CONFIG_BUILD_FLAT),y)
   CMN_UASRCS += xtensa_signal_handler.S
   CMN_ASRCS  += xtensa_dispatch_syscall.S
   CMN_CSRCS  += xtensa_task_start.c xtensa_pthread_start.c
diff --git a/arch/xtensa/src/common/crt0.c b/arch/xtensa/src/common/crt0.c
index 89b21b78db0..5dd1458e982 100644
--- a/arch/xtensa/src/common/crt0.c
+++ b/arch/xtensa/src/common/crt0.c
@@ -25,6 +25,7 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
+#include <nuttx/macro.h>
 
 #include <sys/types.h>
 #include <stdlib.h>
@@ -55,6 +56,53 @@ int main(int argc, char *argv[]);
  * Private Functions
  ****************************************************************************/
 
+#ifdef CONFIG_BUILD_KERNEL
+
+/****************************************************************************
+ * Name: sig_trampoline
+ *
+ * Description:
+ *   The user-space signal handler trampoline.  A kernel build cannot reach
+ *   the one in xtensa_signal_handler.S -- that lives in libarch, which user
+ *   programs do not link -- so it is carried here in crt0 instead, and
+ *   _start() publishes it to the kernel through ARCH_DATA_RESERVE.  The
+ *   kernel enters it from the SYS_signal_handler case of xtensa_swint().
+ *
+ *   Written as file-scope assembly rather than as a naked function because
+ *   GCC does not implement the naked attribute on Xtensa: it would emit a
+ *   window-rotating prologue and quietly invalidate the register assignments
+ *   below.
+ *
+ * Input Parameters:
+ *   a2 = sighand, the user-space signal handling function
+ *   a3, a4, a5 = signo, info and ucontext, its arguments
+ *
+ * Returned Value:
+ *   None.  This function does not return in the normal sense; it returns
+ *   via the SYS_signal_handler_return syscall.
+ *
+ ****************************************************************************/
+
+__asm__
+(
+  "  .text\n"
+  "  .global sig_trampoline\n"
+  "  .type   sig_trampoline, @function\n"
+  "  .align  4\n"
+  "sig_trampoline:\n"
+  "  mov    a6, a3\n"          /* Move signo into the callee's a2 */
+  "  mov    a7, a4\n"          /* Move info into the callee's a3 */
+  "  mov    a8, a5\n"          /* Move ucontext into the callee's a4 */
+  "  callx4 a2\n"              /* Call the signal handler */
+  "  movi   a2, " STRINGIFY(SYS_signal_handler_return) "\n"
+  "  syscall\n"                /* Will not return */
+  "  .size sig_trampoline, .-sig_trampoline\n"
+);
+
+void sig_trampoline(void);
+
+#endif /* CONFIG_BUILD_KERNEL */
+
 #ifdef CONFIG_HAVE_CXXINITIALIZE
 
 /****************************************************************************
diff --git a/arch/xtensa/src/common/xtensa_addrenv_kstack.c 
b/arch/xtensa/src/common/xtensa_addrenv_kstack.c
new file mode 100644
index 00000000000..57bb7495880
--- /dev/null
+++ b/arch/xtensa/src/common/xtensa_addrenv_kstack.c
@@ -0,0 +1,129 @@
+/****************************************************************************
+ * arch/xtensa/src/common/xtensa_addrenv_kstack.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <debug.h>
+#include <errno.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/sched.h>
+
+#include "xtensa.h"
+
+#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_KERNEL_STACK)
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The Xtensa windowed ABI requires 16-byte stack alignment */
+
+#define KSTACK_ALIGNMENT  16
+#define KSTACK_ALIGN_DOWN(a) ((a) & ~(KSTACK_ALIGNMENT - 1))
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: up_addrenv_kstackalloc
+ *
+ * Description:
+ *   This function is called when a new thread is created to allocate the
+ *   new thread's kernel stack.  This function may be called for certain
+ *   terminating threads which have no kernel stack.  It must be tolerant of
+ *   that case.
+ *
+ *   The stack comes from the kernel heap, which lives in internal SRAM and
+ *   is mapped identically no matter which address environment is selected.
+ *   That is the whole point of it:  the kernel needs somewhere to keep the
+ *   exception frame and its spilled register windows that does not move when
+ *   up_addrenv_select() reprograms the user cache-MMU windows.
+ *
+ * Input Parameters:
+ *   tcb - The TCB of the thread that requires the kernel stack.
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int up_addrenv_kstackalloc(struct tcb_s *tcb)
+{
+  DEBUGASSERT(tcb && tcb->xcp.kstack == NULL);
+
+  tcb->xcp.kstack = kmm_memalign(KSTACK_ALIGNMENT, ARCH_KERNEL_STACKSIZE);
+  if (tcb->xcp.kstack == NULL)
+    {
+      berr("ERROR: Failed to allocate the kernel stack\n");
+      return -ENOMEM;
+    }
+
+  /* Xtensa stacks grow down and must stay aligned, so the usable top is the
+   * far end of the allocation.
+   */
+
+  tcb->xcp.ktopstk = (uint32_t *)
+    KSTACK_ALIGN_DOWN((uintptr_t)tcb->xcp.kstack + ARCH_KERNEL_STACKSIZE);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: up_addrenv_kstackfree
+ *
+ * Description:
+ *   This function is called when any thread exits.  This function frees
+ *   the kernel stack.
+ *
+ * Input Parameters:
+ *   tcb - The TCB of the thread that no longer requires the kernel stack.
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int up_addrenv_kstackfree(struct tcb_s *tcb)
+{
+  DEBUGASSERT(tcb);
+
+  /* Does the exiting thread have a kernel stack? */
+
+  if (tcb->xcp.kstack != NULL)
+    {
+      kmm_free(tcb->xcp.kstack);
+      tcb->xcp.kstack  = NULL;
+      tcb->xcp.ktopstk = NULL;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_ARCH_ADDRENV && CONFIG_ARCH_KERNEL_STACK */
diff --git a/arch/xtensa/src/common/xtensa_initialstate.c 
b/arch/xtensa/src/common/xtensa_initialstate.c
index 76549e33b1a..b64989f0ba5 100644
--- a/arch/xtensa/src/common/xtensa_initialstate.c
+++ b/arch/xtensa/src/common/xtensa_initialstate.c
@@ -81,11 +81,24 @@ void up_initial_state(struct tcb_s *tcb)
   const uintptr_t base = ALIGN_UP((uintptr_t)&_rodata_reserved_align,
                                  TCB_SIZE);
 #endif
+#ifdef CONFIG_ARCH_KERNEL_STACK
+  /* The kernel stack is allocated before the thread's initial state is set
+   * up, so hold on to it across the wipe below.
+   */
+
+  uint32_t *kstack  = xcp->kstack;
+  uint32_t *ktopstk = xcp->ktopstk;
+#endif
 
   /* Initialize the initial exception register context structure */
 
   memset(xcp, 0, sizeof(struct xcptcontext));
 
+#ifdef CONFIG_ARCH_KERNEL_STACK
+  xcp->kstack  = kstack;
+  xcp->ktopstk = ktopstk;
+#endif
+
   /* Initialize the idle thread stack */
 
   if (tcb->pid == IDLE_PROCESS_ID)
diff --git a/arch/xtensa/src/common/xtensa_swint.c 
b/arch/xtensa/src/common/xtensa_swint.c
index 112a7f91a4a..97bbc81ba36 100644
--- a/arch/xtensa/src/common/xtensa_swint.c
+++ b/arch/xtensa/src/common/xtensa_swint.c
@@ -32,6 +32,7 @@
 
 #include <arch/xtensa/xtensa_specregs.h>
 #include <nuttx/arch.h>
+#include <nuttx/addrenv.h>
 #include <sys/syscall.h>
 
 #include "sched/sched.h"
@@ -39,6 +40,19 @@
 #include "signal/signal.h"
 #include "xtensa.h"
 
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifdef CONFIG_ARCH_KERNEL_STACK
+/* A stack pointer is 16-byte aligned, and the windowed ABI reserves the
+ * 16 bytes below one as the base save area of the frame that owns it.
+ */
+
+#  define SIGTRAMP_STACK_ALIGN  16
+#  define SIGTRAMP_SAVE_AREA    16
+#endif
+
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -102,6 +116,24 @@ int xtensa_swint(int irq, void *context, void *arg)
       case SYS_restore_context:
       case SYS_switch_context:
         {
+#ifdef CONFIG_ARCH_ADDRENV
+          /* Close down the outgoing task's address environment and
+           * instantiate the incoming one.  up_switch_context() is a
+           * SYS_switch_context call on this architecture, so this is the
+           * path every *voluntary* context switch takes -- without it a task
+           * resumed here keeps running against whatever address environment
+           * happened to be resident, which on the ESP32-S3 means the
+           * cache-MMU windows still point at another process's pages.
+           *
+           * addrenv_switch() may change this_task(), because dropping an
+           * address environment can post to the high-priority work queue, so
+           * re-read the TCB afterwards -- as arm_syscall.c does.
+           */
+
+          addrenv_switch(tcb);
+          tcb = this_task();
+#endif
+
           restore_critical_section(tcb, this_cpu());
 #ifdef CONFIG_DEBUG_SYSCALL_INFO
           svcinfo("SYSCALL Return: Context switch!\n");
@@ -151,6 +183,19 @@ int xtensa_swint(int irq, void *context, void *arg)
 
           rtcb->xcp.nsyscalls = index;
 
+#ifdef CONFIG_ARCH_KERNEL_STACK
+          /* Leaving the outermost system call: hand the thread back its own
+           * stack, which it has not touched while the kernel borrowed its
+           * context.
+           */
+
+          if (index == 0 && rtcb->xcp.ustkptr != NULL)
+            {
+              regs[REG_A1]      = (uintptr_t)rtcb->xcp.ustkptr;
+              rtcb->xcp.ustkptr = NULL;
+            }
+#endif
+
           /* Handle any signal actions that were deferred while processing
            * the system call.
            */
@@ -275,7 +320,11 @@ int xtensa_swint(int irq, void *context, void *arg)
            * unprivileged mode.
            */
 
+#if defined(CONFIG_BUILD_PROTECTED)
           regs[REG_PC]        = (uintptr_t)USERSPACE->signal_handler;
+#else
+          regs[REG_PC]        = (uintptr_t)ARCH_DATA_RESERVE->ar_sigtramp;
+#endif
 
           xtensa_lowerprivilege(regs);        /* User mode */
 
@@ -287,6 +336,55 @@ int xtensa_swint(int irq, void *context, void *arg)
           regs[REG_A3]        = regs[REG_A4]; /* signal */
           regs[REG_A4]        = regs[REG_A5]; /* info */
           regs[REG_A5]        = regs[REG_A6]; /* ucontext */
+
+#ifdef CONFIG_ARCH_KERNEL_STACK
+          /* The handler runs in user mode, so it has to run on the user
+           * stack.  Signal dispatch always reaches here on the thread's
+           * kernel stack -- up_schedule_sigaction() builds the dispatch
+           * context below the interrupted one -- so put that stack pointer
+           * aside and hand the thread its own stack back for the duration.
+           *
+           * Having a kernel stack at all is what says this is a user
+           * process.  Testing xcp.ustkptr instead would be wrong: that
+           * holds the user stack pointer only while a system call is in
+           * progress, so a signal caught in user code would leave the
+           * handler running on the kernel stack.
+           */
+
+          if (rtcb->xcp.kstack != NULL)
+            {
+              uintptr_t usp;
+
+              rtcb->xcp.kstkptr = (uint32_t *)regs[REG_A1];
+
+              /* The thread's own stack pointer is the one the system call
+               * saved if it was in one, and otherwise the one it was
+               * interrupted with, which up_schedule_sigaction() kept.
+               */
+
+              usp = rtcb->xcp.ustkptr != NULL ?
+                    (uintptr_t)rtcb->xcp.ustkptr :
+                    (uintptr_t)rtcb->xcp.saved_regs[REG_A1];
+
+              /* The siginfo passed in lives on the kernel stack, which the
+               * handler must not reach -- and cannot, once the permission
+               * control is programmed.  Copy it onto the user stack and
+               * hand the handler that copy.
+               *
+               * Skip the base save area the windowed ABI keeps in the
+               * 16 bytes below a stack pointer: it belongs to the frame
+               * that was interrupted.
+               */
+
+              usp = (usp - SIGTRAMP_SAVE_AREA - sizeof(siginfo_t)) &
+                    ~(SIGTRAMP_STACK_ALIGN - 1);
+
+              memcpy((void *)usp, (void *)regs[REG_A4], sizeof(siginfo_t));
+
+              regs[REG_A4]      = usp;            /* info */
+              regs[REG_A1]      = usp;
+            }
+#endif
         }
         break;
 #endif
@@ -313,6 +411,20 @@ int xtensa_swint(int irq, void *context, void *arg)
           xtensa_raiseprivilege(regs);        /* Privileged mode */
 
           rtcb->xcp.sigreturn = 0;
+
+#ifdef CONFIG_ARCH_KERNEL_STACK
+          /* The handler is done: return to the kernel stack the signal
+           * dispatch was running on.
+           */
+
+          if (rtcb->xcp.kstack != NULL)
+            {
+              DEBUGASSERT(rtcb->xcp.kstkptr != NULL);
+
+              regs[REG_A1]      = (uintptr_t)rtcb->xcp.kstkptr;
+              rtcb->xcp.kstkptr = NULL;
+            }
+#endif
         }
         break;
 #endif
@@ -365,6 +477,39 @@ int xtensa_swint(int irq, void *context, void *arg)
           xtensa_raiseprivilege(regs);        /* Privileged mode */
 #endif
 
+#ifdef CONFIG_ARCH_KERNEL_STACK
+          /* The system call itself runs in this task's own context, so
+           * without help it would run the kernel on the *user* stack.  That
+           * cannot be allowed in a kernel build: the user stack lives in a
+           * cache-MMU window, and any system call that selects a different
+           * address environment -- exec() loading a program, for one --
+           * reprograms that window and the kernel's stack disappears from
+           * under it, taking the frames it is standing on.
+           *
+           * So the outermost system call moves to the thread's kernel
+           * stack, which lives in kernel memory and is unaffected by
+           * address environment changes.  Nested calls are already on it.
+           */
+
+          if (index == 0 && rtcb->xcp.ktopstk != NULL)
+            {
+              rtcb->xcp.ustkptr = (uint32_t *)regs[REG_A1];
+
+              /* Start at the top of the kernel stack -- unless a signal
+               * handler is running, in which case the kernel stack is in
+               * use down to the point the dispatch left it at, and this
+               * call has to continue below that.  Restarting at the top
+               * would overwrite both the suspended signal dispatch and the
+               * context it saved to resume the thread with, which sits in
+               * the topmost frame.
+               */
+
+              regs[REG_A1]      = rtcb->xcp.kstkptr != NULL ?
+                                  (uintptr_t)rtcb->xcp.kstkptr :
+                                  (uintptr_t)rtcb->xcp.ktopstk;
+            }
+#endif
+
           /* Offset A2 to account for the reserved values */
 
           regs[REG_A2]        -= CONFIG_SYS_RESERVED;
diff --git a/arch/xtensa/src/lx7/Toolchain.defs 
b/arch/xtensa/src/lx7/Toolchain.defs
index 8561a5a883b..d94bf3868b6 100644
--- a/arch/xtensa/src/lx7/Toolchain.defs
+++ b/arch/xtensa/src/lx7/Toolchain.defs
@@ -244,7 +244,16 @@ CXXELFFLAGS = $(CXXFLAGS) -fvisibility=hidden 
-mtext-section-literals
 
 CXXELFFLAGS += -fno-use-cxa-atexit
 
-LDELFFLAGS = -r -e _start
+LDELFFLAGS = -e _start
+
+# A relocatable object is the default for loadable modules.  A kernel build
+# instead needs each user program fully linked at the addresses of its
+# address environment, so the partial link is dropped there.
+
+ifeq ($(CONFIG_BINFMT_ELF_RELOCATABLE),y)
+  LDELFFLAGS += -r
+endif
+
 LDELFFLAGS += -T $(call 
CONVERT_PATH,$(TOPDIR)$(DELIM)libs$(DELIM)libc$(DELIM)elf$(DELIM)gnu-elf.ld)
 ifneq ($(CONFIG_BUILD_KERNEL),y)
   # Flat build and protected elf entry point use crt0,

Reply via email to