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

commit c6b23e3e21ead6de128717e06c60e53e04ba750c
Author: Marco Casaroli <[email protected]>
AuthorDate: Thu Jul 30 14:16:04 2026 +0200

    xtensa/esp32s3: Isolate the unprivileged world.
    
    Separate the world split from the protected user image, give WORLD1 its own
    vector table and its own PMS permissions -- including the PSRAM -- clean up
    the user cache-MMU windows, and stop keeping the page pool mapped.
    
    Folds in:
      xtensa/esp32s3: separate the world split from the protected user image
      xtensa/esp32s3: give the unprivileged world its own vector table
      xtensa/esp32s3: give the unprivileged world its permissions
      xtensa/esp32s3: clean up the user cache-MMU windows
      xtensa/esp32s3: stop keeping the page pool mapped
      xtensa/esp32s3: give the PSRAM its own PMS permissions
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 arch/xtensa/src/common/espressif/esp_irq.c         |   4 +-
 arch/xtensa/src/esp32s3/Make.defs                  |  11 +
 arch/xtensa/src/esp32s3/esp32s3_isolation.c        | 628 +++++++++++++++++++++
 .../{esp32s3_userspace.h => esp32s3_isolation.h}   |  73 ++-
 arch/xtensa/src/esp32s3/esp32s3_pms.c              | 284 +++++++++-
 arch/xtensa/src/esp32s3/esp32s3_pms.h              |  24 +
 arch/xtensa/src/esp32s3/esp32s3_start.c            |  14 +
 arch/xtensa/src/esp32s3/esp32s3_userspace.c        | 160 +-----
 arch/xtensa/src/esp32s3/esp32s3_userspace.h        |  20 -
 arch/xtensa/src/esp32s3/esp32s3_wcl.c              |  24 +
 arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S   | 221 ++++++++
 .../esp32s3-devkit/configs/kernel_oct/defconfig    |   1 +
 12 files changed, 1251 insertions(+), 213 deletions(-)

diff --git a/arch/xtensa/src/common/espressif/esp_irq.c 
b/arch/xtensa/src/common/espressif/esp_irq.c
index 68f4ddb1c68..b7396d59b31 100644
--- a/arch/xtensa/src/common/espressif/esp_irq.c
+++ b/arch/xtensa/src/common/espressif/esp_irq.c
@@ -82,7 +82,7 @@
 #  include "hardware/esp32s3_soc.h"
 #  include "esp_gpio.h"
 #  include "esp32s3_rtc_gpio.h"
-#  include "esp32s3_userspace.h"
+#  include "esp32s3_isolation.h"
 #  ifdef CONFIG_SMP
 #    include "esp32s3_smp.h"
 #    define ESP_FROMCPU1_PERIPH     ESP32S3_PERIPH_INT_FROM_CPU1
@@ -107,7 +107,7 @@
 #  define ESP_NCPUS                     1
 #endif
 
-#if defined(CONFIG_ARCH_CHIP_ESP32S3) && defined(CONFIG_BUILD_PROTECTED)
+#if defined(CONFIG_ARCH_CHIP_ESP32S3) && !defined(CONFIG_BUILD_FLAT)
 #  define esp_pmsirqinitialize() esp32s3_pmsirqinitialize()
 #else
 #  define esp_pmsirqinitialize()
diff --git a/arch/xtensa/src/esp32s3/Make.defs 
b/arch/xtensa/src/esp32s3/Make.defs
index 6fd8e8087fb..574a99a599f 100644
--- a/arch/xtensa/src/esp32s3/Make.defs
+++ b/arch/xtensa/src/esp32s3/Make.defs
@@ -43,6 +43,17 @@ ifeq ($(CONFIG_BUILD_PROTECTED),y)
 CHIP_CSRCS += esp32s3_userspace.c
 endif
 
+# The privileged/unprivileged world split itself, shared by the protected
+# user split and the kernel-build address environments.
+
+ifneq ($(CONFIG_BUILD_FLAT),y)
+CHIP_CSRCS += esp32s3_isolation.c
+endif
+
+ifeq ($(CONFIG_BUILD_KERNEL),y)
+CHIP_ASRCS += esp32s3_world1_vectors.S
+endif
+
 # The MMU/PMS/WCL primitives back both the protected user split and the
 # BUILD_KERNEL address-environment remap.
 
diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c 
b/arch/xtensa/src/esp32s3/esp32s3_isolation.c
new file mode 100644
index 00000000000..9521ac2f42f
--- /dev/null
+++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c
@@ -0,0 +1,628 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_isolation.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/* Permission control that a protected build and a kernel build share: the
+ * violation interrupt, and the peripheral permissions.  Everything here
+ * concerns the WORLD0 (privileged) / WORLD1 (unprivileged) split and is
+ * independent of how the user memory itself is laid out, which is what
+ * separates the two build models.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/nuttx.h>
+#include <nuttx/sched.h>
+
+#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT
+#include <signal.h>
+#include <arch/irq.h>
+#include <arch/xtensa/xtensa_corebits.h>
+#endif
+
+#include <assert.h>
+#include <debug.h>
+
+#include "chip.h"
+#include "xtensa.h"
+#include "esp_attr.h"
+#include "esp_irq.h"
+#include "esp32s3_isolation.h"
+#include "esp32s3_addrenv.h"
+#include "esp32s3_pms.h"
+#include "esp32s3_spiram.h"
+#include "esp32s3_wcl.h"
+#include "hardware/esp32s3_rom_layout.h"
+#include "hardware/esp32s3_sensitive.h"
+#include "hardware/esp32s3_soc.h"
+
+#include "soc/extmem_reg.h"
+
+#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT
+#include "sched/sched.h"
+#include "signal/signal.h"
+#endif
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifdef CONFIG_BUILD_KERNEL
+/* The WORLD1 vector table is one Xtensa vector table: 1 KB. */
+
+#  define WORLD1_VECTORS_SIZE  0x400
+#endif
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef CONFIG_BUILD_KERNEL
+
+/* The WORLD1 vector table (esp32s3_world1_vectors.S), which the linker
+ * script places at the 1 KB alignment a vector base requires.
+ */
+
+extern uint8_t _world1_vectors[];
+
+/* The end of the kernel's instruction memory, and so the line that divides
+ * Internal SRAM1 between the instruction and the data bus.  The linker
+ * script aligns it to the 256 bytes a split line needs.
+ */
+
+extern uint8_t _iram_end[];
+
+/* Vectors in the kernel's own table.  Fetching one of these switches the CPU
+ * to WORLD0 once it is registered as a World Controller entry address.
+ */
+
+extern void _user_exception_vector(void);
+extern void _xtensa_level3_vector(void);
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT
+
+/****************************************************************************
+ * Name: pms_clear_violations
+ *
+ * Description:
+ *   Acknowledge and re-arm every PMS violation monitor.  The monitors raise
+ *   a level-triggered interrupt, so the latch must be cleared (pulse the CLR
+ *   bit) before returning from the ISR or the interrupt re-fires forever.
+ *
+ ****************************************************************************/
+
+static void IRAM_ATTR pms_clear_violations(void)
+{
+  /* IRAM0 / DRAM0 / PIF monitors: pulse VIOLATE_CLR (keeping VIOLATE_EN). */
+
+  modifyreg32(SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_1_REG, 0,
+              SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_VIOLATE_CLR_M);
+  modifyreg32(SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_VIOLATE_CLR_M, 0);
+
+  modifyreg32(SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_1_REG, 0,
+              SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_VIOLATE_CLR_M);
+  modifyreg32(SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_VIOLATE_CLR_M, 0);
+
+  modifyreg32(SENSITIVE_CORE_0_PIF_PMS_MONITOR_1_REG, 0,
+              SENSITIVE_CORE_0_PIF_PMS_MONITOR_VIOLATE_CLR_M);
+  modifyreg32(SENSITIVE_CORE_0_PIF_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_PIF_PMS_MONITOR_VIOLATE_CLR_M, 0);
+
+  /* Flash instruction/data cache reject monitors. */
+
+  modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_CLR_REG, 0,
+              EXTMEM_CORE0_IBUS_REJECT_INT_CLR_M |
+              EXTMEM_CORE0_DBUS_REJECT_INT_CLR_M);
+  modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_CLR_REG,
+              EXTMEM_CORE0_IBUS_REJECT_INT_CLR_M |
+              EXTMEM_CORE0_DBUS_REJECT_INT_CLR_M, 0);
+}
+#endif
+
+/****************************************************************************
+ * Name: pms_violation_isr
+ *
+ * Description:
+ *   This is the common PMS interrupt handler. It will be invoked the PMS
+ *   detects an access violation.
+ *
+ * Parameters:
+ *   cpuint        - CPU interrupt index
+ *   context       - Context data from the ISR
+ *   arg           - Opaque pointer to the internal driver state structure.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success. A negated errno value is returned on
+ *   failure.
+ *
+ ****************************************************************************/
+
+static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg)
+{
+#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT
+  uint32_t *regs = (uint32_t *)context;
+
+  /* Acknowledge and re-arm the monitors first so the level-triggered
+   * interrupt does not immediately re-fire while we handle it.
+   */
+
+  pms_clear_violations();
+
+  /* An ESP32-S3 PMS permission violation is asynchronous (unlike the precise
+   * cache-attribute faults).  If the interruptee was an unprivileged (user)
+   * WORLD1 task -- its saved PS carries the User Mode bit -- terminate only
+   * that task with SIGSEGV instead of the whole system.  The IRQ dispatch
+   * return path applies the up_schedule_sigaction() redirect.
+   */
+
+  if (regs != NULL && (regs[REG_PS] & PS_UM) != 0)
+    {
+      struct tcb_s *tcb = this_task();
+      siginfo_t     info;
+
+      _alert("SIGSEGV (PMS) task %s: PC=%08x\n",
+             get_task_name(tcb), (unsigned)regs[REG_PC]);
+
+      info.si_signo           = SIGSEGV;
+      info.si_code            = SI_USER;
+      info.si_errno           = 0;
+      info.si_value.sival_ptr = NULL;
+
+      nxsig_tcbdispatch(tcb, &info, false);
+      return OK;
+    }
+#endif
+
+  /* Privileged (WORLD0) violation, or abort disabled: not survivable. */
+
+  PANIC();
+
+  return OK;
+}
+
+#ifdef CONFIG_BUILD_KERNEL
+/****************************************************************************
+ * Name: isolation_enable_interrupts
+ *
+ * Description:
+ *   Arm the permission violation monitors.  The handler itself is registered
+ *   later, from up_irqinitialize(); until then a violation is a panic, which
+ *   is what an early kernel violation should be anyway.
+ *
+ ****************************************************************************/
+
+static void isolation_enable_interrupts(void)
+{
+  modifyreg32(SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_VIOLATE_CLR_M,
+              SENSITIVE_CORE_0_IRAM0_PMS_MONITOR_VIOLATE_EN);
+
+  modifyreg32(SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_VIOLATE_CLR_M,
+              SENSITIVE_CORE_0_DRAM0_PMS_MONITOR_VIOLATE_EN);
+
+  modifyreg32(SENSITIVE_CORE_0_PIF_PMS_MONITOR_1_REG,
+              SENSITIVE_CORE_0_PIF_PMS_MONITOR_VIOLATE_CLR_M,
+              SENSITIVE_CORE_0_PIF_PMS_MONITOR_VIOLATE_EN);
+
+  /* Instruction and data cache reject monitors. */
+
+  modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_CLR_REG,
+              EXTMEM_CORE0_IBUS_REJECT_INT_CLR_M |
+              EXTMEM_CORE0_DBUS_REJECT_INT_CLR_M, 0);
+  modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_ENA_REG,
+              EXTMEM_CORE0_IBUS_REJECT_INT_ENA_M |
+              EXTMEM_CORE0_DBUS_REJECT_INT_ENA_M,
+              EXTMEM_CORE0_IBUS_REJECT_INT_ENA |
+              EXTMEM_CORE0_DBUS_REJECT_INT_ENA);
+}
+
+/****************************************************************************
+ * Name: isolation_configure_iram
+ *
+ * Description:
+ *   Instruction memory.  All of it belongs to the kernel except the WORLD1
+ *   vector table, which the unprivileged world must be able to fetch and
+ *   nothing more.
+ *
+ ****************************************************************************/
+
+static void isolation_configure_iram(void)
+{
+  uintptr_t vstart = (uintptr_t)_world1_vectors;
+  uintptr_t vend   = vstart + WORLD1_VECTORS_SIZE;
+
+  /* Internal SRAM0, the blocks not given to the instruction cache.  They
+   * hold the kernel's own vector table and the start of its IRAM code, and
+   * the hardware can say nothing finer than a whole 16 KB block here, which
+   * is why the WORLD1 table is not among them.
+   */
+
+  esp32s3_pms_configure_icache(PMS_AREA_0, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_icache(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_icache(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_icache(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_NONE);
+
+  /* Internal SRAM1, split around the WORLD1 vector table:
+   *
+   *   area 0   the kernel's IRAM code
+   *   area 1   the WORLD1 vector table
+   *   area 2   whatever IRAM follows it
+   *   area 3   past the main split line, which is data memory
+   */
+
+  esp32s3_pms_set_iram_split_line(PMS_SPLIT_LINE_0, vstart);
+  esp32s3_pms_set_iram_split_line(PMS_SPLIT_LINE_1, vend);
+
+  esp32s3_pms_configure_iram_region(PMS_AREA_0, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_iram_region(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_iram_region(PMS_AREA_2, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_iram_region(PMS_AREA_3, PMS_WORLD_0,
+                                    PMS_ACCESS_NONE);
+
+  esp32s3_pms_configure_iram_region(PMS_AREA_0, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_iram_region(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_X);
+  esp32s3_pms_configure_iram_region(PMS_AREA_2, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_iram_region(PMS_AREA_3, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+}
+
+/****************************************************************************
+ * Name: isolation_configure_dram
+ *
+ * Description:
+ *   Data memory.  A kernel build has none that belongs to the user: a
+ *   process keeps its data, heap and stacks in PSRAM behind the cache MMU,
+ *   so the unprivileged world has no business in internal RAM at all.
+ *
+ ****************************************************************************/
+
+static void isolation_configure_dram(void)
+{
+  uintptr_t rom_reserved =
+    ALIGN_DOWN(ets_rom_layout_p->dram0_rtos_reserved_start, 256);
+
+  /* Internal SRAM2, the blocks not given to the data cache. */
+
+  esp32s3_pms_configure_dcache(PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_dcache(PMS_WORLD_1, PMS_ACCESS_NONE);
+
+  /* Area 0 is what lies before the main split line -- instruction memory,
+   * which is not to be reached over the data bus by either world.  The rest
+   * is the kernel's.
+   */
+
+  esp32s3_pms_set_dram_split_line(PMS_SPLIT_LINE_0,
+                                  MAP_IRAM_TO_DRAM((uintptr_t)_iram_end));
+  esp32s3_pms_set_dram_split_line(PMS_SPLIT_LINE_1, rom_reserved);
+
+  esp32s3_pms_configure_dram_region(PMS_AREA_0, PMS_WORLD_0,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_dram_region(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_dram_region(PMS_AREA_2, PMS_WORLD_0, PMS_ACCESS_ALL);
+  esp32s3_pms_configure_dram_region(PMS_AREA_3, PMS_WORLD_0, PMS_ACCESS_ALL);
+
+  esp32s3_pms_configure_dram_region(PMS_AREA_0, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_dram_region(PMS_AREA_1, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_dram_region(PMS_AREA_2, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_dram_region(PMS_AREA_3, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32s3_isolation_revoke_peripherals
+ *
+ * Description:
+ *   Refuse World 1 every peripheral.  A user process reaches a device
+ *   through the kernel, so it needs none of them directly.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_isolation_revoke_peripherals(void)
+{
+  /* Revoke User access permission to every peripheral */
+
+  esp32s3_pms_configure_peripheral(PMS_UART1, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_I2C, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_MISC, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_IO_MUX, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_RTC, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_FE, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_FE2, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_GPIO, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_G0SPI_0, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_G0SPI_1, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_UART, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SYSTIMER, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_TIMERGROUP1, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_TIMERGROUP, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_BB, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_LEDC, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_RMT, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_UHCI0, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_I2C_EXT0, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_BT, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_PWR, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_WIFIMAC, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_RWBT, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_I2S1, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_CAN, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_APB_CTRL, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SPI_2, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_WORLD_CONTROLLER, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_DIO, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_AD, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_CACHE_CONFIG, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_DMA_COPY, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_INTERRUPT, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SENSITIVE, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SYSTEM, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_BT_PWR, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_APB_ADC, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_CRYPTO_DMA, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_CRYPTO_PERI, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_USB_WRAP, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_USB_DEVICE, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_I2S0, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_HINF, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_PWM0, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_BACKUP, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SLC, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_PCNT, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SLCHOST, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_UART2, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_PWM1, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SDIO_HOST, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_I2C_EXT1, PMS_WORLD_1,
+                                   PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_SPI_3, PMS_WORLD_1, PMS_ACCESS_NONE);
+  esp32s3_pms_configure_peripheral(PMS_USB, PMS_WORLD_1, PMS_ACCESS_NONE);
+}
+
+#ifdef CONFIG_BUILD_KERNEL
+
+/****************************************************************************
+ * Name: esp32s3_isolation_worlds
+ *
+ * Description:
+ *   Give World 1 its own vector table and register the kernel entry points
+ *   that return the CPU to World 0.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_isolation_worlds(void)
+{
+  /* Give each world its own vector table.  The override applies to both
+   * worlds at once, so WORLD0 has to be pointed at the kernel table it has
+   * been using all along, the one __start() loaded into VECBASE.
+   */
+
+  esp32s3_wcl_set_vecbase(PMS_WORLD_0, (uintptr_t)_init_start);
+  esp32s3_wcl_set_vecbase(PMS_WORLD_1, (uintptr_t)_world1_vectors);
+
+  /* Fetching one of these kernel vectors is what takes the CPU back to
+   * WORLD0.  Only the level 1 and level 3 paths record the interruptee's
+   * world on the way in and restore it on the way out, so only those two
+   * may be reached from WORLD1; the WORLD1 table handles window spills
+   * itself and never leaves the world.
+   */
+
+  esp32s3_wcl_set_world0_entry(1, (uintptr_t)_user_exception_vector);
+  esp32s3_wcl_set_world0_entry(2, (uintptr_t)_xtensa_level3_vector);
+}
+
+/****************************************************************************
+ * Name: esp32s3_isolation_permissions
+ *
+ * Description:
+ *   Give World 1 its permissions:  its own pages of the page pool, and
+ *   nothing else.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_isolation_permissions(void)
+{
+  size_t psram_size;
+
+  /* The WORLD1 vector table has to be in Internal SRAM1 for any of this to
+   * mean anything (see isolation_configure_iram), and a mistake here would
+   * be silent: the split lines would land somewhere harmless and the
+   * unprivileged world would keep its access to instruction memory.
+   */
+
+  ASSERT((uintptr_t)_world1_vectors >= SOC_DIRAM_IRAM_LOW &&
+         (uintptr_t)_world1_vectors + WORLD1_VECTORS_SIZE <=
+         (uintptr_t)_iram_end);
+
+  isolation_enable_interrupts();
+
+  /* Divide Internal SRAM1 into its instruction and data halves.  The kernel
+   * image is linked with its IRAM below _iram_end and its data above the
+   * corresponding data-bus address.
+   */
+
+  esp32s3_pms_set_sram_main_split_line((uintptr_t)_iram_end);
+
+  esp32s3_pms_configure_irom_access();
+  esp32s3_pms_configure_drom_access();
+
+  isolation_configure_iram();
+  isolation_configure_dram();
+
+  /* Cached external PSRAM.  Every page of a user process -- text, data and
+   * heap alike -- is a page of the pgalloc pool, and that pool is a
+   * contiguous physical window of the PSRAM device.  Give WORLD1 exactly
+   * that window and nothing else, so the kernel's own PSRAM above and below
+   * it is out of reach.  The ACE addresses are physical offsets into the
+   * device, which is the same space mm_pgalloc() hands out.
+   *
+   * These registers were never programmed before, which left PSRAM at its
+   * reset value -- open to both worlds -- while the whole of user space
+   * lived in it.
+   */
+
+  psram_size = esp_spiram_get_size();
+
+  DEBUGASSERT(ESP32S3_PGPOOL_PEND <= psram_size);
+
+  esp32s3_pms_set_sram_split_line(PMS_SPLIT_LINE_0, 0,
+                                  ESP32S3_PGPOOL_PBASE);
+  esp32s3_pms_set_sram_split_line(PMS_SPLIT_LINE_1, ESP32S3_PGPOOL_PBASE,
+                                  ESP32S3_PGPOOL_SIZE);
+  esp32s3_pms_set_sram_split_line(PMS_SPLIT_LINE_2, ESP32S3_PGPOOL_PEND,
+                                  psram_size - ESP32S3_PGPOOL_PEND);
+
+  /* Region 3 is unused.  Park it at the end of the device with zero length:
+   * the TRM forbids overlapping regions, so it cannot be left at zero.
+   */
+
+  esp32s3_pms_set_sram_split_line(PMS_SPLIT_LINE_3, psram_size, 0);
+
+  esp32s3_pms_configure_sram_region(PMS_AREA_0, PMS_WORLD_0,
+                                    PMS_ACCESS_ALL);
+  esp32s3_pms_configure_sram_region(PMS_AREA_1, PMS_WORLD_0,
+                                    PMS_ACCESS_ALL);
+  esp32s3_pms_configure_sram_region(PMS_AREA_2, PMS_WORLD_0,
+                                    PMS_ACCESS_ALL);
+  esp32s3_pms_configure_sram_region(PMS_AREA_3, PMS_WORLD_0,
+                                    PMS_ACCESS_ALL);
+
+  /* The pool holds text and data pages interleaved, so the grant has to
+   * cover both; the ACE cannot separate them at page granularity and W^X
+   * within a process is not what this boundary is for.
+   */
+
+  esp32s3_pms_configure_sram_region(PMS_AREA_0, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_sram_region(PMS_AREA_1, PMS_WORLD_1,
+                                    PMS_ACCESS_ALL);
+  esp32s3_pms_configure_sram_region(PMS_AREA_2, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+  esp32s3_pms_configure_sram_region(PMS_AREA_3, PMS_WORLD_1,
+                                    PMS_ACCESS_NONE);
+
+  /* Cached external flash.  A user process whose text was copied into PSRAM
+   * needs nothing from flash -- but one running XIP does, so this cannot
+   * simply deny every region.  Until the XIP case carries its own split
+   * line between the kernel image and the mapped application, WORLD1 keeps
+   * no flash access and XIP is unsupported here.
+   */
+
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_0, PMS_WORLD_0,
+                                           PMS_ACCESS_ALL);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_1, PMS_WORLD_0,
+                                           PMS_ACCESS_ALL);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_2, PMS_WORLD_0,
+                                           PMS_ACCESS_ALL);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_3, PMS_WORLD_0,
+                                           PMS_ACCESS_ALL);
+
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_0, PMS_WORLD_1,
+                                           PMS_ACCESS_NONE);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_1, PMS_WORLD_1,
+                                           PMS_ACCESS_NONE);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_2, PMS_WORLD_1,
+                                           PMS_ACCESS_NONE);
+  esp32s3_pms_configure_flash_cache_region(PMS_AREA_3, PMS_WORLD_1,
+                                           PMS_ACCESS_NONE);
+
+  esp32s3_isolation_revoke_peripherals();
+}
+#endif
+
+/****************************************************************************
+ * Name: esp32s3_pmsirqinitialize
+ *
+ * Description:
+ *   Install the handler that reports a permission violation.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_pmsirqinitialize(void)
+{
+  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_IRAM0_PMS_MONITOR_VIOLATE,
+                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
+  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_DRAM0_PMS_MONITOR_VIOLATE,
+                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
+  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CACHE_CORE0_ACS,
+                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
+  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_PIF_PMS_MONITOR_VIOLATE,
+                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
+
+  up_enable_irq(ESP32S3_IRQ_CORE_0_IRAM0_PMS_MONITOR_VIOLATE);
+  up_enable_irq(ESP32S3_IRQ_CORE_0_DRAM0_PMS_MONITOR_VIOLATE);
+  up_enable_irq(ESP32S3_IRQ_CACHE_CORE0_ACS);
+  up_enable_irq(ESP32S3_IRQ_CORE_0_PIF_PMS_MONITOR_VIOLATE);
+}
diff --git a/arch/xtensa/src/esp32s3/esp32s3_userspace.h 
b/arch/xtensa/src/esp32s3/esp32s3_isolation.h
similarity index 50%
copy from arch/xtensa/src/esp32s3/esp32s3_userspace.h
copy to arch/xtensa/src/esp32s3/esp32s3_isolation.h
index 0deff1d0f5c..5b0be7abf7b 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_userspace.h
+++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.h
@@ -1,5 +1,5 @@
 /****************************************************************************
- * arch/xtensa/src/esp32s3/esp32s3_userspace.h
+ * arch/xtensa/src/esp32s3/esp32s3_isolation.h
  *
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -18,8 +18,8 @@
  *
  ****************************************************************************/
 
-#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERSPACE_H
-#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERSPACE_H
+#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ISOLATION_H
+#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ISOLATION_H
 
 /****************************************************************************
  * Included Files
@@ -31,19 +31,66 @@
  * Public Functions Prototypes
  ****************************************************************************/
 
+#ifndef CONFIG_BUILD_FLAT
+
+/****************************************************************************
+ * Name: esp32s3_isolation_revoke_peripherals
+ *
+ * Description:
+ *   Revoke the unprivileged world's access to every peripheral.  A user
+ *   task reaches a peripheral only through a system call, so WORLD1 has no
+ *   business addressing one directly.
+ *
+ * Input Parameters:
+ *   None.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_isolation_revoke_peripherals(void);
+
+#ifdef CONFIG_BUILD_KERNEL
+
+/****************************************************************************
+ * Name: esp32s3_isolation_worlds
+ *
+ * Description:
+ *   Give the unprivileged world its own vector table and tell the World
+ *   Controller which kernel vectors return the CPU to the privileged world.
+ *   A protected build does the equivalent from esp32s3_userspace.c, where
+ *   the table belongs to the user image instead.
+ *
+ * Input Parameters:
+ *   None.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void esp32s3_isolation_worlds(void);
+
 /****************************************************************************
- * Name: esp32s3_userspace
+ * Name: esp32s3_isolation_permissions
  *
  * Description:
- *   For the case of the separate user-/kernel-space build, perform whatever
- *   platform specific initialization of the user memory is required.
- *   Normally this just means initializing the user space .data and .bss
- *   segments.
+ *   Program the permission control for a kernel build: what the
+ *   unprivileged world may reach, which is the WORLD1 vector table and
+ *   nothing else in internal memory, and arm the violation monitors.  This
+ *   is the kernel-build counterpart to configure_mpu() in
+ *   esp32s3_userspace.c, which serves the protected user image.
+ *
+ * Input Parameters:
+ *   None.
+ *
+ * Returned Value:
+ *   None.
  *
  ****************************************************************************/
 
-#ifdef CONFIG_BUILD_PROTECTED
-void esp32s3_userspace(void);
+void esp32s3_isolation_permissions(void);
 #endif
 
 /****************************************************************************
@@ -60,10 +107,10 @@ void esp32s3_userspace(void);
  *
  ****************************************************************************/
 
-#ifdef CONFIG_BUILD_PROTECTED
 void esp32s3_pmsirqinitialize(void);
+
 #else
 #  define esp32s3_pmsirqinitialize()
-#endif
+#endif /* !CONFIG_BUILD_FLAT */
 
-#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERSPACE_H */
+#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ISOLATION_H */
diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.c 
b/arch/xtensa/src/esp32s3/esp32s3_pms.c
index e68c284252a..776b805b63f 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_pms.c
+++ b/arch/xtensa/src/esp32s3/esp32s3_pms.c
@@ -205,6 +205,17 @@ static void set_dram_split_line(uintptr_t addr, const 
uint32_t sensitive_reg)
 
 /****************************************************************************
  * Name: esp32s3_pms_set_sram_main_split_line
+ *
+ * Description:
+ *   Set the boundary that divides Internal SRAM1 between the instruction
+ *   bus and the data bus.
+ *
+ * Input Parameters:
+ *   addr     - The boundary address.  It must be aligned to 256 bytes.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_set_sram_main_split_line(uintptr_t addr)
@@ -215,6 +226,18 @@ void esp32s3_pms_set_sram_main_split_line(uintptr_t addr)
 
 /****************************************************************************
  * Name: esp32s3_pms_set_iram_split_line
+ *
+ * Description:
+ *   Set one of the boundaries that divide the instruction bus into the
+ *   areas a permission is given to.
+ *
+ * Input Parameters:
+ *   line     - Which boundary to set.
+ *   addr     - The boundary address.  It must be aligned to 256 bytes.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_set_iram_split_line(enum pms_split_line_e line,
@@ -244,6 +267,18 @@ void esp32s3_pms_set_iram_split_line(enum pms_split_line_e 
line,
 
 /****************************************************************************
  * Name: esp32s3_pms_set_dram_split_line
+ *
+ * Description:
+ *   Set one of the boundaries that divide the data bus into the areas a
+ *   permission is given to.
+ *
+ * Input Parameters:
+ *   line     - Which boundary to set.
+ *   addr     - The boundary address.  It must be aligned to 256 bytes.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_set_dram_split_line(enum pms_split_line_e line,
@@ -273,6 +308,19 @@ void esp32s3_pms_set_dram_split_line(enum pms_split_line_e 
line,
 
 /****************************************************************************
  * Name: esp32s3_pms_set_flash_cache_split_line
+ *
+ * Description:
+ *   Set one of the boundaries that divide cached external flash into the
+ *   areas a permission is given to.
+ *
+ * Input Parameters:
+ *   line     - Which boundary to set.
+ *   addr     - The start of the area.
+ *   length   - The length of the area in bytes.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_set_flash_cache_split_line(enum pms_split_line_e line,
@@ -346,6 +394,18 @@ void esp32s3_pms_set_flash_cache_split_line(enum 
pms_split_line_e line,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_iram_region
+ *
+ * Description:
+ *   Give a world its permission on one area of the instruction bus.
+ *
+ * Input Parameters:
+ *   area     - Which area, as named by the split lines.
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_iram_region(enum pms_area_e area,
@@ -375,6 +435,18 @@ void esp32s3_pms_configure_iram_region(enum pms_area_e 
area,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_icache
+ *
+ * Description:
+ *   Give a world its permission on one area of the instruction cache.
+ *
+ * Input Parameters:
+ *   area     - Which area, as named by the split lines.
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_icache(enum pms_area_e area,
@@ -404,6 +476,17 @@ void esp32s3_pms_configure_icache(enum pms_area_e area,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_dcache
+ *
+ * Description:
+ *   Give a world its permission on the data cache.
+ *
+ * Input Parameters:
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_dcache(enum esp32s3_pms_world_e world,
@@ -443,6 +526,18 @@ void esp32s3_pms_configure_dcache(enum esp32s3_pms_world_e 
world,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_dram_region
+ *
+ * Description:
+ *   Give a world its permission on one area of the data bus.
+ *
+ * Input Parameters:
+ *   area     - Which area, as named by the split lines.
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_dram_region(enum pms_area_e area,
@@ -469,34 +564,47 @@ void esp32s3_pms_configure_dram_region(enum pms_area_e 
area,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_flash_cache_region
+ *
+ * Description:
+ *   Give a world its permission on one area of cached external flash.
+ *
+ * Input Parameters:
+ *   area     - Which area, as named by the split lines.
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
+/****************************************************************************
+ * Name: pms_ace_attr
+ *
+ * Description:
+ *   Convert PMS_ACCESS_* flags to the 3-bit field the external-memory ACE
+ *   registers use.  TRM Table 15.5-2 (p.697): the field is ordered W/R/X
+ *   with X in the least significant bit, which is the reverse of the order
+ *   in enum pms_flags_e, and the reverse again of the IRAM0/DRAM0 constraint
+ *   fields.  Footnote C settles it: 0b010 grants read and neither write nor
+ *   execute.
+ *
+ ****************************************************************************/
+
+static uint32_t pms_ace_attr(enum pms_flags_e flags)
+{
+  return (((flags & PMS_ACCESS_W) != 0) << 2) |
+         (((flags & PMS_ACCESS_R) != 0) << 1) |
+         (((flags & PMS_ACCESS_X) != 0) << 0);
+}
+
 void esp32s3_pms_configure_flash_cache_region(enum pms_area_e area,
                                               enum esp32s3_pms_world_e world,
                                               enum pms_flags_e flags)
 {
   const uint32_t shift = (FLASH_CACHE_S * world);
   const uint32_t mask = FLASH_CACHE_V << shift;
-  uint32_t attr;
-
-  if (flags == PMS_ACCESS_ALL)
-    {
-      attr = 0b11;
-    }
-  else if ((flags & PMS_ACCESS_W) != 0)
-    {
-      PANIC();
-    }
-  else if ((flags & PMS_ACCESS_X) != 0)
-    {
-      attr = flags | 0b1;
-    }
-  else
-    {
-      attr = flags;
-    }
-
-  uint32_t val = 0x40 | (attr & FLASH_CACHE_V) << shift;
+  const uint32_t val = pms_ace_attr(flags) << shift;
 
   switch (area)
     {
@@ -528,8 +636,128 @@ void esp32s3_pms_configure_flash_cache_region(enum 
pms_area_e area,
     }
 }
 
+/****************************************************************************
+ * Name: esp32s3_pms_set_sram_split_line
+ *
+ * Description:
+ *   Place one of the four external-SRAM (PSRAM) split regions.  Address and
+ *   length are physical -- a zero-based offset into the PSRAM device, the
+ *   same space mm_pgalloc() hands out -- and both must be 64 KB aligned
+ *   (TRM 15.5.1 p.696).  Regions must not overlap.
+ *
+ ****************************************************************************/
+
+void esp32s3_pms_set_sram_split_line(enum pms_split_line_e line,
+                                     uintptr_t addr, size_t length)
+{
+  uintptr_t aligned_addr = ALIGN_DOWN(addr, MMU_PAGE_SIZE);
+  size_t length_pages = length / MMU_PAGE_SIZE;
+
+  switch (line)
+    {
+      case PMS_SPLIT_LINE_0:
+        {
+          modifyreg32(APB_CTRL_SRAM_ACE0_ADDR_REG,
+                      APB_CTRL_SRAM_ACE0_ADDR_S_M,
+                      VALUE_TO_FIELD(aligned_addr,
+                                     APB_CTRL_SRAM_ACE0_ADDR_S));
+          modifyreg32(APB_CTRL_SRAM_ACE0_SIZE_REG,
+                      APB_CTRL_SRAM_ACE0_SIZE_M,
+                      VALUE_TO_FIELD(length_pages, APB_CTRL_SRAM_ACE0_SIZE));
+        }
+        break;
+      case PMS_SPLIT_LINE_1:
+        {
+          modifyreg32(APB_CTRL_SRAM_ACE1_ADDR_REG,
+                      APB_CTRL_SRAM_ACE1_ADDR_S_M,
+                      VALUE_TO_FIELD(aligned_addr,
+                                     APB_CTRL_SRAM_ACE1_ADDR_S));
+          modifyreg32(APB_CTRL_SRAM_ACE1_SIZE_REG,
+                      APB_CTRL_SRAM_ACE1_SIZE_M,
+                      VALUE_TO_FIELD(length_pages, APB_CTRL_SRAM_ACE1_SIZE));
+        }
+        break;
+      case PMS_SPLIT_LINE_2:
+        {
+          modifyreg32(APB_CTRL_SRAM_ACE2_ADDR_REG,
+                      APB_CTRL_SRAM_ACE2_ADDR_S_M,
+                      VALUE_TO_FIELD(aligned_addr,
+                                     APB_CTRL_SRAM_ACE2_ADDR_S));
+          modifyreg32(APB_CTRL_SRAM_ACE2_SIZE_REG,
+                      APB_CTRL_SRAM_ACE2_SIZE_M,
+                      VALUE_TO_FIELD(length_pages, APB_CTRL_SRAM_ACE2_SIZE));
+        }
+        break;
+      case PMS_SPLIT_LINE_3:
+        {
+          modifyreg32(APB_CTRL_SRAM_ACE3_ADDR_REG,
+                      APB_CTRL_SRAM_ACE3_ADDR_S_M,
+                      VALUE_TO_FIELD(aligned_addr,
+                                     APB_CTRL_SRAM_ACE3_ADDR_S));
+          modifyreg32(APB_CTRL_SRAM_ACE3_SIZE_REG,
+                      APB_CTRL_SRAM_ACE3_SIZE_M,
+                      VALUE_TO_FIELD(length_pages, APB_CTRL_SRAM_ACE3_SIZE));
+        }
+        break;
+      default:
+        {
+          PANIC();
+        }
+        break;
+    }
+}
+
+/****************************************************************************
+ * Name: esp32s3_pms_configure_sram_region
+ *
+ * Description:
+ *   Set a world's permissions on one external-SRAM split region.  Same
+ *   field layout as the flash regions, TRM Table 15.5-2.
+ *
+ ****************************************************************************/
+
+void esp32s3_pms_configure_sram_region(enum pms_area_e area,
+                                       enum esp32s3_pms_world_e world,
+                                       enum pms_flags_e flags)
+{
+  const uint32_t shift = (FLASH_CACHE_S * world);
+  const uint32_t mask = FLASH_CACHE_V << shift;
+  const uint32_t val = pms_ace_attr(flags) << shift;
+
+  switch (area)
+    {
+      case PMS_AREA_0:
+        modifyreg32(APB_CTRL_SRAM_ACE0_ATTR_REG, mask, val);
+        break;
+      case PMS_AREA_1:
+        modifyreg32(APB_CTRL_SRAM_ACE1_ATTR_REG, mask, val);
+        break;
+      case PMS_AREA_2:
+        modifyreg32(APB_CTRL_SRAM_ACE2_ATTR_REG, mask, val);
+        break;
+      case PMS_AREA_3:
+        modifyreg32(APB_CTRL_SRAM_ACE3_ATTR_REG, mask, val);
+        break;
+      default:
+        PANIC();
+        break;
+    }
+}
+
 /****************************************************************************
  * Name: esp32s3_pms_configure_peripheral
+ *
+ * Description:
+ *   Give a world its permission on one peripheral.
+ *
+ * Input Parameters:
+ *   periph   - The peripheral.
+ *   world    - The world the permission applies to.
+ *   flags    - The access to allow.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_peripheral(enum pms_peripheral_e periph,
@@ -567,6 +795,14 @@ void esp32s3_pms_configure_peripheral(enum 
pms_peripheral_e periph,
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_irom_access
+ *
+ * Description:
+ *   Allow both worlds to read the instruction ROM.  The ROM holds code that
+ *   a user process still calls, so neither world can be refused it.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_irom_access(void)
@@ -588,6 +824,14 @@ void esp32s3_pms_configure_irom_access(void)
 
 /****************************************************************************
  * Name: esp32s3_pms_configure_drom_access
+ *
+ * Description:
+ *   Allow both worlds to read the data ROM.  The ROM holds constants that a
+ *   user process still reads, so neither world can be refused it.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_pms_configure_drom_access(void)
diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.h 
b/arch/xtensa/src/esp32s3/esp32s3_pms.h
index fe4bb7cc787..2afec694c0b 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_pms.h
+++ b/arch/xtensa/src/esp32s3/esp32s3_pms.h
@@ -251,6 +251,30 @@ void esp32s3_pms_configure_flash_cache_region(enum 
pms_area_e area,
                                               enum esp32s3_pms_world_e world,
                                               enum pms_flags_e flags);
 
+/****************************************************************************
+ * Name: esp32s3_pms_set_sram_split_line
+ *
+ * Description:
+ *   Place one of the four external-SRAM (PSRAM) split regions.  Address and
+ *   length are physical offsets into the PSRAM device, both 64 KB aligned.
+ *
+ ****************************************************************************/
+
+void esp32s3_pms_set_sram_split_line(enum pms_split_line_e line,
+                                     uintptr_t addr, size_t length);
+
+/****************************************************************************
+ * Name: esp32s3_pms_configure_sram_region
+ *
+ * Description:
+ *   Configure a world's access permissions to one external-SRAM region.
+ *
+ ****************************************************************************/
+
+void esp32s3_pms_configure_sram_region(enum pms_area_e area,
+                                       enum esp32s3_pms_world_e world,
+                                       enum pms_flags_e flags);
+
 /****************************************************************************
  * Name: esp32s3_pms_configure_peripheral
  *
diff --git a/arch/xtensa/src/esp32s3/esp32s3_start.c 
b/arch/xtensa/src/esp32s3/esp32s3_start.c
index ecf6d42bdce..4e99f628137 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_start.c
+++ b/arch/xtensa/src/esp32s3/esp32s3_start.c
@@ -51,6 +51,9 @@
 #ifdef CONFIG_BUILD_PROTECTED
 #  include "esp32s3_userspace.h"
 #endif
+#ifdef CONFIG_BUILD_KERNEL
+#  include "esp32s3_isolation.h"
+#endif
 #include "esp32s3_spi_timing.h"
 #include "hardware/esp32s3_cache_memory.h"
 #include "hardware/esp32s3_system.h"
@@ -441,6 +444,17 @@ noinstrument_function void noreturn_function IRAM_ATTR 
__esp32s3_start(void)
   showprogress('C');
 #endif
 
+#ifdef CONFIG_BUILD_KERNEL
+  /* A kernel build has no user image to load, but the unprivileged world
+   * still needs its vector table and its permissions before the first user
+   * process runs.
+   */
+
+  esp32s3_isolation_worlds();
+  esp32s3_isolation_permissions();
+  showprogress('C');
+#endif
+
   /* Bring up NuttX */
 
   nx_start();
diff --git a/arch/xtensa/src/esp32s3/esp32s3_userspace.c 
b/arch/xtensa/src/esp32s3/esp32s3_userspace.c
index b18267a3b91..4a8cce1b176 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_userspace.c
+++ b/arch/xtensa/src/esp32s3/esp32s3_userspace.c
@@ -38,7 +38,7 @@
 #include "chip.h"
 #include "xtensa.h"
 #include "esp_attr.h"
-#include "esp_irq.h"
+#include "esp32s3_isolation.h"
 #include "esp32s3_userspace.h"
 #include "esp32s3_mmu.h"
 #include "esp32s3_pms.h"
@@ -50,8 +50,6 @@
 
 #include "soc/extmem_reg.h"
 
-#ifdef CONFIG_BUILD_PROTECTED
-
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
@@ -295,31 +293,6 @@ static void initialize_iram(void)
     }
 }
 
-/****************************************************************************
- * Name: pms_violation_isr
- *
- * Description:
- *   This is the common PMS interrupt handler. It will be invoked the PMS
- *   detects an access violation.
- *
- * Parameters:
- *   cpuint        - CPU interrupt index
- *   context       - Context data from the ISR
- *   arg           - Opaque pointer to the internal driver state structure.
- *
- * Returned Value:
- *   Zero (OK) is returned on success. A negated errno value is returned on
- *   failure.
- *
- ****************************************************************************/
-
-static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg)
-{
-  PANIC();
-
-  return OK;
-}
-
 /****************************************************************************
  * Name: pms_enable_interrupts
  *
@@ -617,102 +590,6 @@ static IRAM_ATTR void 
pms_configure_flash_cache_access(void)
   esp32s3_dcache_resume(cache_state);
 }
 
-/****************************************************************************
- * Name: pms_configure_peripheral_access
- *
- * Description:
- *   Configure Kernel and Userspace permissions for accessing the chip's
- *   peripherals.
- *
- * Input Parameters:
- *   None.
- *
- * Returned Value:
- *   None.
- *
- ****************************************************************************/
-
-static void pms_configure_peripheral_access(void)
-{
-  /* Revoke User access permission to every peripheral */
-
-  esp32s3_pms_configure_peripheral(PMS_UART1, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_I2C, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_MISC, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_IO_MUX, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_RTC, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_FE, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_FE2, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_GPIO, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_G0SPI_0, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_G0SPI_1, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_UART, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SYSTIMER, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_TIMERGROUP1, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_TIMERGROUP, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_BB, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_LEDC, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_RMT, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_UHCI0, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_I2C_EXT0, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_BT, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_PWR, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_WIFIMAC, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_RWBT, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_I2S1, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_CAN, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_APB_CTRL, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SPI_2, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_WORLD_CONTROLLER, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_DIO, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_AD, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_CACHE_CONFIG, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_DMA_COPY, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_INTERRUPT, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SENSITIVE, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SYSTEM, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_BT_PWR, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_APB_ADC, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_CRYPTO_DMA, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_CRYPTO_PERI, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_USB_WRAP, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_USB_DEVICE, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_I2S0, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_HINF, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_PWM0, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_BACKUP, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SLC, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_PCNT, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SLCHOST, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_UART2, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_PWM1, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SDIO_HOST, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_I2C_EXT1, PMS_WORLD_1,
-                                   PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_SPI_3, PMS_WORLD_1, PMS_ACCESS_NONE);
-  esp32s3_pms_configure_peripheral(PMS_USB, PMS_WORLD_1, PMS_ACCESS_NONE);
-}
-
 /****************************************************************************
  * Name: configure_mpu
  *
@@ -767,7 +644,7 @@ static void configure_mpu(void)
    * peripherals.
    */
 
-  pms_configure_peripheral_access();
+  esp32s3_isolation_revoke_peripherals();
 }
 
 /****************************************************************************
@@ -813,36 +690,3 @@ void esp32s3_userspace(void)
 
   configure_mpu();
 }
-
-/****************************************************************************
- * Name: esp32s3_pmsirqinitialize
- *
- * Description:
- *   Initialize interrupt handler for the PMS violation ISR.
- *
- * Input Parameters:
- *   None.
- *
- * Returned Value:
- *   None.
- *
- ****************************************************************************/
-
-void esp32s3_pmsirqinitialize(void)
-{
-  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_IRAM0_PMS_MONITOR_VIOLATE,
-                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
-  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_DRAM0_PMS_MONITOR_VIOLATE,
-                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
-  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CACHE_CORE0_ACS,
-                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
-  VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_PIF_PMS_MONITOR_VIOLATE,
-                       1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL));
-
-  up_enable_irq(ESP32S3_IRQ_CORE_0_IRAM0_PMS_MONITOR_VIOLATE);
-  up_enable_irq(ESP32S3_IRQ_CORE_0_DRAM0_PMS_MONITOR_VIOLATE);
-  up_enable_irq(ESP32S3_IRQ_CACHE_CORE0_ACS);
-  up_enable_irq(ESP32S3_IRQ_CORE_0_PIF_PMS_MONITOR_VIOLATE);
-}
-
-#endif /* CONFIG_BUILD_PROTECTED */
diff --git a/arch/xtensa/src/esp32s3/esp32s3_userspace.h 
b/arch/xtensa/src/esp32s3/esp32s3_userspace.h
index 0deff1d0f5c..2496042ed2a 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_userspace.h
+++ b/arch/xtensa/src/esp32s3/esp32s3_userspace.h
@@ -46,24 +46,4 @@
 void esp32s3_userspace(void);
 #endif
 
-/****************************************************************************
- * Name: esp32s3_pmsirqinitialize
- *
- * Description:
- *   Initialize interrupt handler for the PMS violation ISR.
- *
- * Input Parameters:
- *   None.
- *
- * Returned Value:
- *   None.
- *
- ****************************************************************************/
-
-#ifdef CONFIG_BUILD_PROTECTED
-void esp32s3_pmsirqinitialize(void);
-#else
-#  define esp32s3_pmsirqinitialize()
-#endif
-
 #endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERSPACE_H */
diff --git a/arch/xtensa/src/esp32s3/esp32s3_wcl.c 
b/arch/xtensa/src/esp32s3/esp32s3_wcl.c
index d66e836b321..f176f25d611 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_wcl.c
+++ b/arch/xtensa/src/esp32s3/esp32s3_wcl.c
@@ -57,6 +57,18 @@
 
 /****************************************************************************
  * Name: esp32s3_wcl_set_vecbase
+ *
+ * Description:
+ *   Set the vector table a world uses, and make the World Controller take
+ *   the value from these registers instead of the reset default.
+ *
+ * Input Parameters:
+ *   world    - The world the table belongs to.
+ *   vecbase  - The address of the table.  It must be aligned to 1 KB.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_wcl_set_vecbase(enum esp32s3_pms_world_e world,
@@ -97,6 +109,18 @@ void esp32s3_wcl_set_vecbase(enum esp32s3_pms_world_e world,
 
 /****************************************************************************
  * Name: esp32s3_wcl_set_world0_entry
+ *
+ * Description:
+ *   Register an address that returns the CPU to World 0 when it is fetched.
+ *   This is how an exception taken in World 1 reaches a kernel handler.
+ *
+ * Input Parameters:
+ *   entry    - Which entry to set, from 1 to WCL_ENTRY_MAX.
+ *   addr     - The address that switches the world.
+ *
+ * Returned Value:
+ *   None.
+ *
  ****************************************************************************/
 
 void esp32s3_wcl_set_world0_entry(uint32_t entry, uintptr_t addr)
diff --git a/arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S 
b/arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S
new file mode 100644
index 00000000000..e6dcb31cc18
--- /dev/null
+++ b/arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S
@@ -0,0 +1,221 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/* The vector table used while the CPU executes in WORLD1, the unprivileged
+ * world, in a kernel build.  The World Controller gives each world its own
+ * vector table base, and this one exists so that WORLD1 needs no execute
+ * permission on the kernel's own vectors.
+ *
+ * Window overflow and underflow are handled here, in WORLD1, because they
+ * only ever touch the interrupted task's own stack and because there is no
+ * way back: entering the kernel table switches the CPU to WORLD0, and only
+ * the level 1 and level 3 exception paths restore the interruptee's world
+ * on the way out (see exception_exit_hook in chip_macros.h).  A window spill
+ * returns with RFWO/RFWU, which would leave the task running privileged.
+ *
+ * Every other vector jumps to its counterpart in the kernel table.  Those
+ * kernel entry points are registered with the World Controller as WORLD0
+ * entry addresses, so fetching one is what switches the CPU to WORLD0 -- the
+ * jump instruction itself still executes in WORLD1, out of this table.
+ *
+ * The layout must match the kernel table in esp32s3_sections.ld, since the
+ * hardware picks the slot by exception type and vector base alone.
+ */
+
+       .file   "esp32s3_world1_vectors.S"
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <arch/irq.h>
+#include <arch/chip/core-isa.h>
+#include <arch/xtensa/xtensa_abi.h>
+#include <arch/xtensa/xtensa_specregs.h>
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+       .section        .world1_vectors.text, "ax"
+       .global         _world1_vectors
+       .type           _world1_vectors, @function
+       .align          1024
+
+_world1_vectors:
+
+/****************************************************************************
+ * Window exception vectors
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: _world1_window_overflow4
+ ****************************************************************************/
+
+       .org    0x0
+       .global _world1_window_overflow4
+_world1_window_overflow4:
+       s32e    a0, a5, -16     /* save a0 to call[j+1]'s stack frame */
+       s32e    a1, a5, -12     /* save a1 to call[j+1]'s stack frame */
+       s32e    a2, a5,  -8     /* save a2 to call[j+1]'s stack frame */
+       s32e    a3, a5,  -4     /* save a3 to call[j+1]'s stack frame */
+       rfwo                    /* rotates back to call[i] position */
+
+/****************************************************************************
+ * Name: _world1_window_underflow4
+ ****************************************************************************/
+
+       .org    0x40
+       .global _world1_window_underflow4
+_world1_window_underflow4:
+       l32e    a0, a5, -16     /* restore a0 from call[i+1]'s stack frame */
+       l32e    a1, a5, -12     /* restore a1 from call[i+1]'s stack frame */
+       l32e    a2, a5,  -8     /* restore a2 from call[i+1]'s stack frame */
+       l32e    a3, a5,  -4     /* restore a3 from call[i+1]'s stack frame */
+       rfwu
+
+/****************************************************************************
+ * Name: _world1_window_overflow8
+ ****************************************************************************/
+
+       .org    0x80
+       .global _world1_window_overflow8
+_world1_window_overflow8:
+       s32e    a0, a9, -16     /* save a0 to call[j+1]'s stack frame */
+       l32e    a0, a1, -12     /* a0 <- call[j-1]'s sp
+                                  (used to find end of call[j]'s frame) */
+       s32e    a1, a9, -12     /* save a1 to call[j+1]'s stack frame */
+       s32e    a2, a9,  -8     /* save a2 to call[j+1]'s stack frame */
+       s32e    a3, a9,  -4     /* save a3 to call[j+1]'s stack frame */
+       s32e    a4, a0, -32     /* save a4 to call[j]'s stack frame */
+       s32e    a5, a0, -28     /* save a5 to call[j]'s stack frame */
+       s32e    a6, a0, -24     /* save a6 to call[j]'s stack frame */
+       s32e    a7, a0, -20     /* save a7 to call[j]'s stack frame */
+       rfwo                    /* rotates back to call[i] position */
+
+/****************************************************************************
+ * Name: _world1_window_underflow8
+ ****************************************************************************/
+
+       .org    0xc0
+       .global _world1_window_underflow8
+_world1_window_underflow8:
+       l32e    a0, a9, -16     /* restore a0 from call[i+1]'s stack frame */
+       l32e    a1, a9, -12     /* restore a1 from call[i+1]'s stack frame */
+       l32e    a2, a9,  -8     /* restore a2 from call[i+1]'s stack frame */
+       l32e    a7, a1, -12     /* a7 <- call[i-1]'s sp
+                                  (used to find end of call[i]'s frame) */
+       l32e    a3, a9,  -4     /* restore a3 from call[i+1]'s stack frame */
+       l32e    a4, a7, -32     /* restore a4 from call[i]'s stack frame */
+       l32e    a5, a7, -28     /* restore a5 from call[i]'s stack frame */
+       l32e    a6, a7, -24     /* restore a6 from call[i]'s stack frame */
+       l32e    a7, a7, -20     /* restore a7 from call[i]'s stack frame */
+       rfwu
+
+/****************************************************************************
+ * Name: _world1_window_overflow12
+ ****************************************************************************/
+
+       .org    0x100
+       .global _world1_window_overflow12
+_world1_window_overflow12:
+       s32e    a0,  a13, -16   /* save a0 to call[j+1]'s stack frame */
+       l32e    a0,  a1,  -12   /* a0 <- call[j-1]'s sp
+                                  (used to find end of call[j]'s frame) */
+       s32e    a1,  a13, -12   /* save a1 to call[j+1]'s stack frame */
+       s32e    a2,  a13,  -8   /* save a2 to call[j+1]'s stack frame */
+       s32e    a3,  a13,  -4   /* save a3 to call[j+1]'s stack frame */
+       s32e    a4,  a0,  -48   /* save a4 to end of call[j]'s stack frame */
+       s32e    a5,  a0,  -44   /* save a5 to end of call[j]'s stack frame */
+       s32e    a6,  a0,  -40   /* save a6 to end of call[j]'s stack frame */
+       s32e    a7,  a0,  -36   /* save a7 to end of call[j]'s stack frame */
+       s32e    a8,  a0,  -32   /* save a8 to end of call[j]'s stack frame */
+       s32e    a9,  a0,  -28   /* save a9 to end of call[j]'s stack frame */
+       s32e    a10, a0,  -24   /* save a10 to end of call[j]'s stack frame */
+       s32e    a11, a0,  -20   /* save a11 to end of call[j]'s stack frame */
+       rfwo                    /* rotates back to call[i] position */
+
+/****************************************************************************
+ * Name: _world1_window_underflow12
+ ****************************************************************************/
+
+       .org    0x140
+       .global _world1_window_underflow12
+_world1_window_underflow12:
+       l32e    a0,  a13, -16   /* restore a0 from call[i+1]'s stack frame */
+       l32e    a1,  a13, -12   /* restore a1 from call[i+1]'s stack frame */
+       l32e    a2,  a13,  -8   /* restore a2 from call[i+1]'s stack frame */
+       l32e    a11, a1,  -12   /* a11 <- call[i-1]'s sp
+                                  (used to find end of call[i]'s frame) */
+       l32e    a3,  a13,  -4   /* restore a3 from call[i+1]'s stack frame */
+       l32e    a4,  a11, -48   /* restore a4 from end of call[i]'s stack frame 
*/
+       l32e    a5,  a11, -44   /* restore a5 from end of call[i]'s stack frame 
*/
+       l32e    a6,  a11, -40   /* restore a6 from end of call[i]'s stack frame 
*/
+       l32e    a7,  a11, -36   /* restore a7 from end of call[i]'s stack frame 
*/
+       l32e    a8,  a11, -32   /* restore a8 from end of call[i]'s stack frame 
*/
+       l32e    a9,  a11, -28   /* restore a9 from end of call[i]'s stack frame 
*/
+       l32e    a10, a11, -24   /* restore a10 from end of call[i]'s stack 
frame */
+       l32e    a11, a11, -20   /* restore a11 from end of call[i]'s stack 
frame */
+       rfwu
+
+/****************************************************************************
+ * Medium-/High-priority interrupt vectors
+ ****************************************************************************/
+
+       .org    0x180
+       j       _xtensa_level2_vector
+
+       .org    0x1c0
+       j       _xtensa_level3_vector
+
+/* 0x200 and 0x240 are the level 4 and level 5 vectors.  This configuration
+ * has no interrupt above level 3, and the kernel table leaves those slots
+ * empty as well.
+ */
+
+       .org    0x280
+       j       _debug_exception_vector
+
+       .org    0x2c0
+       j       _xtensa_nmi_vector
+
+/****************************************************************************
+ * General exception vectors
+ ****************************************************************************/
+
+/* A WORLD1 task always takes the user exception vector, since it runs with
+ * PS.UM set.  The kernel slot is jumped to anyway, so that a table entry is
+ * never a hole.
+ */
+
+       .org    0x300
+       j       _kernel_exception_vector
+
+       .org    0x340
+       j       _user_exception_vector
+
+       .org    0x3c0
+       j       _double_exception_vector
+
+       .org    0x400
+
+       .size   _world1_vectors, . - _world1_vectors
diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig 
b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig
index e7b65090a03..0e515654878 100644
--- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig
+++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig
@@ -57,6 +57,7 @@ CONFIG_ESP32S3_SPIRAM=y
 CONFIG_ESP32S3_SPIRAM_MODE_OCT=y
 CONFIG_ESP32S3_UART0=y
 CONFIG_ESP32S3_WCL=y
+CONFIG_EXAMPLES_PFFAULT=y
 CONFIG_FS_PROCFS=y
 CONFIG_FS_ROMFS=y
 CONFIG_HAVE_CXX=y

Reply via email to