From 2391a81049461a0a310b75874b395592dcfda65f Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 09:36:56 +0200 Subject: [PATCH 01/22] arch/xtensa: Provide vfork(). Xtensa selected neither fork primitive, so vfork() was simply absent. This wires it onto the two-primitive semantics. There is no assembly entry point and none is needed. Every exception entry already runs SPILL_ALL_WINDOWS, so the whole context of the calling thread is in its exception frame and copying its stack copies a complete frame chain. A flat build reaches that frame through SYS_save_context, issued inline so that the recorded stack pointer belongs to a frame that stays alive for the whole operation; a build with syscalls reaches it through xcp.sregs, recorded by xtensa_swint() for the duration of the call. The stack copy needs more than a relocated stack pointer here. A windowed ABI stores each frame's caller stack pointer absolutely, in the base save area below the frame, so a copy taken at a different address still names the parent throughout and the child's first retw would underflow onto the parent's stack. xtensa_fork_rebase() walks that chain and adds the relocation offset to each link. The copy also starts one base save area below the stack pointer rather than at it, because the frame the child resumes into keeps its caller's spilled a0-a3 there. Ported from the per-architecture work, reduced to the two primitives. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/Kconfig | 1 + arch/xtensa/include/irq.h | 10 + arch/xtensa/src/common/Make.defs | 5 + arch/xtensa/src/common/xtensa_fork.c | 455 +++++++++++++++++++ arch/xtensa/src/common/xtensa_initialstate.c | 33 +- arch/xtensa/src/common/xtensa_swint.c | 9 + 6 files changed, 504 insertions(+), 9 deletions(-) create mode 100644 arch/xtensa/src/common/xtensa_fork.c diff --git a/arch/Kconfig b/arch/Kconfig index 60039f1991e4e..e5df9c22a3393 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -158,6 +158,7 @@ config ARCH_X86_64 config ARCH_XTENSA bool "Xtensa" select ARCH_HAVE_BACKTRACE + select ARCH_HAVE_VFORK select ARCH_HAVE_CPUINFO select ARCH_HAVE_INTERRUPTSTACK select ARCH_HAVE_STACKCHECK diff --git a/arch/xtensa/include/irq.h b/arch/xtensa/include/irq.h index 537262ebae3ae..5b2ee5551dae2 100644 --- a/arch/xtensa/include/irq.h +++ b/arch/xtensa/include/irq.h @@ -217,6 +217,16 @@ struct xcptcontext uint32_t *regs; #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 + * runs as ordinary C code long after the exception has been dispatched, so + * this is the only way for one to reach the registers of the thread that + * made the call -- vfork() needs the caller's stack pointer and return + * address to give the child a copy. + */ + + uint32_t *sregs; + /* The following array holds the return address and the exc_return value * needed to return from each nested system call. */ diff --git a/arch/xtensa/src/common/Make.defs b/arch/xtensa/src/common/Make.defs index 3e1ea13a78582..e91fc78b5e69d 100644 --- a/arch/xtensa/src/common/Make.defs +++ b/arch/xtensa/src/common/Make.defs @@ -39,6 +39,11 @@ CMN_CSRCS += xtensa_modifyreg8.c xtensa_modifyreg16.c xtensa_modifyreg32.c CMN_CSRCS += xtensa_mpu.c xtensa_nputs.c xtensa_oneshot.c xtensa_perf.c CMN_CSRCS += xtensa_releasestack.c xtensa_registerdump.c CMN_CSRCS += xtensa_swint.c xtensa_stackframe.c + +ifneq ($(CONFIG_ARCH_HAVE_FORK)$(CONFIG_ARCH_HAVE_VFORK),) +CMN_CSRCS += xtensa_fork.c +endif + CMN_CSRCS += xtensa_saveusercontext.c CMN_CSRCS += xtensa_usestack.c xtensa_tcbinfo.c diff --git a/arch/xtensa/src/common/xtensa_fork.c b/arch/xtensa/src/common/xtensa_fork.c new file mode 100644 index 0000000000000..e45ed5ba8c079 --- /dev/null +++ b/arch/xtensa/src/common/xtensa_fork.c @@ -0,0 +1,455 @@ +/**************************************************************************** + * arch/xtensa/src/common/xtensa_fork.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 + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "sched/sched.h" +#include "xtensa.h" +#include "chip_macros.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The base save area: the 16 bytes below a frame's stack pointer, holding + * the spilled a0-a3 of that frame's caller. Window overflow writes them and + * underflow reads them back, so it is the link that makes the frame chain + * walkable, and the reason a copy starting at the stack pointer is missing + * its first link. + */ + +#define BASE_SAVE_AREA 16 +#define BASE_SAVE_A1 1 /* a0, a1, a2, a3 -- a1 is the caller's SP */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Everything a child is built from: the register context of the thread that + * called, and where that thread was. There are two ways to come by it, and + * they differ in more than provenance -- see xtensa_fork_direct() below. + */ + +struct fork_snapshot_s +{ + FAR const uint32_t *regs; /* The caller's full register context */ + uintptr_t usp; /* The caller's user stack pointer */ + uintptr_t pc; /* Where the child resumes */ + uint32_t a2; /* What the child sees returned in A2 */ +#ifndef CONFIG_BUILD_FLAT + uintptr_t ctx; /* The caller's privilege, from its syscall */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: xtensa_fork_rebase + * + * Description: + * Rebase the frame chain of a relocated stack copy. + * + * A windowed ABI stores each frame's caller stack pointer absolutely, in + * the base save area at [sp - 16), so a copy taken at a different address + * still names the parent and the child's first retw would underflow onto + * the parent's stack. Each link gets the relocation offset added. + * + * Spilled a4-a15 and any other stack addresses in the copy are left alone. + * Those are data; the chain is what the child needs to return at all. + * + * Input Parameters: + * newsp - The child's stack pointer + * usp - The parent's stack pointer, which newsp is a relocation of + * stacktop - The top of the parent's stack; the walk ends there + * offset - newsp - usp, the amount every link moves by + * + ****************************************************************************/ + +static void xtensa_fork_rebase(uintptr_t newsp, uintptr_t usp, + uintptr_t stacktop, intptr_t offset) +{ + uintptr_t csp = newsp; /* The frame being fixed, in the child's copy */ + uintptr_t psp = usp; /* The same frame, as the parent addresses it */ + + while (psp < stacktop) + { + FAR uint32_t *save = (FAR uint32_t *)(csp - BASE_SAVE_AREA); + uintptr_t caller = save[BASE_SAVE_A1]; + + /* The chain grows towards the top of the stack and ends there. Stop + * on anything else rather than following it: the outermost frame's + * save area was never written by an overflow, so what is in it is + * whatever the stack was coloured with. + */ + + if (caller <= psp || caller > stacktop) + { + break; + } + + save[BASE_SAVE_A1] = (uint32_t)(uintptr_t)((intptr_t)caller + offset); + + psp = caller; + csp = (uintptr_t)((intptr_t)caller + offset); + } +} + +/**************************************************************************** + * Name: xtensa_fork_stack + * + * Description: + * Give the child its stack pointer, copying the parent's frames if needed. + * + * A fork() child keeps the parent's stack addresses inside its own address + * environment, so it has nothing to copy. A vfork() child gets a stack of + * its own, which needs the copy and xtensa_fork_rebase() with it. + * + * The copy starts one base save area below the stack pointer: the frame + * the child resumes into keeps its caller's spilled a0-a3 there. + * + * Input Parameters: + * parent - The parent task's TCB + * child - The child task's TCB + * usp - The parent's stack pointer + * + * Returned Value: + * The child's stack pointer. + * + ****************************************************************************/ + +static uintptr_t xtensa_fork_stack(FAR struct tcb_s *parent, + FAR struct tcb_s *child, + uintptr_t usp) +{ + uintptr_t stacktop; + uintptr_t stackutil; + uintptr_t newtop; + uintptr_t newsp; + + stacktop = (uintptr_t)parent->stack_base_ptr + parent->adj_stack_size; + DEBUGASSERT(stacktop > usp); + + if (child->stack_base_ptr == parent->stack_base_ptr) + { + /* The child is running at the parent's stack addresses, inside its + * own duplicated address environment. There is nothing to relocate: + * every stack address the child inherits is still the address it + * names. + */ + + return usp; + } + + DEBUGASSERT(usp - BASE_SAVE_AREA >= (uintptr_t)parent->stack_base_ptr); + + stackutil = stacktop - (usp - BASE_SAVE_AREA); + newtop = (uintptr_t)child->stack_base_ptr + child->adj_stack_size; + + /* The copy has to fit, and the register save area goes below it when the + * child has no kernel stack to put it on -- see xtensa_fork(). + */ + + DEBUGASSERT(newtop - stackutil > + (uintptr_t)child->stack_base_ptr + XCPTCONTEXT_SIZE); + + newsp = newtop - stackutil + BASE_SAVE_AREA; + + memcpy((FAR void *)(newsp - BASE_SAVE_AREA), + (FAR const void *)(usp - BASE_SAVE_AREA), stackutil); + + xtensa_fork_rebase(newsp, usp, stacktop, (intptr_t)(newsp - usp)); + + return newsp; +} + +/**************************************************************************** + * Name: xtensa_fork + * + * Description: + * The common core of the two primitives. They differ in the flag handed + * to nxtask_setup_fork(), which is where the memory semantics are decided, + * and in where their snapshot of the caller comes from. + * + * Input Parameters: + * vfork - true for vfork(), false for fork() + * snap - The caller's context; see struct fork_snapshot_s + * + * Returned Value: + * The pid of the child, or ERROR on failure. + * + ****************************************************************************/ + +static pid_t xtensa_fork(bool vfork, FAR const struct fork_snapshot_s *snap) +{ + FAR struct tcb_s *parent = this_task(); + FAR struct tcb_s *child; + uintptr_t newsp; + uintptr_t regstop; + + DEBUGASSERT(snap->regs != NULL && snap->pc != 0); + + /* Allocate and initialise a TCB for the child. The start address is only + * bookkeeping here: what the child actually resumes with is the register + * context assembled below. + */ + + child = nxtask_setup_fork((start_t)snap->pc, vfork); + if (child == NULL) + { + sinfo("nxtask_setup_fork failed\n"); + return (pid_t)ERROR; + } + + newsp = xtensa_fork_stack(parent, child, snap->usp); + + /* Where the child's register context is restored from. */ + +#ifdef CONFIG_ARCH_KERNEL_STACK + if (child->xcp.kstack != NULL) + { + /* On the kernel stack: the child is resumed by the same path a system + * call returns through, which runs in kernel context. + */ + + regstop = (uintptr_t)child->xcp.ktopstk; + } + else +#endif + { + /* There is no kernel stack, so it goes on the child's own stack, below + * the base save area rather than at the stack pointer. Writing + * XCPTCONTEXT_SIZE bytes down from newsp would destroy the very words + * the child's first `retw' reads. It is dead memory once the context + * has been restored, so the child may then grow over it. + * + * This is only sound because a child without a kernel stack always has + * a stack of its own: writing here on a shared stack would land in + * the parent's frames. See xtensa_fork_stack(). + */ + + DEBUGASSERT(child->stack_base_ptr != parent->stack_base_ptr); + regstop = newsp - BASE_SAVE_AREA; + } + + child->xcp.regs = (FAR uint32_t *)(regstop - XCPTCONTEXT_SIZE); + + /* Start from the parent's context, then correct what must differ */ + + memcpy(child->xcp.regs, snap->regs, XCPTCONTEXT_SIZE); + + /* The child is not returning the way the parent will: it is being + * started. Give it directly what its resume path would have produced -- + * the address to resume at, its privilege, its own stack, and the value + * the call returns to it. + * + * The privilege matters most. The frame copied above carries the world + * the *exception* left in it, not the caller's; taking it would resume an + * unprivileged process privileged. + */ + + child->xcp.regs[REG_PC] = snap->pc; + child->xcp.regs[REG_A1] = newsp; + child->xcp.regs[REG_A2] = snap->a2; + +#ifndef CONFIG_BUILD_FLAT + xtensa_restoreprivilege(child->xcp.regs, snap->ctx); +#endif + +#ifdef CONFIG_ARCH_KERNEL_STACK + /* The parent is inside a system call, so its saved user stack pointer is + * held aside in ustkptr and A1 names the kernel stack. The child is not + * inside that call and must not inherit it. + */ + + child->xcp.ustkptr = NULL; +#endif + + /* And start the child. On failure nxtask_start_fork() discards the TCB + * through nxtask_abort_fork(). + */ + + return nxtask_start_fork(child, vfork); +} + +#ifdef CONFIG_LIB_SYSCALL +/**************************************************************************** + * Name: xtensa_fork_syscall + * + * Description: + * Fork from the system call that brought the caller into the kernel. + * + * There is no assembly counterpart to this, and it does not need one. + * Other architectures enter through a stub that spills the caller's + * registers into a struct fork_s, because a C function cannot see its + * caller's callee-saved registers. Here the system call has already done + * better than that: _xtensa_context_save() runs SPILL_ALL_WINDOWS on + * every exception entry, so every live register window of the calling + * thread has been written to its stack and the whole context is in the + * exception frame. Copying the stack therefore copies a complete and + * self-consistent frame chain. xcp.sregs is that frame (see + * xtensa_swint()). + * + * The child resumes where the system call would have returned, and A2 is + * the value the call yields -- 0, as fork() and vfork() return to a child. + * + ****************************************************************************/ + +static pid_t xtensa_fork_syscall(bool vfork) +{ + FAR struct tcb_s *parent = this_task(); + struct fork_snapshot_s snap; + int index; + + /* This runs as the body of a system call, so the caller's own state is not + * simply what is in the exception frame. + */ + + DEBUGASSERT(parent->xcp.sregs != NULL); + index = (int)parent->xcp.nsyscalls - 1; + DEBUGASSERT(index >= 0); + + snap.regs = parent->xcp.sregs; + snap.pc = parent->xcp.syscall[index].sysreturn; + snap.a2 = 0; +#ifndef CONFIG_BUILD_FLAT + snap.ctx = parent->xcp.syscall[index].int_ctx; +#endif + + /* The stack pointer to work from is the *user* one. A kernel build moves + * the outermost system call onto the thread's kernel stack and holds the + * user stack pointer aside in ustkptr, so the A1 in the exception frame + * names the kernel stack from here on -- measuring the parent's user stack + * against it would produce a nonsense length. + */ + +#ifdef CONFIG_ARCH_KERNEL_STACK + snap.usp = parent->xcp.ustkptr != NULL ? + (uintptr_t)parent->xcp.ustkptr : snap.regs[REG_A1]; +#else + snap.usp = snap.regs[REG_A1]; +#endif + + return xtensa_fork(vfork, &snap); +} + +#else /* CONFIG_LIB_SYSCALL */ + +/**************************************************************************** + * Name: xtensa_fork_direct + * + * Description: + * Fork a caller that did not arrive through a system call, which in a flat + * build is every caller. + * + * SYS_save_context spills every window and copies out the exception frame, + * giving the same snapshot a system call would have left behind. + * + * It is issued inline rather than through up_saveusercontext() because the + * snapshot records the stack pointer of the frame that issues it, and the + * child resumes on a copy of the stack from that point up. Everything + * called from here runs below this frame, so the copied region stays + * valid; a helper's frame would be dead and reused by the time the child + * ran on it. + * + * The child resumes after the syscall rather than at a syscall return, so + * the PC comes from the frame, and A2 is 1 so the branch below can tell + * parent from child. + * + * Returned Value: + * The pid of the child to the parent, 0 to the child, ERROR on failure. + * + ****************************************************************************/ + +static pid_t xtensa_fork_direct(bool vfork) +{ + uint32_t regs[XCPTCONTEXT_REGS] aligned_data(16); + struct fork_snapshot_s snap; + + if (sys_call1(SYS_save_context, (uintptr_t)regs) != 0) + { + /* The child, resumed from the context captured just above with A2 set + * to 1. It has nothing to do but leave. + */ + + return 0; + } + + snap.regs = regs; + snap.usp = regs[REG_A1]; + snap.pc = regs[REG_PC]; + snap.a2 = 1; + + return xtensa_fork(vfork, &snap); +} +#endif /* CONFIG_LIB_SYSCALL */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_fork + * + * Description: + * The architecture half of fork() and vfork(). With vfork true the child + * shares the parent's memory and runs on a private copy of its stack, and + * the parent is suspended until the child leaves. With vfork false the + * child receives its own copy of the parent's memory at the same virtual + * addresses. + * + * Input Parameters: + * vfork - true for vfork(), false for fork() + * + * Returned Value: + * Upon successful completion, 0 is returned to the child and the pid of + * the child to the parent. Otherwise, -1 is returned to the parent, no + * child is created, and errno is set to indicate the error. + * + ****************************************************************************/ + +pid_t up_fork(bool vfork) +{ +#ifdef CONFIG_LIB_SYSCALL + return xtensa_fork_syscall(vfork); +#else + return xtensa_fork_direct(vfork); +#endif +} diff --git a/arch/xtensa/src/common/xtensa_initialstate.c b/arch/xtensa/src/common/xtensa_initialstate.c index 1a0a14579c2e6..76549e33b1a48 100644 --- a/arch/xtensa/src/common/xtensa_initialstate.c +++ b/arch/xtensa/src/common/xtensa_initialstate.c @@ -78,7 +78,7 @@ void up_initial_state(struct tcb_s *tcb) { struct xcptcontext *xcp = &tcb->xcp; #ifdef CONFIG_SCHED_THREAD_LOCAL - const uint32_t base = ALIGN_UP((uint32_t)&_rodata_reserved_align, + const uintptr_t base = ALIGN_UP((uintptr_t)&_rodata_reserved_align, TCB_SIZE); #endif @@ -107,9 +107,24 @@ void up_initial_state(struct tcb_s *tcb) /* Initialize the context registers to stack top */ - xcp->regs = (void *)((uint32_t)tcb->stack_base_ptr + - tcb->adj_stack_size - - XCPTCONTEXT_SIZE); +#ifdef CONFIG_ARCH_KERNEL_STACK + if (xcp->kstack != NULL) + { + /* Put the frame on the thread's kernel stack rather than its user + * stack. It is restored in kernel context, and leaving it in user + * memory means the thread can scribble on the register set it is + * about to be started with. + */ + + xcp->regs = (void *)((uintptr_t)xcp->ktopstk - XCPTCONTEXT_SIZE); + } + else +#endif + { + xcp->regs = (void *)((uintptr_t)tcb->stack_base_ptr + + tcb->adj_stack_size - + XCPTCONTEXT_SIZE); + } /* Initialize the xcp registers */ @@ -117,9 +132,9 @@ void up_initial_state(struct tcb_s *tcb) /* Set initial values of registers */ - xcp->regs[REG_PC] = (uint32_t)tcb->start; /* Task entrypoint */ - xcp->regs[REG_A0] = 0; /* To terminate GDB backtrace */ - xcp->regs[REG_A1] = (uint32_t)tcb->stack_base_ptr + /* Physical top of stack frame */ + xcp->regs[REG_PC] = (uintptr_t)tcb->start; /* Task entrypoint */ + xcp->regs[REG_A0] = 0; /* To terminate GDB backtrace */ + xcp->regs[REG_A1] = (uintptr_t)tcb->stack_base_ptr + /* Physical top of stack frame */ tcb->adj_stack_size; /* Each task access the TLS variables using the THREADPTR register plus an @@ -228,8 +243,8 @@ void up_initial_state(struct tcb_s *tcb) #ifdef CONFIG_SCHED_THREAD_LOCAL xcp->regs[REG_THREADPTR] = (uintptr_t)tcb->stack_alloc_ptr + sizeof(struct tls_info_s) - - ((uint32_t)&_thread_local_start - - (uint32_t)&_rodata_reserved_start) - base; + ((uintptr_t)&_thread_local_start - + (uintptr_t)&_rodata_reserved_start) - base; #endif /* Set initial PS to int level 0, user mode. */ diff --git a/arch/xtensa/src/common/xtensa_swint.c b/arch/xtensa/src/common/xtensa_swint.c index 4b38987e17e38..035811f249260 100644 --- a/arch/xtensa/src/common/xtensa_swint.c +++ b/arch/xtensa/src/common/xtensa_swint.c @@ -66,6 +66,15 @@ int xtensa_swint(int irq, void *context, void *arg) cmd = regs[REG_A2]; +#ifdef CONFIG_LIB_SYSCALL + /* Record the caller's register context for the duration of the call. A + * system call body runs as C code after this exception has returned, so + * this is how it reaches the registers of the thread that called it. + */ + + tcb->xcp.sregs = regs; +#endif + /* The syscall software interrupt is called with A2 = system call command * and A3..A9 = variable number of arguments depending on the system call. */ From b1daf042973e05c92d3cfaf081f0d1d276c3b970 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 14:16:02 +0200 Subject: [PATCH 02/22] mm/pgalloc: support 32 KB and 64 KB page sizes Add CONFIG_MM_PGSIZE == 32768 and 65536 to the page-size switch (and the Kconfig help text). The 64 KB size matches the ESP32-S3 cache-MMU page granularity, so an address-environment port there can use one mm_pgalloc() page per cache-MMU page (naturally 64 KB-aligned by the granule allocator) instead of coalescing several smaller pages. Inert for existing configs: MM_PGSIZE is only used when CONFIG_MM_PGALLOC is enabled (BUILD_KERNEL). Assisted-by: Claude Opus 4.8 (1M context) Signed-off-by: Marco Casaroli --- include/nuttx/pgalloc.h | 6 ++++++ mm/Kconfig | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/nuttx/pgalloc.h b/include/nuttx/pgalloc.h index 33f6a937b4593..bec03d9b4e719 100644 --- a/include/nuttx/pgalloc.h +++ b/include/nuttx/pgalloc.h @@ -69,6 +69,12 @@ #elif CONFIG_MM_PGSIZE == 16384 # define MM_PGSIZE 16384 # define MM_PGSHIFT 14 +#elif CONFIG_MM_PGSIZE == 32768 +# define MM_PGSIZE 32768 +# define MM_PGSHIFT 15 +#elif CONFIG_MM_PGSIZE == 65536 +# define MM_PGSIZE 65536 +# define MM_PGSHIFT 16 #else # error CONFIG_MM_PGSIZE not supported #endif diff --git a/mm/Kconfig b/mm/Kconfig index aebba1d0130fe..e0b8441a6ae71 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -301,9 +301,10 @@ config MM_PGSIZE int "Page Size" default 4096 ---help--- - The MMU page size. Must be one of {1024, 2048, 4096, 8192, or - 16384}. This is easily extensible, but only those values are - currently support. + The MMU page size. Must be one of {1024, 2048, 4096, 8192, 16384, + 32768, or 65536}. This is easily extensible, but only those values + are currently support. 64 KB (65536) matches the ESP32-S3 cache-MMU + page size. config DEBUG_PGALLOC bool "Page Allocator Debug" From 4f564e44ea8dd061d325e5541f69263ade0e7fef Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 15:17:24 +0200 Subject: [PATCH 03/22] 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) Signed-off-by: Marco Casaroli --- 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 ++++++ .../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(-) create mode 100644 arch/xtensa/src/common/xtensa_addrenv_kstack.c diff --git a/arch/xtensa/include/irq.h b/arch/xtensa/include/irq.h index 5b2ee5551dae2..d2005dcbcad92 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 31425e38fa3bc..522e622be0cf3 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 e91fc78b5e69d..82be3de9de413 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 89b21b78db07e..5dd1458e9822a 100644 --- a/arch/xtensa/src/common/crt0.c +++ b/arch/xtensa/src/common/crt0.c @@ -25,6 +25,7 @@ ****************************************************************************/ #include +#include #include #include @@ -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 0000000000000..57bb749588062 --- /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 + +#include +#include +#include + +#include +#include +#include + +#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 76549e33b1a48..b64989f0ba5cf 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 035811f249260..08eeb5b9726f3 100644 --- a/arch/xtensa/src/common/xtensa_swint.c +++ b/arch/xtensa/src/common/xtensa_swint.c @@ -32,6 +32,7 @@ #include #include +#include #include #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 ****************************************************************************/ @@ -111,6 +125,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"); @@ -160,6 +192,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. */ @@ -284,7 +329,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 */ @@ -296,6 +345,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 @@ -322,6 +420,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 @@ -362,6 +474,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 cd4e593230cff..26dcb1f053591 100644 --- a/arch/xtensa/src/lx7/Toolchain.defs +++ b/arch/xtensa/src/lx7/Toolchain.defs @@ -240,7 +240,16 @@ LDMODULEFLAGS = -r -T $(call CONVERT_PATH,$(TOPDIR)/libs/libc/elf/gnu-elf.ld) CELFFLAGS = $(CFLAGS) -fvisibility=hidden -mtext-section-literals CXXELFFLAGS = $(CXXFLAGS) -fvisibility=hidden -mtext-section-literals -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, From 0c0514194e4630b8fc9cba5e1ee3c2bad1972257 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 14:16:01 +0200 Subject: [PATCH 04/22] xtensa/esp32s3: add recoverable cache-attribute fault dispatcher (Unit B) Route the precise cache-attribute permission faults -- Load/Store/InstrFetch Prohibited (EXCCAUSE 28/29/20) -- from xtensa_user() to a new dispatcher, esp32s3_pagefault_dispatch(). On a serviced fault the register frame is returned so the exception vector's RFE re-executes the faulting instruction; otherwise it declines to the existing panic path. Gated by CONFIG_ESP32S3_PAGEFAULT (default n, depends on BUILD_PROTECTED); the build is unchanged when the option is off. This is the recoverable-fault primitive the address-environment / demand-paging work builds on. Proven on the ESP32-S3-DevKitC WROOM-2: - A precise LoadProhibited carries a tracking EXCVADDR (the exact faulting address), and RFE cleanly re-executes the faulted load on return -- verified with CONFIG_ESP32S3_PAGEFAULT_SELFTEST (the identical instruction restarts three times, then steps past, and the task resumes with the shell alive). - ESP32-S3 PMS (World Controller) permission violations are NOT delivered as these precise causes; they raise the asynchronous DRAM0/IRAM0 PMS-monitor interrupt, so PMS is an isolation (kill) mechanism, not a restartable one. No regression: esp32s3-devkit:knsh (WROOM-2) boots to nsh and ostest passes with the option enabled. Assisted-by: Claude Opus 4.8 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/Kconfig | 28 ++++ arch/xtensa/src/esp32s3/Make.defs | 4 + arch/xtensa/src/esp32s3/esp32s3_pagefault.c | 135 ++++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_pagefault.h | 62 +++++++++ arch/xtensa/src/esp32s3/esp32s3_user.c | 28 ++++ 5 files changed, 257 insertions(+) create mode 100644 arch/xtensa/src/esp32s3/esp32s3_pagefault.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_pagefault.h diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index 23c017e1acd70..62d580abaeabf 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -925,6 +925,34 @@ config ESP32S3_WCL select ARCH_USE_MPU select XTENSA_HAVE_GENERAL_EXCEPTION_HOOKS if BUILD_PROTECTED +config ESP32S3_PAGEFAULT + bool "Recoverable PMS permission faults" + default n + depends on BUILD_PROTECTED + ---help--- + Route the precise Load/Store/InstrFetch Prohibited exceptions raised + by PMS (memory-protection) permission violations through a + recoverable-fault dispatcher instead of panicking unconditionally. + This is the foundation for guard pages, lazy stack/heap growth and, + ultimately, demand paging / copy-on-write on the ESP32-S3. + +if ESP32S3_PAGEFAULT + +config ESP32S3_PAGEFAULT_SELFTEST + bool "Recoverable-fault self-test" + default n + ---help--- + Prove the recoverable-fault primitive on silicon. A load from + 0x80000000 raises a precise LoadProhibited (EXCCAUSE 28, "cache + attribute does not allow load"); the fault dispatcher lets the RFE + re-execute the identical faulting instruction several times (proving + a faulted precise access restarts cleanly) before stepping past it, + reporting the result over the console. Trigger it from a user task, + e.g. with examples/pffault: nsh> pffault r 0x80000000 + For bring-up and evaluation only. + +endif # ESP32S3_PAGEFAULT + config ESP32S3_LCD bool "LCD" default n diff --git a/arch/xtensa/src/esp32s3/Make.defs b/arch/xtensa/src/esp32s3/Make.defs index b306e15c46cd1..e80bf885ba62a 100644 --- a/arch/xtensa/src/esp32s3/Make.defs +++ b/arch/xtensa/src/esp32s3/Make.defs @@ -43,6 +43,10 @@ ifeq ($(CONFIG_BUILD_PROTECTED),y) CHIP_CSRCS += esp32s3_userspace.c endif +ifeq ($(CONFIG_ESP32S3_PAGEFAULT),y) +CHIP_CSRCS += esp32s3_pagefault.c +endif + ifeq ($(CONFIG_SMP),y) CHIP_CSRCS += esp32s3_cpuidlestack.c esp32s3_cpustart.c esp32s3_intercpu_interrupt.c endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c new file mode 100644 index 0000000000000..e9f9c64472f28 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c @@ -0,0 +1,135 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_pagefault.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "xtensa.h" +#include "sched/sched.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_ESP32S3_PAGEFAULT_SELFTEST +/* Runtime self-test: proof of the recoverable-fault primitive on silicon. + * A load of this out-of-cache-region address raises a precise LoadProhibited + * (EXCCAUSE 28, "cache attribute does not allow load") whose EXCVADDR tracks + * the address exactly. The dispatcher returns "serviced" WITHOUT making the + * address accessible for the first PF_SELFTEST_REPEATS re-executions, so the + * exception vector's RFE re-runs the identical faulting instruction. + * Being re-entered that many times for one instruction proves that RFE + * cleanly restarts a faulted precise access (the write-buffer / prefetch + * corner case that gates recoverable page faults); it then steps the saved + * PC past the 2-byte l32i.n so the faulting task resumes. Trigger it from a + * user task, e.g. examples/pffault: nsh> pffault r 0x80000000 + */ + +# define PF_SELFTEST_VADDR 0x80000000ul +# define PF_SELFTEST_REPEATS 3 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static volatile int g_pf_selftest_hits; +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pagefault_dispatch + * + * Description: + * Offered the precise, restartable exceptions raised by a cache-attribute + * permission violation (EXCCAUSE Load/Store/InstrFetch Prohibited). The + * faulting virtual address is in regs[REG_EXCVADDR] and the faulting PC in + * regs[REG_PC]; both are populated by the common Xtensa user exception + * handler (frame layout: NuttX REG_*). + * + * This unit establishes the recoverable-fault primitive. The full + * servicing (map a page / restore a cache attribute, then RFE-restart) is + * built on top in the addrenv / demand-paging units; here the dispatcher + * reports the fault (with its tracking EXCVADDR) and declines to service + * it, except under the self-test which proves the RFE-restart on silicon. + * + * Returned Value: + * OK if the faulting instruction may be (re-)executed via RFE; a negated + * errno otherwise (the caller then panics / aborts the faulting task). + * + ****************************************************************************/ + +int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs) +{ + uintptr_t vaddr = (uintptr_t)regs[REG_EXCVADDR]; + uintptr_t pc = (uintptr_t)regs[REG_PC]; + +#ifdef CONFIG_ESP32S3_PAGEFAULT_SELFTEST + if (exccause == EXCCAUSE_LOAD_PROHIBITED && vaddr == PF_SELFTEST_VADDR) + { + g_pf_selftest_hits++; + + _alert("PAGEFAULT SELFTEST: restart #%d precise LoadProhibited " + "EXCVADDR=%08x PC=%08x\n", + g_pf_selftest_hits, (unsigned)vaddr, (unsigned)pc); + + if (g_pf_selftest_hits < PF_SELFTEST_REPEATS) + { + /* Return serviced without changing anything: the RFE must + * re-execute the identical faulting load and land back here. + */ + + return OK; + } + + /* Proof complete: step past the 2-byte l32i.n so the task resumes. */ + + regs[REG_PC] = pc + 2; + g_pf_selftest_hits = 0; + _alert("PAGEFAULT SELFTEST: RFE cleanly restarted the load %d times; " + "resuming\n", PF_SELFTEST_REPEATS); + return OK; + } +#endif + + /* Report the precise fault (with its tracking EXCVADDR) and decline to + * service it, so the caller falls through to the panic / abort path. + */ + + _alert("cache fault: EXCCAUSE=%d EXCVADDR=%08x PC=%08x task=%s\n", + exccause, (unsigned)vaddr, (unsigned)pc, + get_task_name(this_task())); + + return -EFAULT; +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.h b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h new file mode 100644 index 0000000000000..13ed1dbbcf388 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_pagefault.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PAGEFAULT_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PAGEFAULT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pagefault_dispatch + * + * Description: + * Service a precise PMS permission fault (EXCCAUSE Load/Store/InstrFetch + * Prohibited). This is the recoverable-fault entry point invoked from the + * Xtensa user exception handler (xtensa_user()). + * + * The faulting data address is read from the register save area + * (regs[REG_EXCVADDR]). If the fault is serviced, the handler leaves the + * saved PC (regs[REG_PC] == EPC1) unchanged so that, upon return, the RFE + * in the exception vector re-executes the faulting instruction. + * + * Input Parameters: + * exccause - The EXCCAUSE value (20/28/29). + * regs - Pointer to the register save area. + * + * Returned Value: + * OK if the fault was serviced and the instruction should be retried via + * RFE; a negated errno value if the fault is not recoverable (the caller + * then panics / terminates the faulting task). + * + ****************************************************************************/ + +int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs); + +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PAGEFAULT_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index cb1c43e9949de..88050c5eb8976 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -27,6 +27,10 @@ #include #include "xtensa.h" +#include +#ifdef CONFIG_ESP32S3_PAGEFAULT +#include "esp32s3_pagefault.h" +#endif #ifdef CONFIG_ESPRESSIF_SPIFLASH #include "esp_private/cache_utils.h" #endif @@ -73,6 +77,30 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) } #endif /* CONFIG_ESPRESSIF_SPIFLASH */ +#ifdef CONFIG_ESP32S3_PAGEFAULT + /* A cache-attribute permission violation raises a precise, restartable + * exception: Load/Store/InstrFetch Prohibited (EXCCAUSE 28/29/20), with + * EXCVADDR holding the exact faulting address. This is proven on silicon + * (see esp32s3_pagefault.c) and is the recoverable-fault primitive. Offer + * these to the dispatcher; if serviced, return the register frame so that + * the RFE in the exception vector re-executes the faulting instruction. + * + * Note: ESP32-S3 PMS (World Controller) memory-protection violations are + * NOT delivered as these precise causes; they raise the asynchronous + * DRAM0/IRAM0 PMS-monitor interrupt instead (handled elsewhere). + */ + + if (exccause == EXCCAUSE_LOAD_PROHIBITED || + exccause == EXCCAUSE_STORE_PROHIBITED || + exccause == EXCCAUSE_INSTR_PROHIBITED) + { + if (esp32s3_pagefault_dispatch(exccause, regs) == OK) + { + return regs; + } + } +#endif + /* xtensa_user_panic never returns. */ xtensa_user_panic(exccause, regs); From f01eb708576d96151696361c5f5080e45cb832d5 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 14:16:01 +0200 Subject: [PATCH 05/22] xtensa/esp32s3: keep octal-flash init in IRAM for the protected build The protected kernel linker (kernel-space.ld) placed the octal (OPI) flash bring-up helpers -- esp_rom_spiflash / esp_rom_opiflash_*, spi_flash_oct_flash_init, mmu_hal, mspi_timing_*, bootloader_flash*, efuse_hal/efuse_utility, esp_mmu_map and esp32s3_spi_timing -- in mapped flash. During configure_cpu_caches() / spi_flash_init_chip_state() in __start these run while the flash mapping is being reconfigured, which faults (illegal instruction) on octal-flash modules such as the ESP32-S3-WROOM-2. Quad-flash parts never exercise the OPI path, so the problem was latent. Place those functions in .iram0.text (mirroring the flat sections script) so they are safe to execute during flash reconfiguration. Assisted-by: Claude Opus 4.8 (1M context) Signed-off-by: Marco Casaroli --- boards/xtensa/esp32s3/common/scripts/kernel-space.ld | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boards/xtensa/esp32s3/common/scripts/kernel-space.ld b/boards/xtensa/esp32s3/common/scripts/kernel-space.ld index 942d3d4753fbf..1e33d5df35b0c 100644 --- a/boards/xtensa/esp32s3/common/scripts/kernel-space.ld +++ b/boards/xtensa/esp32s3/common/scripts/kernel-space.ld @@ -190,6 +190,7 @@ SECTIONS *libkarch.a:*uart_hal.*(.text .text.* .literal .literal.*) *libkarch.a:*mpu_hal.*(.text .text.* .literal .literal.*) *libkarch.a:*mmu_hal.*(.text .text.* .literal .literal.*) + *libkarch.a:*esp_mmu_map.*(.text .text.* .literal .literal.*) *libkarch.a:*efuse_hal.*(.text .text.* .literal .literal.*) *libkarch.a:*uart_periph.*(.text .text.* .literal .literal.*) *libkarch.a:*esp_rom_uart.*(.text .text.* .literal .literal.*) @@ -217,6 +218,7 @@ SECTIONS *libkarch.a:*mspi_timing_tuning.*(.text .text.* .literal .literal.*) *libkarch.a:*mspi_timing_config.*(.text .text.* .literal .literal.*) + *libkarch.a:esp32s3_spi_timing.*(.text .text.* .literal .literal.*) *libkarch.a:*spi_flash_oct_flash_init.*(.text .text.* .literal .literal.*) *libkarch.a:*spi_flash_hpm_enable.*(.text .text.* .literal .literal.*) #endif From 512efe93d37cb44f37e83679206ee75a278ebcfc Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 15:17:06 +0200 Subject: [PATCH 06/22] xtensa/esp32s3: Implement per-process address environments. Give the ESP32-S3 the arch_addrenv_t machinery that BUILD_KERNEL needs: a per-process page directory built from the 64 KiB MMU pages of the chip, with allocation, teardown, and the vaddr-to-paddr translation that the kernel uses to reach a user buffer. The MMU, PMS and WCL primitives are exposed as an arch API first, because the address environment code and the protected user split both need them and neither owns them. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/Kconfig | 3 + arch/xtensa/include/arch.h | 50 + arch/xtensa/src/esp32s3/Make.defs | 15 + arch/xtensa/src/esp32s3/esp32s3_addrenv.c | 598 ++++++++ arch/xtensa/src/esp32s3/esp32s3_addrenv.h | 169 ++ .../src/esp32s3/esp32s3_addrenv_utils.c | 171 +++ arch/xtensa/src/esp32s3/esp32s3_mmu.c | 132 ++ arch/xtensa/src/esp32s3/esp32s3_mmu.h | 156 ++ arch/xtensa/src/esp32s3/esp32s3_pgalloc.c | 227 +++ arch/xtensa/src/esp32s3/esp32s3_pms.c | 608 ++++++++ arch/xtensa/src/esp32s3/esp32s3_pms.h | 288 ++++ arch/xtensa/src/esp32s3/esp32s3_userspace.c | 1359 ++--------------- arch/xtensa/src/esp32s3/esp32s3_wcl.c | 127 ++ arch/xtensa/src/esp32s3/esp32s3_wcl.h | 90 ++ .../xtensa/esp32s3/esp32s3-devkit/.gitignore | 1 + 15 files changed, 2798 insertions(+), 1196 deletions(-) create mode 100644 arch/xtensa/src/esp32s3/esp32s3_addrenv.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_addrenv.h create mode 100644 arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_mmu.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_mmu.h create mode 100644 arch/xtensa/src/esp32s3/esp32s3_pgalloc.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_pms.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_pms.h create mode 100644 arch/xtensa/src/esp32s3/esp32s3_wcl.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_wcl.h create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/.gitignore diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 5f25569d86687..6f2d955653254 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -85,6 +85,9 @@ config ARCH_CHIP_ESP32S3 select ARCH_HAVE_DEBUG select ARCH_HAVE_FPU select ARCH_HAVE_MPU + select ARCH_HAVE_MMU + select ARCH_HAVE_ADDRENV + select ARCH_NEED_ADDRENV_MAPPING select ARCH_HAVE_MULTICPU select ARCH_HAVE_RESET select ARCH_HAVE_TEXT_HEAP diff --git a/arch/xtensa/include/arch.h b/arch/xtensa/include/arch.h index cb68d0807098b..5d700dbe478a8 100644 --- a/arch/xtensa/include/arch.h +++ b/arch/xtensa/include/arch.h @@ -45,6 +45,56 @@ * Public Types ****************************************************************************/ +#ifdef CONFIG_ARCH_ADDRENV +#ifndef __ASSEMBLY__ + +/* The ESP32-S3 has no general paging MMU and no per-process page-table root. + * External memory (PSRAM) is reached through a single global cache-MMU remap + * table with separate instruction-bus (.text) and data-bus (.data/.bss/heap) + * address windows, at 64 KB page granularity. An address environment is + * therefore described not by a page-table root (no satp/TTBR equivalent) but + * by the set of physical PSRAM pages that back the process plus the fixed + * virtual window bases. up_addrenv_select() makes an environment active by + * reprogramming the cache-MMU window entries to point at these pages, + * invalidating the cache, and reprogramming the PMS split lines that gate + * WORLD1 (user) access -- with a fast path when the incoming environment is + * already the active one (thread<->thread, ISR, syscall). + */ + +struct arch_addrenv_s +{ + /* Virtual bases and heap size. Returned by up_addrenv_vtext/vdata/vheap + * and up_addrenv_heapsize. .text maps through the instruction-bus window, + * .data/.bss and the heap through the data-bus window. + */ + + uintptr_t textvbase; + uintptr_t datavbase; + uintptr_t heapvbase; + size_t heapsize; + + /* Physical PSRAM page addresses backing each region -- one 64 KB page per + * entry, allocated from the PSRAM page pool by up_addrenv_create(). + * Index i of a region backs virtual page i counted from that region's + * vbase. + */ + + uintptr_t textpages[CONFIG_ARCH_TEXT_NPAGES]; + uintptr_t datapages[CONFIG_ARCH_DATA_NPAGES]; + uintptr_t heappages[CONFIG_ARCH_HEAP_NPAGES]; + + /* Number of pages actually allocated in each region */ + + uint16_t ntext; + uint16_t ndata; + uint16_t nheap; +}; + +typedef struct arch_addrenv_s arch_addrenv_t; + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_ARCH_ADDRENV */ + /**************************************************************************** * Public Data ****************************************************************************/ diff --git a/arch/xtensa/src/esp32s3/Make.defs b/arch/xtensa/src/esp32s3/Make.defs index e80bf885ba62a..6fd8e8087fbab 100644 --- a/arch/xtensa/src/esp32s3/Make.defs +++ b/arch/xtensa/src/esp32s3/Make.defs @@ -43,10 +43,25 @@ ifeq ($(CONFIG_BUILD_PROTECTED),y) CHIP_CSRCS += esp32s3_userspace.c endif +# The MMU/PMS/WCL primitives back both the protected user split and the +# BUILD_KERNEL address-environment remap. + +ifneq ($(filter y,$(CONFIG_BUILD_PROTECTED) $(CONFIG_ARCH_ADDRENV)),) +CHIP_CSRCS += esp32s3_mmu.c esp32s3_pms.c esp32s3_wcl.c +endif + ifeq ($(CONFIG_ESP32S3_PAGEFAULT),y) CHIP_CSRCS += esp32s3_pagefault.c endif +ifeq ($(CONFIG_ARCH_ADDRENV),y) +CHIP_CSRCS += esp32s3_addrenv.c esp32s3_addrenv_utils.c +endif + +ifeq ($(CONFIG_MM_PGALLOC),y) +CHIP_CSRCS += esp32s3_pgalloc.c +endif + ifeq ($(CONFIG_SMP),y) CHIP_CSRCS += esp32s3_cpuidlestack.c esp32s3_cpustart.c esp32s3_intercpu_interrupt.c endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.c b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c new file mode 100644 index 0000000000000..ccf665917d3e9 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c @@ -0,0 +1,598 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_addrenv.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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "soc/ext_mem_defs.h" + +#include "esp32s3_addrenv.h" +#include "esp32s3_mmu.h" +#include "esp32s3_spiram.h" + +#ifdef CONFIG_ARCH_ADDRENV + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* The ESP32-S3 cache MMU is a single global remap table: only one user + * address environment can be resident in the shared .text/.data/.heap + * windows at a time. Track which one it is so up_addrenv_select() can skip + * the (expensive) remap when the incoming environment is already active -- + * the common thread<->thread, ISR and syscall case. + */ + +static const arch_addrenv_t *g_current_addrenv; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: alloc_region + * + * Description: + * Allocate and wipe the physical page-pool (PSRAM) pages that back a + * single user region, recording them in the caller's page array. On the + * ESP32-S3 the physical pages are only recorded here; the global cache-MMU + * table is (re)programmed lazily in up_addrenv_select(). + * + * Input Parameters: + * pages - Destination page array (physical addresses) + * maxpages - Capacity of the page array + * size - Region size in bytes + * count - Receives the number of pages actually allocated + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. On failure + * *count reflects the pages allocated so far so the caller can free them. + * + ****************************************************************************/ + +static int alloc_region(uintptr_t *pages, unsigned int maxpages, size_t size, + uint16_t *count) +{ + unsigned int npages = MM_NPAGES(size); + unsigned int i; + + *count = 0; + + if (npages > maxpages) + { + berr("ERROR: region needs %u pages, only %u available\n", + npages, maxpages); + return -E2BIG; + } + + for (i = 0; i < npages; i++) + { + uintptr_t paddr = mm_pgalloc(1); + if (paddr == 0) + { + *count = i; + return -ENOMEM; + } + + esp32s3_pgwipe(paddr); + pages[i] = paddr; + } + + *count = npages; + return OK; +} + +/**************************************************************************** + * Name: free_region + * + * Description: + * Return every page recorded in a region's page array to the page pool. + * + ****************************************************************************/ + +static void free_region(uintptr_t *pages, uint16_t *count) +{ + uint16_t i; + + for (i = 0; i < *count; i++) + { + if (pages[i] != 0) + { + mm_pgfree(pages[i], 1); + pages[i] = 0; + } + } + + *count = 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_addrenv_create + * + * Description: + * This function is called when a new task is created in order to + * instantiate an address environment for the new task group. Physical + * pages for .text, .data/.bss and the heap are allocated from the PSRAM + * page pool and recorded in 'addrenv'. The reserved OS region (heap MM + * bookkeeping and signal delivery support) occupies the first page of the + * data window. + * + ****************************************************************************/ + +int up_addrenv_create(size_t textsize, size_t datasize, size_t heapsize, + arch_addrenv_t *addrenv) +{ + size_t datatotal; + int ret; + + DEBUGASSERT(addrenv); + DEBUGASSERT(MM_ISALIGNED(CONFIG_ARCH_TEXT_VBASE)); + DEBUGASSERT(MM_ISALIGNED(CONFIG_ARCH_DATA_VBASE)); + DEBUGASSERT(MM_ISALIGNED(CONFIG_ARCH_HEAP_VBASE)); + + /* Start from a clean slate */ + + memset(addrenv, 0, sizeof(arch_addrenv_t)); + + /* The data window carries the OS reserve at its base, followed by the + * task's .data/.bss. vdata is therefore reported past the reserve. + */ + + datatotal = MM_PGALIGNUP(ARCH_DATA_RESERVE_SIZE) + datasize; + + addrenv->textvbase = CONFIG_ARCH_TEXT_VBASE; + addrenv->datavbase = CONFIG_ARCH_DATA_VBASE + + MM_PGALIGNUP(ARCH_DATA_RESERVE_SIZE); + addrenv->heapvbase = CONFIG_ARCH_HEAP_VBASE; + addrenv->heapsize = heapsize; + + /* Allocate the backing pages for each region */ + + ret = alloc_region(addrenv->textpages, CONFIG_ARCH_TEXT_NPAGES, textsize, + &addrenv->ntext); + if (ret < 0) + { + goto errout; + } + + ret = alloc_region(addrenv->datapages, CONFIG_ARCH_DATA_NPAGES, datatotal, + &addrenv->ndata); + if (ret < 0) + { + goto errout; + } + + ret = alloc_region(addrenv->heappages, CONFIG_ARCH_HEAP_NPAGES, heapsize, + &addrenv->nheap); + if (ret < 0) + { + goto errout; + } + + return OK; + +errout: + up_addrenv_destroy(addrenv); + return ret; +} + +/**************************************************************************** + * Name: up_addrenv_destroy + * + * Description: + * This function is called when a task group is finally deleted. Return + * all of the group's physical pages to the page pool. + * + ****************************************************************************/ + +int up_addrenv_destroy(arch_addrenv_t *addrenv) +{ + DEBUGASSERT(addrenv); + + /* If this environment is the resident one, forget it so a later select of + * a different environment that happens to reuse this address does not take + * the fast path by mistake. + */ + + if (addrenv == g_current_addrenv) + { + g_current_addrenv = NULL; + } + + free_region(addrenv->textpages, &addrenv->ntext); + free_region(addrenv->datapages, &addrenv->ndata); + free_region(addrenv->heappages, &addrenv->nheap); + + memset(addrenv, 0, sizeof(arch_addrenv_t)); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_vtext + * + * Description: + * Return the virtual address associated with the newly created .text + * address environment. + * + ****************************************************************************/ + +int up_addrenv_vtext(arch_addrenv_t *addrenv, void **vtext) +{ + DEBUGASSERT(addrenv && vtext); + *vtext = (void *)addrenv->textvbase; + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_vdata + * + * Description: + * Return the virtual address associated with the newly created .bss/.data + * address environment. + * + ****************************************************************************/ + +int up_addrenv_vdata(arch_addrenv_t *addrenv, uintptr_t textsize, + void **vdata) +{ + DEBUGASSERT(addrenv && vdata); + *vdata = (void *)addrenv->datavbase; + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_vheap + * + * Description: + * Return the heap virtual address associated with the newly created + * address environment. + * + ****************************************************************************/ + +int up_addrenv_vheap(const arch_addrenv_t *addrenv, void **vheap) +{ + DEBUGASSERT(addrenv && vheap); + *vheap = (void *)addrenv->heapvbase; + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_heapsize + * + * Description: + * Return the size of the initial heap allocation. + * + ****************************************************************************/ + +ssize_t up_addrenv_heapsize(const arch_addrenv_t *addrenv) +{ + DEBUGASSERT(addrenv); + return (ssize_t)addrenv->heapsize; +} + +/**************************************************************************** + * Name: up_addrenv_select + * + * Description: + * After an address environment has been established for a task group (via + * up_addrenv_create()), this function may be called to instantiate that + * address environment in the virtual address space. On the ESP32-S3 there + * is no page-table-base register to load; instead the shared user + * cache-MMU windows (.text on the instruction bus, .data/.bss and heap on + * the data bus) are reprogrammed to point at this environment's PSRAM + * pages. Only one user environment can be resident at a time, so + * isolation between groups is provided by this remap: while a group runs, + * only its own pages are visible in the windows. + * + * The remap is skipped when 'addrenv' is already the resident environment + * (thread<->thread within a group, ISRs, syscalls), which pays nothing. + * + * Input Parameters: + * addrenv - Describes the address environment to instantiate. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_select(const arch_addrenv_t *addrenv) +{ + irqstate_t flags; + uint32_t cache_state; + uint16_t i; + + DEBUGASSERT(addrenv); + + /* Fast path: this environment is already resident */ + + if (addrenv == g_current_addrenv) + { + return OK; + } + + flags = enter_critical_section(); + + /* Suspend the data cache while the windows are rewritten. Instruction + * fetches keep running from the (unchanged) flash mapping through the + * instruction cache, so this function may execute from flash. + */ + + cache_state = esp32s3_dcache_suspend(false); + + /* TODO(Unit F hardening): only the pages this group actually uses are + * remapped below. Window entries beyond ntext/ndata/nheap still point at + * the previously-resident group's pages, so a buggy or malicious task that + * touches its window above its own allocation could reach stale mappings. + * A well-behaved task never does, and the guard-page/SIGSEGV abort + * (CONFIG_ESP32S3_PAGEFAULT_ABORT) is the backstop, but full isolation + * needs the unused window entries invalidated here. Deferred to on-target + * bring-up because invalidating cache-MMU entries has documented sharp + * edges (an invalid in-window entry reads 0 silently, it does not fault). + */ + + /* Point the instruction-bus (.text) window at this group's pages */ + + for (i = 0; i < addrenv->ntext; i++) + { + esp32s3_mmu_map_ibus(SOC_MMU_ACCESS_SPIRAM, + ESP32S3_TEXT_VBASE + i * MM_PGSIZE, + addrenv->textpages[i], 1); + } + + /* Point the data-bus (.data/.bss, then heap) windows at this group's + * pages + */ + + for (i = 0; i < addrenv->ndata; i++) + { + esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_SPIRAM, + ESP32S3_DATA_VBASE + i * MM_PGSIZE, + addrenv->datapages[i], 1); + } + + for (i = 0; i < addrenv->nheap; i++) + { + esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_SPIRAM, + ESP32S3_HEAP_VBASE + i * MM_PGSIZE, + addrenv->heappages[i], 1); + } + + /* Drop stale instruction lines from the previous mapping and resume */ + + esp32s3_icache_invalidate_all(); + esp32s3_dcache_resume(cache_state); + + g_current_addrenv = addrenv; + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: esp32s3_addrenv_coherent + * + * Description: + * Make everything written through the data bus visible to instruction + * fetch: push the data cache out to PSRAM, then drop the instruction cache + * so the next fetch reloads from it. + * + * Both halves are needed. The write-back is needed because text arrives + * through the data side; the invalidate is needed because the cache-MMU is + * one global table, so an entry now pointing at a process's page may still + * have instruction-cache lines belonging to whatever it mapped before. + * + ****************************************************************************/ + +static void esp32s3_addrenv_coherent(void) +{ + irqstate_t flags = enter_critical_section(); + + esp_spiram_writeback_cache(); + esp32s3_icache_invalidate_all(); + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: esp32s3_addrenv_mapnew + * + * Description: + * Make a page that was added to an address environment after that + * environment was created -- heap growth through sbrk()/pgalloc() -- + * visible to the running task. The cache-MMU windows only ever reflect + * the resident environment, so this is a no-op unless 'addrenv' is the one + * currently selected. For any other environment the page is picked up + * from the page array by the next up_addrenv_select(). + * + * Input Parameters: + * addrenv - The address environment the page was added to. + * vaddr - The user virtual address the page is mapped at. + * paddr - The physical (page pool) address of the page. + * + ****************************************************************************/ + +void esp32s3_addrenv_mapnew(const arch_addrenv_t *addrenv, uintptr_t vaddr, + uintptr_t paddr) +{ + irqstate_t flags; + uint32_t cache_state; + + DEBUGASSERT(addrenv); + + if (addrenv != g_current_addrenv) + { + return; + } + + flags = enter_critical_section(); + + cache_state = esp32s3_dcache_suspend(false); + esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_SPIRAM, vaddr, paddr, 1); + esp32s3_dcache_resume(cache_state); + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: up_addrenv_coherent + * + * Description: + * Flush D-Cache and invalidate I-Cache in preparation for a change in + * address environments. + * + * This matters more here than the name suggests, because it is the point + * at which a freshly loaded program becomes executable. The ELF loader + * writes .text as *data*, so it lands in the data cache; instructions are + * fetched through the separate instruction cache. Without a writeback the + * PSRAM pages still hold whatever was there before, and the new process + * runs a mixture of its own code and stale bytes -- which shows up as an + * illegal instruction somewhere in the middle of a function, not at its + * entry point. up_addrenv_select() cannot cover this: it skips all cache + * maintenance when the environment it is handed is already resident, which + * is exactly the case on the way out of the loader. + * + ****************************************************************************/ + +int up_addrenv_coherent(const arch_addrenv_t *addrenv) +{ + DEBUGASSERT(addrenv); + + esp32s3_addrenv_coherent(); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_mprot + * + * Description: + * Modify access rights to an address range. The ELF loader uses this to + * make .text writable while it copies the program in, and read-only again + * afterwards. + * + * The ESP32-S3 cannot honour that. A cache-MMU entry carries only a valid + * bit, a memory type and a physical page number -- there are no per-page + * permission bits -- and the coarse alternatives cannot express it either: + * the PMS areas gate a whole window per world rather than a page, and they + * report violations asynchronously. A mapped user page is therefore + * always readable and writable by its owner, so a process can write to its + * own .text. This does not weaken isolation between groups, which comes + * from the window remap in up_addrenv_select(), and the request is + * accepted so that the loader can proceed. + * + ****************************************************************************/ + +int up_addrenv_mprot(arch_addrenv_t *addrenv, uintptr_t addr, size_t len, + int prot) +{ + UNUSED(addrenv); + UNUSED(addr); + UNUSED(len); + UNUSED(prot); + + /* The permission change cannot be honoured, but the call still marks the + * two moments that matter for cache state: the loader asks for write + * access before it copies a program in, and takes it away again once the + * program is complete. Make both a synchronisation point. + * + * This is what makes a freshly loaded program executable. Text is written + * as data through the data-bus alias of the same cache-MMU entry, so it + * sits in the data cache, while instructions are fetched through the + * separate instruction cache -- which may still hold lines from whatever + * that entry mapped before, since one global table is shared by every + * process and by the kernel's own PSRAM window. Those stale lines read + * back as zeroes, so without this the process runs some of its own code + * and then executes a hole. + */ + + esp32s3_addrenv_coherent(); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_clone + * + * Description: + * Duplicate an address environment. The threads of a task group share one + * address environment, so cloning it is simply copying the descriptor: the + * copy references the same PSRAM pages. (A true fork() that gives the + * child its own pages is a separate, higher-level operation built on + * up_addrenv_create() + a content copy.) + * + ****************************************************************************/ + +int up_addrenv_clone(const arch_addrenv_t *src, arch_addrenv_t *dest) +{ + DEBUGASSERT(src && dest); + memcpy(dest, src, sizeof(arch_addrenv_t)); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_attach + * + * Description: + * Called when a task or thread is created in order to instantiate an + * address environment. On the ESP32-S3 the group's environment is made + * resident lazily by up_addrenv_select() at context-switch time, so there + * is nothing to do here. + * + ****************************************************************************/ + +int up_addrenv_attach(struct tcb_s *ptcb, struct tcb_s *tcb) +{ + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_detach + * + * Description: + * Called when a task or thread exits. The group's pages are released by + * up_addrenv_destroy() when the final member leaves, so there is nothing + * per-thread to undo here. + * + ****************************************************************************/ + +int up_addrenv_detach(struct tcb_s *tcb) +{ + return OK; +} + +#endif /* CONFIG_ARCH_ADDRENV */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.h b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h new file mode 100644 index 0000000000000..5de0eab46494e --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h @@ -0,0 +1,169 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_addrenv.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ADDRENV_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ADDRENV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +#ifdef CONFIG_ARCH_ADDRENV + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_ARCH_PGPOOL_MAPPING +# error "ESP32-S3 address environments need CONFIG_ARCH_PGPOOL_MAPPING" +#endif + +/* The user address space is split across two disjoint cache-MMU windows: + * .text lives in the instruction-bus window, .data/.bss and the heap in the + * data-bus window. Each window is described by its base and page count. + */ + +#define ESP32S3_TEXT_VBASE (CONFIG_ARCH_TEXT_VBASE) +#define ESP32S3_TEXT_VEND (CONFIG_ARCH_TEXT_VBASE + \ + CONFIG_ARCH_TEXT_NPAGES * MM_PGSIZE) +#define ESP32S3_DATA_VBASE (CONFIG_ARCH_DATA_VBASE) +#define ESP32S3_DATA_VEND (CONFIG_ARCH_DATA_VBASE + \ + CONFIG_ARCH_DATA_NPAGES * MM_PGSIZE) +#define ESP32S3_HEAP_VBASE (CONFIG_ARCH_HEAP_VBASE) +#define ESP32S3_HEAP_VEND (CONFIG_ARCH_HEAP_VBASE + \ + CONFIG_ARCH_HEAP_NPAGES * MM_PGSIZE) + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pgvaddr + * + * Description: + * Get the kernel-addressable virtual address of a page-pool physical + * address. The page pool (PSRAM) is permanently mapped into the kernel + * (WORLD0) address space, so a page allocated by mm_pgalloc() can be + * reached directly through this fixed offset. Returns 0 if the physical + * address is not inside the page pool. + * + ****************************************************************************/ + +static inline uintptr_t esp32s3_pgvaddr(uintptr_t paddr) +{ + if (paddr >= CONFIG_ARCH_PGPOOL_PBASE && paddr < CONFIG_ARCH_PGPOOL_PEND) + { + return paddr - CONFIG_ARCH_PGPOOL_PBASE + CONFIG_ARCH_PGPOOL_VBASE; + } + + return 0; +} + +/**************************************************************************** + * Name: esp32s3_pgpaddr + * + * Description: + * Inverse of esp32s3_pgvaddr(): translate a kernel page-pool virtual + * address back to its physical address. Returns 0 if the virtual address + * is not inside the mapped page pool. + * + ****************************************************************************/ + +static inline uintptr_t esp32s3_pgpaddr(uintptr_t vaddr) +{ + if (vaddr >= CONFIG_ARCH_PGPOOL_VBASE && vaddr < CONFIG_ARCH_PGPOOL_VEND) + { + return vaddr - CONFIG_ARCH_PGPOOL_VBASE + CONFIG_ARCH_PGPOOL_PBASE; + } + + return 0; +} + +/**************************************************************************** + * Name: esp32s3_uservaddr + * + * Description: + * Return true if vaddr lies inside one of the user (.text/.data/heap) + * cache-MMU windows. + * + ****************************************************************************/ + +static inline bool esp32s3_uservaddr(uintptr_t vaddr) +{ + return ((vaddr >= ESP32S3_TEXT_VBASE && vaddr < ESP32S3_TEXT_VEND) || + (vaddr >= ESP32S3_DATA_VBASE && vaddr < ESP32S3_DATA_VEND) || + (vaddr >= ESP32S3_HEAP_VBASE && vaddr < ESP32S3_HEAP_VEND)); +} + +/**************************************************************************** + * Name: esp32s3_pgwipe + * + * Description: + * Zero a page-pool physical page through its kernel virtual mapping. + * + ****************************************************************************/ + +static inline void esp32s3_pgwipe(uintptr_t paddr) +{ + uintptr_t vaddr = esp32s3_pgvaddr(paddr); + if (vaddr) + { + memset((void *)vaddr, 0, MM_PGSIZE); + } +} + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_addrenv_mapnew + * + * Description: + * Make a page that was added to an address environment after that + * environment was created -- heap growth through sbrk()/pgalloc() -- + * visible to the running task. The cache-MMU windows only ever reflect + * the resident environment, so this is a no-op unless 'addrenv' is the one + * currently selected. For any other environment the page is picked up + * from the page array by the next up_addrenv_select(). + * + * Input Parameters: + * addrenv - The address environment the page was added to. + * vaddr - The user virtual address the page is mapped at. + * paddr - The physical (page pool) address of the page. + * + ****************************************************************************/ + +void esp32s3_addrenv_mapnew(const arch_addrenv_t *addrenv, uintptr_t vaddr, + uintptr_t paddr); + +#endif /* CONFIG_ARCH_ADDRENV */ +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ADDRENV_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c b/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c new file mode 100644 index 0000000000000..c44fc4732e85a --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c @@ -0,0 +1,171 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.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 + +#include +#include +#include + +#include +#include +#include + +#include "esp32s3_addrenv.h" + +#ifdef CONFIG_ARCH_ADDRENV + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_addrenv_find_page + * + * Description: + * Find the physical address of the page that backs a user virtual address + * in the given address environment. On the ESP32-S3 there is no per-task + * page table to walk; the physical pages are recorded per region in the + * address environment, so the lookup reduces to selecting the window that + * contains 'vaddr' and indexing that region's page array. + * + * Returned Value: + * Physical address of the backing page (page-aligned) on success, or 0 if + * 'vaddr' is not a mapped user address. + * + ****************************************************************************/ + +uintptr_t up_addrenv_find_page(arch_addrenv_t *addrenv, uintptr_t vaddr) +{ + const uintptr_t *pages; + uintptr_t base; + uint16_t count; + uint16_t index; + + DEBUGASSERT(addrenv); + + if (vaddr >= ESP32S3_TEXT_VBASE && vaddr < ESP32S3_TEXT_VEND) + { + base = ESP32S3_TEXT_VBASE; + pages = addrenv->textpages; + count = addrenv->ntext; + } + else if (vaddr >= ESP32S3_DATA_VBASE && vaddr < ESP32S3_DATA_VEND) + { + base = ESP32S3_DATA_VBASE; + pages = addrenv->datapages; + count = addrenv->ndata; + } + else if (vaddr >= ESP32S3_HEAP_VBASE && vaddr < ESP32S3_HEAP_VEND) + { + base = ESP32S3_HEAP_VBASE; + pages = addrenv->heappages; + count = addrenv->nheap; + } + else + { + return 0; + } + + index = (uint16_t)((vaddr - base) >> MM_PGSHIFT); + if (index >= count) + { + return 0; + } + + return pages[index]; +} + +/**************************************************************************** + * Name: up_addrenv_page_vaddr + * + * Description: + * Get the kernel virtual address of a physical page allocated for an + * address environment. Since the PSRAM page pool is permanently mapped + * into the kernel, this is a fixed offset translation. + * + ****************************************************************************/ + +uintptr_t up_addrenv_page_vaddr(uintptr_t page) +{ + return esp32s3_pgvaddr(page); +} + +/**************************************************************************** + * Name: up_addrenv_user_vaddr + * + * Description: + * Check if a virtual address is a user virtual address. + * + ****************************************************************************/ + +bool up_addrenv_user_vaddr(uintptr_t vaddr) +{ + return esp32s3_uservaddr(vaddr); +} + +/**************************************************************************** + * Name: up_addrenv_page_wipe + * + * Description: + * Wipe a page of physical memory, first mapping it into kernel virtual + * memory. + * + ****************************************************************************/ + +void up_addrenv_page_wipe(uintptr_t page) +{ + esp32s3_pgwipe(page); +} + +/**************************************************************************** + * Name: up_addrenv_pa_to_va + * + * Description: + * Map a physical page-pool address to the kernel virtual address that + * currently maps it. + * + ****************************************************************************/ + +void *up_addrenv_pa_to_va(uintptr_t pa) +{ + return (void *)esp32s3_pgvaddr(pa); +} + +/**************************************************************************** + * Name: up_addrenv_va_to_pa + * + * Description: + * Map a kernel page-pool virtual address back to its physical address. + * + ****************************************************************************/ + +uintptr_t up_addrenv_va_to_pa(void *va) +{ + return esp32s3_pgpaddr((uintptr_t)va); +} + +#endif /* CONFIG_ARCH_ADDRENV */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_mmu.c b/arch/xtensa/src/esp32s3/esp32s3_mmu.c new file mode 100644 index 0000000000000..cf54eb0c55de1 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_mmu.c @@ -0,0 +1,132 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_mmu.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include + +#include "xtensa.h" +#include "esp_attr.h" + +#include "soc/extmem_reg.h" + +#include "esp32s3_mmu.h" + +/**************************************************************************** + * ROM Function Prototypes + ****************************************************************************/ + +extern uint32_t cache_suspend_dcache(void); +extern void cache_resume_dcache(uint32_t val); +extern void cache_invalidate_dcache_all(void); +extern void cache_invalidate_icache_all(void); +extern void cache_writeback_all(void); +extern int cache_dbus_mmu_set(uint32_t ext_ram, uint32_t vaddr, + uint32_t paddr, uint32_t psize, uint32_t num, + uint32_t fixed); +extern int cache_ibus_mmu_set(uint32_t ext_ram, uint32_t vaddr, + uint32_t paddr, uint32_t psize, uint32_t num, + uint32_t fixed); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_dcache_suspend + ****************************************************************************/ + +uint32_t IRAM_ATTR esp32s3_dcache_suspend(bool needs_wb) +{ + uint32_t dcache_state = cache_suspend_dcache(); + + if (needs_wb) + { + cache_writeback_all(); + } + + cache_invalidate_dcache_all(); + + return dcache_state; +} + +/**************************************************************************** + * Name: esp32s3_dcache_resume + ****************************************************************************/ + +void IRAM_ATTR esp32s3_dcache_resume(uint32_t cache_state) +{ + uint32_t regval; + + regval = getreg32(EXTMEM_DCACHE_CTRL1_REG); + regval &= ~EXTMEM_DCACHE_SHUT_CORE0_BUS; +#ifdef CONFIG_SMP + regval &= ~EXTMEM_DCACHE_SHUT_CORE1_BUS; +#endif + putreg32(regval, EXTMEM_DCACHE_CTRL1_REG); + + cache_resume_dcache(cache_state); +} + +/**************************************************************************** + * Name: esp32s3_icache_invalidate_all + ****************************************************************************/ + +void IRAM_ATTR esp32s3_icache_invalidate_all(void) +{ + cache_invalidate_icache_all(); +} + +/**************************************************************************** + * Name: esp32s3_mmu_calc_pages + ****************************************************************************/ + +uint32_t esp32s3_mmu_calc_pages(uint32_t size, uint32_t vaddr) +{ + return (size + (vaddr - (vaddr & MMU_FLASH_MASK)) + MMU_PAGE_SIZE - 1) / + MMU_PAGE_SIZE; +} + +/**************************************************************************** + * Name: esp32s3_mmu_map_dbus + ****************************************************************************/ + +int IRAM_ATTR esp32s3_mmu_map_dbus(uint32_t ext_ram, uint32_t vaddr, + uint32_t paddr, uint32_t npages) +{ + return cache_dbus_mmu_set(ext_ram, vaddr, paddr, 64, (int)npages, 0); +} + +/**************************************************************************** + * Name: esp32s3_mmu_map_ibus + ****************************************************************************/ + +int IRAM_ATTR esp32s3_mmu_map_ibus(uint32_t ext_ram, uint32_t vaddr, + uint32_t paddr, uint32_t npages) +{ + return cache_ibus_mmu_set(ext_ram, vaddr, paddr, 64, (int)npages, 0); +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_mmu.h b/arch/xtensa/src/esp32s3/esp32s3_mmu.h new file mode 100644 index 0000000000000..6553c9e77cf1e --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_mmu.h @@ -0,0 +1,156 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_mmu.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_MMU_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_MMU_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "hardware/esp32s3_cache_memory.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Cache MMU address mask (MMU tables ignore bits which are zero) */ + +#define MMU_FLASH_MASK (~(MMU_PAGE_SIZE - 1)) + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_dcache_suspend + * + * Description: + * Suspend the data cache access for the CPU, optionally writing back its + * contents first, then invalidating it. + * + * Input Parameters: + * needs_wb - Whether to write back the data cache contents prior to + * invalidation. + * + * Returned Value: + * Current cache state (to be passed to esp32s3_dcache_resume()). + * + ****************************************************************************/ + +uint32_t esp32s3_dcache_suspend(bool needs_wb); + +/**************************************************************************** + * Name: esp32s3_dcache_resume + * + * Description: + * Resume the data cache access for the CPU. + * + * Input Parameters: + * cache_state - Previously saved data cache state to be restored. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void esp32s3_dcache_resume(uint32_t cache_state); + +/**************************************************************************** + * Name: esp32s3_icache_invalidate_all + * + * Description: + * Invalidate the whole instruction cache. + * + ****************************************************************************/ + +void esp32s3_icache_invalidate_all(void); + +/**************************************************************************** + * Name: esp32s3_mmu_calc_pages + * + * Description: + * Calculate the required number of MMU pages for mapping a given region + * at the cache MMU 64 KB page granularity. + * + * Input Parameters: + * size - Length of the region to map. + * vaddr - Starting offset to map (its intra-page offset is accounted for). + * + * Returned Value: + * Number of 64 KB MMU pages required. + * + ****************************************************************************/ + +uint32_t esp32s3_mmu_calc_pages(uint32_t size, uint32_t vaddr); + +/**************************************************************************** + * Name: esp32s3_mmu_map_dbus + * + * Description: + * Program the data-bus cache MMU to map a range of 64 KB pages from an + * external memory (flash or PSRAM) into the CPU virtual address space. + * The caller is responsible for suspending/resuming the data cache around + * the mapping change. + * + * Input Parameters: + * ext_ram - Selects the external memory (SOC_MMU_ACCESS_FLASH or + * SOC_MMU_ACCESS_SPIRAM). + * vaddr - 64 KB-aligned virtual base address. + * paddr - 64 KB-aligned physical/flash offset. + * npages - Number of 64 KB pages to map. + * + * Returned Value: + * Zero (OK) on success; the ROM error code otherwise. + * + ****************************************************************************/ + +int esp32s3_mmu_map_dbus(uint32_t ext_ram, uint32_t vaddr, uint32_t paddr, + uint32_t npages); + +/**************************************************************************** + * Name: esp32s3_mmu_map_ibus + * + * Description: + * Program the instruction-bus cache MMU to map a range of 64 KB pages from + * an external memory into the CPU virtual address space. The caller is + * responsible for suspending/resuming the data cache around the change. + * + * Input Parameters: + * ext_ram - Selects the external memory (SOC_MMU_ACCESS_FLASH or + * SOC_MMU_ACCESS_SPIRAM). + * vaddr - 64 KB-aligned virtual base address. + * paddr - 64 KB-aligned physical/flash offset. + * npages - Number of 64 KB pages to map. + * + * Returned Value: + * Zero (OK) on success; the ROM error code otherwise. + * + ****************************************************************************/ + +int esp32s3_mmu_map_ibus(uint32_t ext_ram, uint32_t vaddr, uint32_t paddr, + uint32_t npages); + +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_MMU_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c b/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c new file mode 100644 index 0000000000000..6b3e5d0f1d81f --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c @@ -0,0 +1,227 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_pgalloc.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 + +#include +#include +#include + +#include +#include +#include +#include + +#include "sched/sched.h" + +#include "esp32s3_addrenv.h" + +#ifdef CONFIG_ESP32S3_SPIRAM +# include "esp32s3_spiram.h" +#endif + +#ifdef CONFIG_MM_PGALLOC + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_allocate_pgheap + * + * Description: + * If there is a page allocator in the configuration, then this function + * must be provided by the platform-specific code. The OS initialization + * logic will call this function early in the initialization sequence to + * get the page heap information needed to configure the page allocator. + * + * On the ESP32-S3 the page pool is a slice of the external octal PSRAM. + * The pool is described by its *physical* base -- for the cache MMU that + * is the zero-based offset into the PSRAM device -- because mm_pgalloc() + * hands out physical page addresses. The whole pool is also permanently + * mapped into the kernel (WORLD0) data-bus window at + * CONFIG_ARCH_PGPOOL_VBASE so the kernel can reach any page it allocates + * (see esp32s3_pgvaddr()). + * + * Input Parameters: + * heap_start - Receives the physical base address of the page pool. + * heap_size - Receives the size of the page pool in bytes. + * + ****************************************************************************/ + +void up_allocate_pgheap(void **heap_start, size_t *heap_size) +{ + DEBUGASSERT(heap_start && heap_size); + +#ifdef CONFIG_ESP32S3_SPIRAM + /* Where the kernel's PSRAM window lands is decided at run time: + * esp32s3_spiram.c maps PSRAM immediately after the last cache-MMU entry + * the flash mappings occupy, so it moves as the kernel image grows. The + * page pool is described to the OS by compile-time constants, so the two + * have to be checked against each other -- and loudly, because getting it + * wrong is otherwise silent: an unmapped cache window swallows writes and + * reads back as zero without faulting, so a misplaced pool would simply + * lose every page handed out of it. + */ + + { + uintptr_t ramstart = (uintptr_t)esp_spiram_allocable_vaddr_start(); + uintptr_t ramend = (uintptr_t)esp_spiram_allocable_vaddr_end(); + + _info("PSRAM window %08" PRIxPTR "-%08" PRIxPTR ", " + "page pool %08x-%08x\n", + ramstart, ramend, + CONFIG_ARCH_PGPOOL_VBASE, CONFIG_ARCH_PGPOOL_VEND); + + if ((uintptr_t)CONFIG_ARCH_PGPOOL_VBASE < ramstart || + (uintptr_t)CONFIG_ARCH_PGPOOL_VEND > ramend) + { + _err("ERROR: page pool %08x-%08x is outside the mapped PSRAM " + "window %08" PRIxPTR "-%08" PRIxPTR "\n", + CONFIG_ARCH_PGPOOL_VBASE, CONFIG_ARCH_PGPOOL_VEND, + ramstart, ramend); + PANIC(); + } + } +#endif + + *heap_start = (void *)CONFIG_ARCH_PGPOOL_PBASE; + *heap_size = (size_t)CONFIG_ARCH_PGPOOL_SIZE; +} + +#ifdef CONFIG_BUILD_KERNEL + +/**************************************************************************** + * Name: pgalloc + * + * Description: + * If there is a page allocator in the configuration and if and MMU is + * available to map physical addresses to virtual address, then function + * must be provided by the platform-specific code. This is part of the + * implementation of sbrk(). This function will allocate the requested + * number of pages using the page allocator and map them into consecutive + * virtual addresses beginning with 'brkaddr' + * + * NOTE: This function does not use the up_ naming standard because it + * is indirectly callable from user-space code via a system trap. + * Therefore, it is a system interface and follows a different naming + * convention. + * + * The ESP32-S3 has no per-process page table: a page is "mapped" by + * recording it in the address environment's heap page array, and the + * shared data-bus cache-MMU window is programmed from that array by + * up_addrenv_select(). Since sbrk() runs on behalf of the calling task, + * whose environment is by definition the resident one, the new pages are + * also programmed into the window right away so the caller can use them + * without waiting for a context switch. + * + * Input Parameters: + * brkaddr - The heap break address. The next page will be allocated and + * mapped to this address. Must be page aligned. If the memory manager + * has not yet been initialized and this is the first block requested for + * the heap, then brkaddr should be zero. pgalloc will then assigned the + * well-known virtual address of the beginning of the heap. + * npages - The number of pages to allocate and map. Mapping of pages + * will be contiguous beginning beginning at 'brkaddr' + * + * Returned Value: + * The (virtual) base address of the mapped page will returned on success. + * Normally this will be the same as the 'brkaddr' input. However, if + * the 'brkaddr' input was zero, this will be the virtual address of the + * beginning of the heap. Zero is returned on any failure. + * + ****************************************************************************/ + +uintptr_t pgalloc(uintptr_t brkaddr, unsigned int npages) +{ + struct tcb_s *tcb = this_task(); + arch_addrenv_t *addrenv; + uintptr_t vaddr; + unsigned int index; + + DEBUGASSERT(tcb && tcb->addrenv_own); + addrenv = &tcb->addrenv_own->addrenv; + + /* brkaddr = 0 means that no heap has yet been allocated */ + + if (brkaddr == 0) + { + brkaddr = addrenv->heapvbase; + } + + DEBUGASSERT(brkaddr >= addrenv->heapvbase); + DEBUGASSERT(MM_ISALIGNED(brkaddr)); + + /* Start mapping from the old heap break address */ + + vaddr = brkaddr; + index = (brkaddr - addrenv->heapvbase) >> MM_PGSHIFT; + + for (; npages > 0; npages--, index++, vaddr += MM_PGSIZE) + { + uintptr_t paddr; + + if (index >= CONFIG_ARCH_HEAP_NPAGES) + { + berr("ERROR: heap window is full (%d pages)\n", + CONFIG_ARCH_HEAP_NPAGES); + return 0; + } + + /* up_addrenv_create() already backs the initial heap allocation, so a + * page may well be present at this index. Reuse it rather than + * leaking it behind a fresh allocation. + */ + + paddr = addrenv->heappages[index]; + if (paddr == 0) + { + paddr = mm_pgalloc(1); + if (paddr == 0) + { + berr("ERROR: page pool exhausted\n"); + return 0; + } + + esp32s3_pgwipe(paddr); + + addrenv->heappages[index] = paddr; + if (index >= addrenv->nheap) + { + addrenv->nheap = index + 1; + } + } + + /* Make the page reachable by the running task */ + + esp32s3_addrenv_mapnew(addrenv, vaddr, paddr); + } + + return brkaddr; +} + +#endif /* CONFIG_BUILD_KERNEL */ +#endif /* CONFIG_MM_PGALLOC */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.c b/arch/xtensa/src/esp32s3/esp32s3_pms.c new file mode 100644 index 0000000000000..e68c284252a5e --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_pms.c @@ -0,0 +1,608 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_pms.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include "chip.h" +#include "xtensa.h" +#include "hardware/esp32s3_apb_ctrl.h" +#include "hardware/esp32s3_cache_memory.h" +#include "hardware/esp32s3_sensitive.h" +#include "hardware/esp32s3_soc.h" + +#include "esp32s3_pms.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Helper just for shortening */ + +#define VALUE_TO_PMS_FIELD(v, f) VALUE_TO_FIELD(v, SENSITIVE_CORE_X_ ## f) + +/* Categories bits for split line configuration */ + +#define PMS_SRAM_CATEGORY_BELOW 0x0 +#define PMS_SRAM_CATEGORY_EQUAL 0x2 +#define PMS_SRAM_CATEGORY_ABOVE 0x3 + +/* Offsets for helping setting values to register fields */ + +#define ICACHE_PMS_W0_BASE 12 +#define ICACHE_PMS_W1_BASE 12 +#define ICACHE_PMS_S 3 +#define ICACHE_PMS_V 7 + +#define IRAM_PMS_W0_BASE 0 +#define IRAM_PMS_W1_BASE 0 +#define IRAM_PMS_S 3 +#define IRAM_PMS_V 7 + +#define DRAM_PMS_W0_BASE 0 +#define DRAM_PMS_W1_BASE 12 +#define DRAM_PMS_S 2 +#define DRAM_PMS_V 3 + +#define FLASH_CACHE_S 3 +#define FLASH_CACHE_V 7 + +#define PIF_PMS_MAX_REG_ENTRY 16 +#define PIF_PMS_V 3 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const intptr_t g_sram_rg3_level_hlimits[] = +{ + 0x4037ffff, /* Block 2 (32KB) */ + 0x4038ffff, /* Block 3 (64KB) */ + 0x4039ffff, /* Block 4 (64KB) */ + 0x403affff, /* Block 5 (64KB) */ + 0x403bffff, /* Block 6 (64KB) */ + 0x403cffff, /* Block 7 (64KB) */ + 0x403dffff /* Block 8 (64KB) */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: set_iram_split_line + * + * Description: + * Split the IRAM region into two sub regions. + * + * Input Parameters: + * addr - Address for the split line. + * sensitive_reg - Register to which the split line configuration will be + * applied. + * + ****************************************************************************/ + +static void set_iram_split_line(uintptr_t addr, const uint32_t sensitive_reg) +{ + /* Set category bits for a given split line */ + + uint32_t cat[7] = + { + [0 ... 6] = PMS_SRAM_CATEGORY_ABOVE + }; + + for (size_t x = 0; x < 7; x++) + { + if (addr <= g_sram_rg3_level_hlimits[x]) + { + cat[x] = PMS_SRAM_CATEGORY_EQUAL; + break; + } + else + { + cat[x] = PMS_SRAM_CATEGORY_BELOW; + } + } + + /* Resolve split address' significant bits. + * Split address must be aligned to 256 bytes. + */ + + uint32_t regval = + VALUE_TO_PMS_FIELD((addr >> 8), IRAM0_DRAM0_DMA_SRAM_SPLITADDR) | + VALUE_TO_PMS_FIELD(cat[6], IRAM0_DRAM0_DMA_SRAM_CATEGORY_6) | + VALUE_TO_PMS_FIELD(cat[5], IRAM0_DRAM0_DMA_SRAM_CATEGORY_5) | + VALUE_TO_PMS_FIELD(cat[4], IRAM0_DRAM0_DMA_SRAM_CATEGORY_4) | + VALUE_TO_PMS_FIELD(cat[3], IRAM0_DRAM0_DMA_SRAM_CATEGORY_3) | + VALUE_TO_PMS_FIELD(cat[2], IRAM0_DRAM0_DMA_SRAM_CATEGORY_2) | + VALUE_TO_PMS_FIELD(cat[1], IRAM0_DRAM0_DMA_SRAM_CATEGORY_1) | + VALUE_TO_PMS_FIELD(cat[0], IRAM0_DRAM0_DMA_SRAM_CATEGORY_0); + + putreg32(regval, sensitive_reg); +} + +/**************************************************************************** + * Name: set_dram_split_line + * + * Description: + * Split the DRAM region into two sub regions. + * + * Input Parameters: + * addr - Address for the split line. + * sensitive_reg - Register to which the split line configuration will be + * applied. + * + ****************************************************************************/ + +static void set_dram_split_line(uintptr_t addr, const uint32_t sensitive_reg) +{ + /* Set category bits for a given split line */ + + uint32_t cat[7] = + { + [0 ... 6] = PMS_SRAM_CATEGORY_ABOVE + }; + + for (size_t x = 0; x < 7; x++) + { + if (addr <= MAP_IRAM_TO_DRAM(g_sram_rg3_level_hlimits[x])) + { + cat[x] = PMS_SRAM_CATEGORY_EQUAL; + break; + } + else + { + cat[x] = PMS_SRAM_CATEGORY_BELOW; + } + } + + /* Resolve split address' significant bits. + * Split address must be aligned to 256 bytes. + */ + + uint32_t regval = + VALUE_TO_PMS_FIELD((addr >> 8), DRAM0_DMA_SRAM_LINE_0_SPLITADDR) | + VALUE_TO_PMS_FIELD(cat[6], DRAM0_DMA_SRAM_LINE_0_CATEGORY_6) | + VALUE_TO_PMS_FIELD(cat[5], DRAM0_DMA_SRAM_LINE_0_CATEGORY_5) | + VALUE_TO_PMS_FIELD(cat[4], DRAM0_DMA_SRAM_LINE_0_CATEGORY_4) | + VALUE_TO_PMS_FIELD(cat[3], DRAM0_DMA_SRAM_LINE_0_CATEGORY_3) | + VALUE_TO_PMS_FIELD(cat[2], DRAM0_DMA_SRAM_LINE_0_CATEGORY_2) | + VALUE_TO_PMS_FIELD(cat[1], DRAM0_DMA_SRAM_LINE_0_CATEGORY_1) | + VALUE_TO_PMS_FIELD(cat[0], DRAM0_DMA_SRAM_LINE_0_CATEGORY_0); + + putreg32(regval, sensitive_reg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pms_set_sram_main_split_line + ****************************************************************************/ + +void esp32s3_pms_set_sram_main_split_line(uintptr_t addr) +{ + set_iram_split_line(addr, + SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_1_REG); +} + +/**************************************************************************** + * Name: esp32s3_pms_set_iram_split_line + ****************************************************************************/ + +void esp32s3_pms_set_iram_split_line(enum pms_split_line_e line, + uintptr_t addr) +{ + switch (line) + { + case PMS_SPLIT_LINE_0: + { + set_iram_split_line(addr, + SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_2_REG); + } + break; + case PMS_SPLIT_LINE_1: + { + set_iram_split_line(addr, + SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_3_REG); + } + break; + default: + { + PANIC(); + } + break; + } +} + +/**************************************************************************** + * Name: esp32s3_pms_set_dram_split_line + ****************************************************************************/ + +void esp32s3_pms_set_dram_split_line(enum pms_split_line_e line, + uintptr_t addr) +{ + switch (line) + { + case PMS_SPLIT_LINE_0: + { + set_dram_split_line(addr, + SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_4_REG); + } + break; + case PMS_SPLIT_LINE_1: + { + set_dram_split_line(addr, + SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_5_REG); + } + break; + default: + { + PANIC(); + } + break; + } +} + +/**************************************************************************** + * Name: esp32s3_pms_set_flash_cache_split_line + ****************************************************************************/ + +void esp32s3_pms_set_flash_cache_split_line(enum pms_split_line_e line, + uintptr_t addr, size_t length) +{ + /* The starting address of each region should be aligned to 64 KB */ + + uintptr_t aligned_addr = ALIGN_DOWN(addr, MMU_PAGE_SIZE); + + /* The length of each region should be the integral multiples of 64 KB */ + + size_t length_pages = length / MMU_PAGE_SIZE; + + switch (line) + { + case PMS_SPLIT_LINE_0: + { + modifyreg32(APB_CTRL_FLASH_ACE0_ADDR_REG, + APB_CTRL_FLASH_ACE0_ADDR_S_M, + VALUE_TO_FIELD(aligned_addr, + APB_CTRL_FLASH_ACE0_ADDR_S)); + modifyreg32(APB_CTRL_FLASH_ACE0_SIZE_REG, + APB_CTRL_FLASH_ACE0_SIZE_M, + VALUE_TO_FIELD(length_pages, + APB_CTRL_FLASH_ACE0_SIZE)); + } + break; + case PMS_SPLIT_LINE_1: + { + modifyreg32(APB_CTRL_FLASH_ACE1_ADDR_REG, + APB_CTRL_FLASH_ACE1_ADDR_S_M, + VALUE_TO_FIELD(aligned_addr, + APB_CTRL_FLASH_ACE1_ADDR_S)); + modifyreg32(APB_CTRL_FLASH_ACE1_SIZE_REG, + APB_CTRL_FLASH_ACE1_SIZE_M, + VALUE_TO_FIELD(length_pages, + APB_CTRL_FLASH_ACE1_SIZE)); + } + break; + case PMS_SPLIT_LINE_2: + { + modifyreg32(APB_CTRL_FLASH_ACE2_ADDR_REG, + APB_CTRL_FLASH_ACE2_ADDR_S_M, + VALUE_TO_FIELD(aligned_addr, + APB_CTRL_FLASH_ACE2_ADDR_S)); + modifyreg32(APB_CTRL_FLASH_ACE2_SIZE_REG, + APB_CTRL_FLASH_ACE2_SIZE_M, + VALUE_TO_FIELD(length_pages, + APB_CTRL_FLASH_ACE2_SIZE)); + } + break; + case PMS_SPLIT_LINE_3: + { + modifyreg32(APB_CTRL_FLASH_ACE3_ADDR_REG, + APB_CTRL_FLASH_ACE3_ADDR_S_M, + VALUE_TO_FIELD(aligned_addr, + APB_CTRL_FLASH_ACE3_ADDR_S)); + modifyreg32(APB_CTRL_FLASH_ACE3_SIZE_REG, + APB_CTRL_FLASH_ACE3_SIZE_M, + VALUE_TO_FIELD(length_pages, + APB_CTRL_FLASH_ACE3_SIZE)); + } + break; + default: + { + PANIC(); + } + break; + } +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_iram_region + ****************************************************************************/ + +void esp32s3_pms_configure_iram_region(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags) +{ + uint32_t reg; + uint32_t offset; + + if (world == PMS_WORLD_0) + { + reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG; + offset = IRAM_PMS_W0_BASE; + } + else + { + reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG; + offset = IRAM_PMS_W1_BASE; + } + + uint32_t shift = offset + (area * IRAM_PMS_S); + uint32_t mask = IRAM_PMS_V << shift; + uint32_t val = (flags & IRAM_PMS_V) << shift; + + modifyreg32(reg, mask, val); +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_icache + ****************************************************************************/ + +void esp32s3_pms_configure_icache(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags) +{ + uint32_t reg; + uint32_t offset; + + if (world == PMS_WORLD_0) + { + reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG; + offset = ICACHE_PMS_W0_BASE; + } + else + { + reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG; + offset = ICACHE_PMS_W1_BASE; + } + + uint32_t shift = offset + (area * ICACHE_PMS_S); + uint32_t mask = ICACHE_PMS_V << shift; + uint32_t val = (flags & ICACHE_PMS_V) << shift; + + modifyreg32(reg, mask, val); +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_dcache + ****************************************************************************/ + +void esp32s3_pms_configure_dcache(enum esp32s3_pms_world_e world, + enum pms_flags_e flags) +{ + switch (world) + { + case PMS_WORLD_0: + { + modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, + SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_0_M + | SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_1_M, + VALUE_TO_PMS_FIELD(flags, + DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_0) + | VALUE_TO_PMS_FIELD(flags, + DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_1)); + } + break; + case PMS_WORLD_1: + { + modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, + SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_0_M + | SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_1_M, + VALUE_TO_PMS_FIELD(flags, + DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_0) + | VALUE_TO_PMS_FIELD(flags, + DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_1)); + } + break; + default: + { + PANIC(); + } + break; + } +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_dram_region + ****************************************************************************/ + +void esp32s3_pms_configure_dram_region(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags) +{ + uint32_t offset; + + if (world == PMS_WORLD_0) + { + offset = DRAM_PMS_W0_BASE; + } + else + { + offset = DRAM_PMS_W1_BASE; + } + + uint32_t shift = offset + (area * DRAM_PMS_S); + uint32_t mask = DRAM_PMS_V << shift; + uint32_t val = (flags & DRAM_PMS_V) << shift; + + modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, mask, val); +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_flash_cache_region + ****************************************************************************/ + +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; + + switch (area) + { + case PMS_AREA_0: + { + modifyreg32(APB_CTRL_FLASH_ACE0_ATTR_REG, mask, val); + } + break; + case PMS_AREA_1: + { + modifyreg32(APB_CTRL_FLASH_ACE1_ATTR_REG, mask, val); + } + break; + case PMS_AREA_2: + { + modifyreg32(APB_CTRL_FLASH_ACE2_ATTR_REG, mask, val); + } + break; + case PMS_AREA_3: + { + modifyreg32(APB_CTRL_FLASH_ACE3_ATTR_REG, mask, val); + } + break; + default: + { + PANIC(); + } + break; + } +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_peripheral + ****************************************************************************/ + +void esp32s3_pms_configure_peripheral(enum pms_peripheral_e periph, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags) +{ + uint32_t reg = 0; + uint32_t reg_off = periph / PIF_PMS_MAX_REG_ENTRY; + uint32_t bit_field_base = 30 - (2 * (periph % PIF_PMS_MAX_REG_ENTRY)); + + switch (world) + { + case PMS_WORLD_0: + { + reg = SENSITIVE_CORE_0_PIF_PMS_CONSTRAIN_1_REG + (4 * reg_off); + } + break; + case PMS_WORLD_1: + { + reg = SENSITIVE_CORE_0_PIF_PMS_CONSTRAIN_5_REG + (4 * reg_off); + } + break; + default: + { + PANIC(); + } + break; + } + + uint32_t mask = PIF_PMS_V << bit_field_base; + uint32_t val = (flags & PIF_PMS_V) << bit_field_base; + + modifyreg32(reg, mask, val); +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_irom_access + ****************************************************************************/ + +void esp32s3_pms_configure_irom_access(void) +{ + /* Kernel permission to the IROM region */ + + modifyreg32(SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG, + SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS_M, + VALUE_TO_PMS_FIELD(PMS_ACCESS_ALL, + IRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS)); + + /* User permission to the IROM region */ + + modifyreg32(SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG, + SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS_M, + VALUE_TO_PMS_FIELD(PMS_ACCESS_NONE, + IRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS)); +} + +/**************************************************************************** + * Name: esp32s3_pms_configure_drom_access + ****************************************************************************/ + +void esp32s3_pms_configure_drom_access(void) +{ + /* Kernel permission to the DROM region */ + + modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, + SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS_M, + VALUE_TO_PMS_FIELD(PMS_ACCESS_ALL, + DRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS)); + + /* User permission to the DROM region */ + + modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, + SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS_M, + VALUE_TO_PMS_FIELD(PMS_ACCESS_NONE, + DRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS)); +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.h b/arch/xtensa/src/esp32s3/esp32s3_pms.h new file mode 100644 index 0000000000000..fe4bb7cc787c6 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_pms.h @@ -0,0 +1,288 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_pms.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PMS_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PMS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "esp32s3_wcl.h" + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Split lines partition a memory type into areas. Up to 4 split lines + * (PMS_SPLIT_LINE_0..3) exist per applicable memory type. + */ + +enum pms_split_line_e +{ + PMS_SPLIT_LINE_0 = 0, + PMS_SPLIT_LINE_1, + PMS_SPLIT_LINE_2, + PMS_SPLIT_LINE_3 +}; + +/* Areas are the regions delimited by the BASE, the split lines and the END + * of a memory type. Up to 4 areas (PMS_AREA_0..3) exist per memory type. + */ + +enum pms_area_e +{ + PMS_AREA_0 = 0, /* Area between BASE and split_line 0 */ + PMS_AREA_1, /* Area between split_line 0 and split_line 1 */ + PMS_AREA_2, /* Area between split_line 1 and END */ + PMS_AREA_3, + PMS_AREA_INVALID +}; + +/* Per-operation access flags applied to a world within an area. */ + +enum pms_flags_e +{ + PMS_ACCESS_NONE = 0, + PMS_ACCESS_R = 1, + PMS_ACCESS_W = 2, + PMS_ACCESS_X = 4, + PMS_ACCESS_ALL = PMS_ACCESS_X | PMS_ACCESS_W | PMS_ACCESS_R +}; + +/* There are 55 peripherals, each having 2 bits for permission configuration. + * These are spread across 4 registers, each register having maximum of 16 + * peripheral entries. + * + * Enum defined as per the bit field position of the peripheral in the + * register: + * FIELD = 30 - 2 * (ENUM % 16) + */ + +enum pms_peripheral_e +{ + PMS_UART1 = 0, + PMS_I2S0, + PMS_I2C, + PMS_MISC, + PMS_HINF = 5, + PMS_IO_MUX = 7, + PMS_RTC, + PMS_FE = 10, + PMS_FE2 = 11, + PMS_GPIO, + PMS_G0SPI_0, + PMS_G0SPI_1, + PMS_UART, + PMS_SYSTIMER, + PMS_TIMERGROUP1, + PMS_TIMERGROUP, + PMS_PWM0, + PMS_BB, + PMS_BACKUP = 22, + PMS_LEDC, + PMS_SLC, + PMS_PCNT, + PMS_RMT, + PMS_SLCHOST, + PMS_UHCI0, + PMS_I2C_EXT0, + PMS_BT = 31, + PMS_PWR = 33, + PMS_WIFIMAC, + PMS_RWBT = 36, + PMS_UART2 = 39, + PMS_I2S1, + PMS_PWM1, + PMS_CAN, + PMS_SDIO_HOST, + PMS_I2C_EXT1, + PMS_APB_CTRL, + PMS_SPI_3, + PMS_SPI_2, + PMS_WORLD_CONTROLLER, + PMS_DIO, + PMS_AD, + PMS_CACHE_CONFIG, + PMS_DMA_COPY, + PMS_INTERRUPT, + PMS_SENSITIVE, + PMS_SYSTEM, + PMS_USB, + PMS_BT_PWR, + PMS_LCD_CAM, + PMS_APB_ADC, + PMS_CRYPTO_DMA, + PMS_CRYPTO_PERI, + PMS_USB_WRAP, + PMS_USB_DEVICE, + PMS_MAX +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pms_set_sram_main_split_line + * + * Description: + * Configure the main Internal SRAM1 Instruction/Data split line. + * + ****************************************************************************/ + +void esp32s3_pms_set_sram_main_split_line(uintptr_t addr); + +/**************************************************************************** + * Name: esp32s3_pms_set_iram_split_line + * + * Description: + * Set one of the IRAM region split lines. + * + ****************************************************************************/ + +void esp32s3_pms_set_iram_split_line(enum pms_split_line_e line, + uintptr_t addr); + +/**************************************************************************** + * Name: esp32s3_pms_set_dram_split_line + * + * Description: + * Set one of the DRAM region split lines. + * + ****************************************************************************/ + +void esp32s3_pms_set_dram_split_line(enum pms_split_line_e line, + uintptr_t addr); + +/**************************************************************************** + * Name: esp32s3_pms_set_flash_cache_split_line + * + * Description: + * Split the External Flash cached region into sub regions. The starting + * address is aligned to 64 KB and the length is expressed in 64 KB pages. + * + ****************************************************************************/ + +void esp32s3_pms_set_flash_cache_split_line(enum pms_split_line_e line, + uintptr_t addr, size_t length); + +/**************************************************************************** + * Name: esp32s3_pms_configure_iram_region + * + * Description: + * Configure access permissions to a given IRAM split region for a world. + * + ****************************************************************************/ + +void esp32s3_pms_configure_iram_region(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags); + +/**************************************************************************** + * Name: esp32s3_pms_configure_dram_region + * + * Description: + * Configure access permissions to a given DRAM split region for a world. + * + ****************************************************************************/ + +void esp32s3_pms_configure_dram_region(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags); + +/**************************************************************************** + * Name: esp32s3_pms_configure_icache + * + * Description: + * Configure access permissions to the Internal SRAM0 blocks not used as + * Instruction Cache, for a world. + * + ****************************************************************************/ + +void esp32s3_pms_configure_icache(enum pms_area_e area, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags); + +/**************************************************************************** + * Name: esp32s3_pms_configure_dcache + * + * Description: + * Configure access permissions to the Internal SRAM2 blocks not used as + * Data Cache, for a world. + * + ****************************************************************************/ + +void esp32s3_pms_configure_dcache(enum esp32s3_pms_world_e world, + enum pms_flags_e flags); + +/**************************************************************************** + * Name: esp32s3_pms_configure_flash_cache_region + * + * Description: + * Configure access permissions to a given External Flash cached split + * region for a world. + * + ****************************************************************************/ + +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_configure_peripheral + * + * Description: + * Configure access permissions to a given peripheral for a world. + * + ****************************************************************************/ + +void esp32s3_pms_configure_peripheral(enum pms_peripheral_e periph, + enum esp32s3_pms_world_e world, + enum pms_flags_e flags); + +/**************************************************************************** + * Name: esp32s3_pms_configure_irom_access + * + * Description: + * Configure the kernel (all) / user (none) access permissions to the IROM + * region. + * + ****************************************************************************/ + +void esp32s3_pms_configure_irom_access(void); + +/**************************************************************************** + * Name: esp32s3_pms_configure_drom_access + * + * Description: + * Configure the kernel (all) / user (none) access permissions to the DROM + * region. + * + ****************************************************************************/ + +void esp32s3_pms_configure_drom_access(void); + +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PMS_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_userspace.c b/arch/xtensa/src/esp32s3/esp32s3_userspace.c index 30257f686978a..4f9731bd667d1 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_userspace.c +++ b/arch/xtensa/src/esp32s3/esp32s3_userspace.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -39,12 +40,13 @@ #include "esp_attr.h" #include "esp_irq.h" #include "esp32s3_userspace.h" -#include "hardware/esp32s3_apb_ctrl.h" +#include "esp32s3_mmu.h" +#include "esp32s3_pms.h" +#include "esp32s3_wcl.h" #include "hardware/esp32s3_cache_memory.h" #include "hardware/esp32s3_rom_layout.h" #include "hardware/esp32s3_sensitive.h" #include "hardware/esp32s3_soc.h" -#include "hardware/esp32s3_wcl_core.h" #include "soc/extmem_reg.h" @@ -60,57 +62,10 @@ #define MMU_SIZE 0x3f0000 #define MMU_BLOCK63_VADDR (MMU_BLOCK0_VADDR + MMU_SIZE) -/* Cache MMU address mask (MMU tables ignore bits which are zero) */ - -#define MMU_FLASH_MASK (~(MMU_PAGE_SIZE - 1)) - -/* Helper just for shortening */ - -#define VALUE_TO_PMS_FIELD(v, f) VALUE_TO_FIELD(v, SENSITIVE_CORE_X_ ## f) - /* Total addressable space is 1GB for the External Memories */ #define EXTMEM_MAX_LENGTH 0x40000000 -/* Maximum number of supported entry addresses */ - -#define WCL_ENTRY_MAX 13 - -/* Last value of the agreed sequence to be written to the address configured - * in WCL_CORE_0_MESSAGE_ADDR register. - */ - -#define WCL_SEQ_LAST_VAL 6 - -/* Categories bits for split line configuration */ - -#define PMS_SRAM_CATEGORY_BELOW 0x0 -#define PMS_SRAM_CATEGORY_EQUAL 0x2 -#define PMS_SRAM_CATEGORY_ABOVE 0x3 - -/* Offsets for helping setting values to register fields */ - -#define ICACHE_PMS_W0_BASE 12 -#define ICACHE_PMS_W1_BASE 12 -#define ICACHE_PMS_S 3 -#define ICACHE_PMS_V 7 - -#define IRAM_PMS_W0_BASE 0 -#define IRAM_PMS_W1_BASE 0 -#define IRAM_PMS_S 3 -#define IRAM_PMS_V 7 - -#define DRAM_PMS_W0_BASE 0 -#define DRAM_PMS_W1_BASE 12 -#define DRAM_PMS_S 2 -#define DRAM_PMS_V 3 - -#define FLASH_CACHE_S 3 -#define FLASH_CACHE_V 7 - -#define PIF_PMS_MAX_REG_ENTRY 16 -#define PIF_PMS_V 3 - /**************************************************************************** * Private Types ****************************************************************************/ @@ -128,224 +83,16 @@ struct user_image_load_header_s uintptr_t irom_size; /* Size of IROM region */ }; -enum pms_world_e -{ - PMS_WORLD_0 = 0, - PMS_WORLD_1 -}; - -enum pms_split_line_e -{ - PMS_SPLIT_LINE_0 = 0, - PMS_SPLIT_LINE_1, - PMS_SPLIT_LINE_2, - PMS_SPLIT_LINE_3 -}; - -enum pms_area_e -{ - PMS_AREA_0 = 0, /* Area between BASE and split_line 0 */ - PMS_AREA_1, /* Area between split_line 0 and split_line 1 */ - PMS_AREA_2, /* Area between split_line 1 and END */ - PMS_AREA_3, - PMS_AREA_INVALID -}; - -enum pms_flags_e -{ - PMS_ACCESS_NONE = 0, - PMS_ACCESS_R = 1, - PMS_ACCESS_W = 2, - PMS_ACCESS_X = 4, - PMS_ACCESS_ALL = PMS_ACCESS_X | PMS_ACCESS_W | PMS_ACCESS_R -}; - -/* There are 55 peripherals, each having 2 bits for permission configuration. - * These are spread across 4 registers, each register having maximum of 16 - * peripheral entries. - * - * Enum defined as per the bit field position of the peripheral in the - * register: - * FIELD = 30 - 2 * (ENUM % 16) - */ - -enum pms_peripheral_e -{ - PMS_UART1 = 0, - PMS_I2S0, - PMS_I2C, - PMS_MISC, - PMS_HINF = 5, - PMS_IO_MUX = 7, - PMS_RTC, - PMS_FE = 10, - PMS_FE2 = 11, - PMS_GPIO, - PMS_G0SPI_0, - PMS_G0SPI_1, - PMS_UART, - PMS_SYSTIMER, - PMS_TIMERGROUP1, - PMS_TIMERGROUP, - PMS_PWM0, - PMS_BB, - PMS_BACKUP = 22, - PMS_LEDC, - PMS_SLC, - PMS_PCNT, - PMS_RMT, - PMS_SLCHOST, - PMS_UHCI0, - PMS_I2C_EXT0, - PMS_BT = 31, - PMS_PWR = 33, - PMS_WIFIMAC, - PMS_RWBT = 36, - PMS_UART2 = 39, - PMS_I2S1, - PMS_PWM1, - PMS_CAN, - PMS_SDIO_HOST, - PMS_I2C_EXT1, - PMS_APB_CTRL, - PMS_SPI_3, - PMS_SPI_2, - PMS_WORLD_CONTROLLER, - PMS_DIO, - PMS_AD, - PMS_CACHE_CONFIG, - PMS_DMA_COPY, - PMS_INTERRUPT, - PMS_SENSITIVE, - PMS_SYSTEM, - PMS_USB, - PMS_BT_PWR, - PMS_LCD_CAM, - PMS_APB_ADC, - PMS_CRYPTO_DMA, - PMS_CRYPTO_PERI, - PMS_USB_WRAP, - PMS_USB_DEVICE, - PMS_MAX -}; - -/**************************************************************************** - * ROM Function Prototypes - ****************************************************************************/ - -extern uint32_t cache_suspend_dcache(void); -extern void cache_resume_dcache(uint32_t val); -extern void cache_invalidate_dcache_all(void); -extern void cache_invalidate_icache_all(void); -extern void cache_writeback_all(void); -extern int cache_dbus_mmu_set(uint32_t ext_ram, uint32_t vaddr, - uint32_t paddr, uint32_t psize, uint32_t num, - uint32_t fixed); -extern int cache_ibus_mmu_set(uint32_t ext_ram, uint32_t vaddr, - uint32_t paddr, uint32_t psize, uint32_t num, - uint32_t fixed); - /**************************************************************************** * Private Data ****************************************************************************/ static struct user_image_load_header_s g_header; -static const intptr_t g_sram_rg3_level_hlimits[] = -{ - 0x4037ffff, /* Block 2 (32KB) */ - 0x4038ffff, /* Block 3 (64KB) */ - 0x4039ffff, /* Block 4 (64KB) */ - 0x403affff, /* Block 5 (64KB) */ - 0x403bffff, /* Block 6 (64KB) */ - 0x403cffff, /* Block 7 (64KB) */ - 0x403dffff /* Block 8 (64KB) */ -}; - /**************************************************************************** * Private Functions ****************************************************************************/ -/**************************************************************************** - * Name: dcache_suspend - * - * Description: - * Helper function for suspending the data cache access for the CPU. - * - * Input Parameters: - * needs_wb - Flag indicating whether the CPU should perform the - * write-back of the data cache contents prior to - * invalidation. - * - * Returned Value: - * Current cache state. - * - ****************************************************************************/ - -static inline uint32_t dcache_suspend(bool needs_wb) -{ - uint32_t dcache_state = cache_suspend_dcache(); - - if (needs_wb) - { - cache_writeback_all(); - } - - cache_invalidate_dcache_all(); - - return dcache_state; -} - -/**************************************************************************** - * Name: dcache_resume - * - * Description: - * Helper function for resuming the data cache access for the CPU. - * - * Input Parameters: - * cache_state - Previously saved data cache state to be restored. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static inline void dcache_resume(uint32_t cache_state) -{ - uint32_t regval; - - regval = getreg32(EXTMEM_DCACHE_CTRL1_REG); - regval &= ~EXTMEM_DCACHE_SHUT_CORE0_BUS; -#ifdef CONFIG_SMP - regval &= ~EXTMEM_DCACHE_SHUT_CORE1_BUS; -#endif - putreg32(regval, EXTMEM_DCACHE_CTRL1_REG); - - cache_resume_dcache(cache_state); -} - -/**************************************************************************** - * Name: calc_mmu_pages - * - * Description: - * Calculate the required number of MMU pages for mapping a given region - * from External Flash into Internal RAM. - * - * Input Parameters: - * size - Length of the region to map - * vaddr - Starting External Flash offset to map to Internal RAM - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static inline uint32_t calc_mmu_pages(uint32_t size, uint32_t vaddr) -{ - return (size + (vaddr - (vaddr & MMU_FLASH_MASK)) + MMU_PAGE_SIZE - 1) / - MMU_PAGE_SIZE; -} - /**************************************************************************** * Name: configure_flash_mmu * @@ -378,23 +125,21 @@ static noinline_function IRAM_ATTR void configure_flash_mmu(void) uint32_t app_irom_size = g_header.irom_size; uint32_t app_irom_vma = g_header.irom_vma; - uint32_t cache_state = dcache_suspend(false); + uint32_t cache_state = esp32s3_dcache_suspend(false); drom_lma_aligned = app_drom_lma & MMU_FLASH_MASK; drom_vma_aligned = app_drom_vma & MMU_FLASH_MASK; - drom_page_count = calc_mmu_pages(app_drom_size, app_drom_vma); - ASSERT(cache_dbus_mmu_set(SOC_MMU_ACCESS_FLASH, drom_vma_aligned, - drom_lma_aligned, 64, - (int)drom_page_count, 0) == 0); + drom_page_count = esp32s3_mmu_calc_pages(app_drom_size, app_drom_vma); + ASSERT(esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_FLASH, drom_vma_aligned, + drom_lma_aligned, drom_page_count) == 0); irom_lma_aligned = app_irom_lma & MMU_FLASH_MASK; irom_vma_aligned = app_irom_vma & MMU_FLASH_MASK; - irom_page_count = calc_mmu_pages(app_irom_size, app_irom_vma); - ASSERT(cache_ibus_mmu_set(SOC_MMU_ACCESS_FLASH, irom_vma_aligned, - irom_lma_aligned, 64, - (int)irom_page_count, 0) == 0); + irom_page_count = esp32s3_mmu_calc_pages(app_irom_size, app_irom_vma); + ASSERT(esp32s3_mmu_map_ibus(SOC_MMU_ACCESS_FLASH, irom_vma_aligned, + irom_lma_aligned, irom_page_count) == 0); - dcache_resume(cache_state); + esp32s3_dcache_resume(cache_state); } /**************************************************************************** @@ -418,15 +163,15 @@ static noinline_function IRAM_ATTR const void *map_flash(uint32_t src_addr, uint32_t src_addr_aligned; uint32_t page_count; - uint32_t cache_state = dcache_suspend(false); + uint32_t cache_state = esp32s3_dcache_suspend(false); src_addr_aligned = src_addr & MMU_FLASH_MASK; - page_count = calc_mmu_pages(size, src_addr); + page_count = esp32s3_mmu_calc_pages(size, src_addr); - ASSERT(cache_dbus_mmu_set(SOC_MMU_ACCESS_FLASH, MMU_BLOCK63_VADDR, - src_addr_aligned, 64, (int)page_count, 0) == 0); + ASSERT(esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_FLASH, MMU_BLOCK63_VADDR, + src_addr_aligned, page_count) == 0); - dcache_resume(cache_state); + esp32s3_dcache_resume(cache_state); return (void *)(MMU_BLOCK63_VADDR + (src_addr - src_addr_aligned)); } @@ -550,102 +295,6 @@ static void initialize_iram(void) } } -/**************************************************************************** - * Name: wcl_set_vecbase - * - * Description: - * Override Vector Table base address via World Controller. - * - * Input Parameters: - * world - World to which the vector table base address will apply - * to. - * vecbase - Vector table base address to set. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void wcl_set_vecbase(enum pms_world_e world, uintptr_t vecbase) -{ - switch (world) - { - case PMS_WORLD_0: - { - modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_1_REG, - SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD0_VALUE_M, - VALUE_TO_FIELD(vecbase >> 10, - SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD0_VALUE)); - } - break; - case PMS_WORLD_1: - { - modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_2_REG, - SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD1_VALUE_M, - VALUE_TO_FIELD(vecbase >> 10, - SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD1_VALUE)); - } - break; - default: - { - PANIC(); - } - break; - } - - modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_1_REG, - SENSITIVE_CORE_0_VECBASE_OVERRIDE_SEL_M, - VALUE_TO_FIELD(0x3, SENSITIVE_CORE_0_VECBASE_OVERRIDE_SEL)); - - modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_0_REG, - SENSITIVE_CORE_0_VECBASE_WORLD_MASK_M, 0); -} - -/**************************************************************************** - * Name: wcl_set_world0_entry - * - * Description: - * Configure the World Controller to switch to World 0 whenever the CPU - * performs an instruction fetch from a given address. - * - * Input Parameters: - * entry - Entry number. Up to 13 entry addresses are supported. - * Entry 0 is reserved and must be skipped. - * addr - Interrupt vector address that will trigger the change to - * World 0. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void wcl_set_world0_entry(uint32_t entry, uintptr_t addr) -{ - ASSERT(entry > 0 && entry <= WCL_ENTRY_MAX); - - /* Configure registers required for cleaning the World Controller write - * buffer upon World0 entry. - * - * Refer to ESP32-S3 Technical Reference Manual, section 16.4.3, for a - * detailed description of the write buffer clearing process. - */ - - putreg32(SOC_RTC_DATA_LOW, WCL_CORE_0_MESSAGE_ADDR_REG); - putreg32(WCL_SEQ_LAST_VAL, WCL_CORE_0_MESSAGE_MAX_REG); - - uint32_t reg = WCL_CORE_0_ENTRY_1_ADDR_REG + ((entry - 1) * 4); - - /* Write ENTRY address */ - - putreg32(addr, reg); - - /* Enable check for that particular address. When fetched, World will - * switch to World 0. - */ - - modifyreg32(WCL_CORE_0_ENTRY_CHECK_REG, 0, BIT(entry)); -} - /**************************************************************************** * Name: pms_violation_isr * @@ -696,13 +345,13 @@ static void pms_enable_interrupts(void) * The vector table for WORLD1 is placed right at the start of WORLD1 IRAM. */ - wcl_set_vecbase(PMS_WORLD_0, VECTORS_START); - wcl_set_vecbase(PMS_WORLD_1, UIRAM_START); + esp32s3_wcl_set_vecbase(PMS_WORLD_0, VECTORS_START); + esp32s3_wcl_set_vecbase(PMS_WORLD_1, UIRAM_START); extern void _user_exception_vector(void); - wcl_set_world0_entry(1, (uintptr_t)_user_exception_vector); + esp32s3_wcl_set_world0_entry(1, (uintptr_t)_user_exception_vector); extern void _xtensa_level3_vector(void); - wcl_set_world0_entry(2, (uintptr_t)_xtensa_level3_vector); + esp32s3_wcl_set_world0_entry(2, (uintptr_t)_xtensa_level3_vector); /* Enable IRAM0 permission violation interrupt */ @@ -741,664 +390,6 @@ static void pms_enable_interrupts(void) SENSITIVE_CORE_0_PIF_PMS_MONITOR_VIOLATE_EN); } -/**************************************************************************** - * Name: set_iram_split_line - * - * Description: - * Split the IRAM region into two sub regions. - * - * Input Parameters: - * addr - Address for the split line. - * sensitive_reg - Register to which the split line configuration will be - * applied. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void set_iram_split_line(uintptr_t addr, const uint32_t sensitive_reg) -{ - /* Set category bits for a given split line */ - - uint32_t cat[7] = - { - [0 ... 6] = PMS_SRAM_CATEGORY_ABOVE - }; - - for (size_t x = 0; x < 7; x++) - { - if (addr <= g_sram_rg3_level_hlimits[x]) - { - cat[x] = PMS_SRAM_CATEGORY_EQUAL; - break; - } - else - { - cat[x] = PMS_SRAM_CATEGORY_BELOW; - } - } - - /* Resolve split address' significant bits. - * Split address must be aligned to 256 bytes. - */ - - uint32_t regval = - VALUE_TO_PMS_FIELD((addr >> 8), IRAM0_DRAM0_DMA_SRAM_SPLITADDR) | - VALUE_TO_PMS_FIELD(cat[6], IRAM0_DRAM0_DMA_SRAM_CATEGORY_6) | - VALUE_TO_PMS_FIELD(cat[5], IRAM0_DRAM0_DMA_SRAM_CATEGORY_5) | - VALUE_TO_PMS_FIELD(cat[4], IRAM0_DRAM0_DMA_SRAM_CATEGORY_4) | - VALUE_TO_PMS_FIELD(cat[3], IRAM0_DRAM0_DMA_SRAM_CATEGORY_3) | - VALUE_TO_PMS_FIELD(cat[2], IRAM0_DRAM0_DMA_SRAM_CATEGORY_2) | - VALUE_TO_PMS_FIELD(cat[1], IRAM0_DRAM0_DMA_SRAM_CATEGORY_1) | - VALUE_TO_PMS_FIELD(cat[0], IRAM0_DRAM0_DMA_SRAM_CATEGORY_0); - - putreg32(regval, sensitive_reg); -} - -/**************************************************************************** - * Name: pms_set_sram_main_split_line - * - * Description: - * Configure the Internal SRAM1 Instruction and Data regions. - * - * Input Parameters: - * addr - Address for the split line. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static inline void pms_set_sram_main_split_line(uintptr_t addr) -{ - set_iram_split_line(addr, - SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_1_REG); -} - -/**************************************************************************** - * Name: pms_set_iram_split_line - * - * Description: - * Helper function for setting the a split line into IRAM region. - * - * Input Parameters: - * line - Split line to be set. - * addr - Address for the split line. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static inline void pms_set_iram_split_line(enum pms_split_line_e line, - uintptr_t addr) -{ - switch (line) - { - case PMS_SPLIT_LINE_0: - { - set_iram_split_line(addr, - SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_2_REG); - } - break; - case PMS_SPLIT_LINE_1: - { - set_iram_split_line(addr, - SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_3_REG); - } - break; - default: - { - PANIC(); - } - break; - } -} - -/**************************************************************************** - * Name: pms_configure_iram_split_region - * - * Description: - * Configure access permissions to a given split region in IRAM. - * - * Input Parameters: - * area - A given region created after setting a split line. - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed within the target area. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_iram_split_region(enum pms_area_e area, - enum pms_world_e world, - enum pms_flags_e flags) -{ - uint32_t reg; - uint32_t offset; - - if (world == PMS_WORLD_0) - { - reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG; - offset = IRAM_PMS_W0_BASE; - } - else - { - reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG; - offset = IRAM_PMS_W1_BASE; - } - - uint32_t shift = offset + (area * IRAM_PMS_S); - uint32_t mask = IRAM_PMS_V << shift; - uint32_t val = (flags & IRAM_PMS_V) << shift; - - modifyreg32(reg, mask, val); -} - -/**************************************************************************** - * Name: pms_configure_icache_permission - * - * Description: - * Configure access permissions to the Internal SRAM 0 blocks that won't be - * used as Instruction Cache. - * - * Input Parameters: - * area - Which of the two SRAM blocks used as Instruction Cache. - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed within the target area. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_icache_permission(enum pms_area_e area, - enum pms_world_e world, - enum pms_flags_e flags) -{ - uint32_t reg; - uint32_t offset; - - if (world == PMS_WORLD_0) - { - reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG; - offset = ICACHE_PMS_W0_BASE; - } - else - { - reg = SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG; - offset = ICACHE_PMS_W1_BASE; - } - - uint32_t shift = offset + (area * ICACHE_PMS_S); - uint32_t mask = ICACHE_PMS_V << shift; - uint32_t val = (flags & ICACHE_PMS_V) << shift; - - modifyreg32(reg, mask, val); -} - -/**************************************************************************** - * Name: pms_configure_dcache_permission - * - * Description: - * Configure access permissions to the Internal SRAM 2 blocks that won't be - * used as Data Cache. - * - * Input Parameters: - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed within the target area. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_dcache_permission(enum pms_world_e world, - enum pms_flags_e flags) -{ - switch (world) - { - case PMS_WORLD_0: - { - modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, - SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_0_M - | SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_1_M, - VALUE_TO_PMS_FIELD(flags, - DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_0) - | VALUE_TO_PMS_FIELD(flags, - DRAM0_PMS_CONSTRAIN_SRAM_WORLD_0_CACHEDATAARRAY_PMS_1)); - } - break; - case PMS_WORLD_1: - { - modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, - SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_0_M - | SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_1_M, - VALUE_TO_PMS_FIELD(flags, - DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_0) - | VALUE_TO_PMS_FIELD(flags, - DRAM0_PMS_CONSTRAIN_SRAM_WORLD_1_CACHEDATAARRAY_PMS_1)); - } - break; - default: - { - PANIC(); - } - break; - } -} - -/**************************************************************************** - * Name: set_dram_split_line - * - * Description: - * Split the DRAM region into two sub regions. - * - * Input Parameters: - * addr - Address for the split line. - * sensitive_reg - Register to which the split line configuration will be - * applied. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void set_dram_split_line(uintptr_t addr, const uint32_t sensitive_reg) -{ - /* Set category bits for a given split line */ - - uint32_t cat[7] = - { - [0 ... 6] = PMS_SRAM_CATEGORY_ABOVE - }; - - for (size_t x = 0; x < 7; x++) - { - if (addr <= MAP_IRAM_TO_DRAM(g_sram_rg3_level_hlimits[x])) - { - cat[x] = PMS_SRAM_CATEGORY_EQUAL; - break; - } - else - { - cat[x] = PMS_SRAM_CATEGORY_BELOW; - } - } - - /* Resolve split address' significant bits. - * Split address must be aligned to 256 bytes. - */ - - uint32_t regval = - VALUE_TO_PMS_FIELD((addr >> 8), DRAM0_DMA_SRAM_LINE_0_SPLITADDR) | - VALUE_TO_PMS_FIELD(cat[6], DRAM0_DMA_SRAM_LINE_0_CATEGORY_6) | - VALUE_TO_PMS_FIELD(cat[5], DRAM0_DMA_SRAM_LINE_0_CATEGORY_5) | - VALUE_TO_PMS_FIELD(cat[4], DRAM0_DMA_SRAM_LINE_0_CATEGORY_4) | - VALUE_TO_PMS_FIELD(cat[3], DRAM0_DMA_SRAM_LINE_0_CATEGORY_3) | - VALUE_TO_PMS_FIELD(cat[2], DRAM0_DMA_SRAM_LINE_0_CATEGORY_2) | - VALUE_TO_PMS_FIELD(cat[1], DRAM0_DMA_SRAM_LINE_0_CATEGORY_1) | - VALUE_TO_PMS_FIELD(cat[0], DRAM0_DMA_SRAM_LINE_0_CATEGORY_0); - - putreg32(regval, sensitive_reg); -} - -/**************************************************************************** - * Name: pms_set_dram_split_line - * - * Description: - * Helper function for setting the a split line into DRAM region. - * - * Input Parameters: - * line - Split line to be set. - * addr - Address for the split line. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_set_dram_split_line(enum pms_split_line_e line, - uintptr_t addr) -{ - switch (line) - { - case PMS_SPLIT_LINE_0: - { - set_dram_split_line(addr, - SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_4_REG); - } - break; - case PMS_SPLIT_LINE_1: - { - set_dram_split_line(addr, - SENSITIVE_CORE_X_IRAM0_DRAM0_DMA_SPLIT_LINE_CONSTRAIN_5_REG); - } - break; - default: - { - PANIC(); - } - break; - } -} - -/**************************************************************************** - * Name: pms_configure_dram_split_region - * - * Description: - * Configure access permissions to a given split region in DRAM. - * - * Input Parameters: - * area - A given region created after setting a split line. - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed within the target area. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_dram_split_region(enum pms_area_e area, - enum pms_world_e world, - enum pms_flags_e flags) -{ - uint32_t offset; - - if (world == PMS_WORLD_0) - { - offset = DRAM_PMS_W0_BASE; - } - else - { - offset = DRAM_PMS_W1_BASE; - } - - uint32_t shift = offset + (area * DRAM_PMS_S); - uint32_t mask = DRAM_PMS_V << shift; - uint32_t val = (flags & DRAM_PMS_V) << shift; - - modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, mask, val); -} - -/**************************************************************************** - * Name: pms_set_flash_cache_split_line - * - * Description: - * Split the External Flash region into sub regions. - * - * Input Parameters: - * line - Split line to be set. - * addr - Starting address for the new region. - * length - Length of the new region in bytes. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_set_flash_cache_split_line(enum pms_split_line_e line, - uintptr_t addr, size_t length) -{ - /* The starting address of each region should be aligned to 64 KB */ - - uintptr_t aligned_addr = ALIGN_DOWN(addr, MMU_PAGE_SIZE); - - /* The length of each region should be the integral multiples of 64 KB */ - - size_t length_pages = length / MMU_PAGE_SIZE; - - switch (line) - { - case PMS_SPLIT_LINE_0: - { - modifyreg32(APB_CTRL_FLASH_ACE0_ADDR_REG, - APB_CTRL_FLASH_ACE0_ADDR_S_M, - VALUE_TO_FIELD(aligned_addr, - APB_CTRL_FLASH_ACE0_ADDR_S)); - modifyreg32(APB_CTRL_FLASH_ACE0_SIZE_REG, - APB_CTRL_FLASH_ACE0_SIZE_M, - VALUE_TO_FIELD(length_pages, - APB_CTRL_FLASH_ACE0_SIZE)); - } - break; - case PMS_SPLIT_LINE_1: - { - modifyreg32(APB_CTRL_FLASH_ACE1_ADDR_REG, - APB_CTRL_FLASH_ACE1_ADDR_S_M, - VALUE_TO_FIELD(aligned_addr, - APB_CTRL_FLASH_ACE1_ADDR_S)); - modifyreg32(APB_CTRL_FLASH_ACE1_SIZE_REG, - APB_CTRL_FLASH_ACE1_SIZE_M, - VALUE_TO_FIELD(length_pages, - APB_CTRL_FLASH_ACE1_SIZE)); - } - break; - case PMS_SPLIT_LINE_2: - { - modifyreg32(APB_CTRL_FLASH_ACE2_ADDR_REG, - APB_CTRL_FLASH_ACE2_ADDR_S_M, - VALUE_TO_FIELD(aligned_addr, - APB_CTRL_FLASH_ACE2_ADDR_S)); - modifyreg32(APB_CTRL_FLASH_ACE2_SIZE_REG, - APB_CTRL_FLASH_ACE2_SIZE_M, - VALUE_TO_FIELD(length_pages, - APB_CTRL_FLASH_ACE2_SIZE)); - } - break; - case PMS_SPLIT_LINE_3: - { - modifyreg32(APB_CTRL_FLASH_ACE3_ADDR_REG, - APB_CTRL_FLASH_ACE3_ADDR_S_M, - VALUE_TO_FIELD(aligned_addr, - APB_CTRL_FLASH_ACE3_ADDR_S)); - modifyreg32(APB_CTRL_FLASH_ACE3_SIZE_REG, - APB_CTRL_FLASH_ACE3_SIZE_M, - VALUE_TO_FIELD(length_pages, - APB_CTRL_FLASH_ACE3_SIZE)); - } - break; - default: - { - PANIC(); - } - break; - } -} - -/**************************************************************************** - * Name: pms_configure_flash_cache_split_region - * - * Description: - * Configure access permissions to a given split region in External Flash. - * - * Input Parameters: - * area - A given region created after setting a split line. - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed within the target area. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void -pms_configure_flash_cache_split_region(enum pms_area_e area, - enum 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; - - switch (area) - { - case PMS_AREA_0: - { - modifyreg32(APB_CTRL_FLASH_ACE0_ATTR_REG, mask, val); - } - break; - case PMS_AREA_1: - { - modifyreg32(APB_CTRL_FLASH_ACE1_ATTR_REG, mask, val); - } - break; - case PMS_AREA_2: - { - modifyreg32(APB_CTRL_FLASH_ACE2_ATTR_REG, mask, val); - } - break; - case PMS_AREA_3: - { - modifyreg32(APB_CTRL_FLASH_ACE3_ATTR_REG, mask, val); - } - break; - default: - { - PANIC(); - } - break; - } -} - -/**************************************************************************** - * Name: pms_configure_peripheral_permission - * - * Description: - * Configure access permissions to a given peripheral. - * - * Input Parameters: - * periph - A given peripheral to be configured. - * world - World to which the flags will apply to. - * flags - Attributes representing the operations allowed to be - * performed with the selected peripheral. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_peripheral_permission(enum pms_peripheral_e periph, - enum pms_world_e world, - enum pms_flags_e flags) -{ - uint32_t reg = 0; - uint32_t reg_off = periph / PIF_PMS_MAX_REG_ENTRY; - uint32_t bit_field_base = 30 - (2 * (periph % PIF_PMS_MAX_REG_ENTRY)); - - switch (world) - { - case PMS_WORLD_0: - { - reg = SENSITIVE_CORE_0_PIF_PMS_CONSTRAIN_1_REG + (4 * reg_off); - } - break; - case PMS_WORLD_1: - { - reg = SENSITIVE_CORE_0_PIF_PMS_CONSTRAIN_5_REG + (4 * reg_off); - } - break; - default: - { - PANIC(); - } - break; - } - - uint32_t mask = PIF_PMS_V << bit_field_base; - uint32_t val = (flags & PIF_PMS_V) << bit_field_base; - - modifyreg32(reg, mask, val); -} - -/**************************************************************************** - * Name: pms_configure_irom_access - * - * Description: - * Configure the access permissions to the IROM region. - * - * Input Parameters: - * None. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_irom_access(void) -{ - /* Kernel permission to the IROM region */ - - modifyreg32(SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_2_REG, - SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS_M, - VALUE_TO_PMS_FIELD(PMS_ACCESS_ALL, - IRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS)); - - /* User permission to the IROM region */ - - modifyreg32(SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_1_REG, - SENSITIVE_CORE_X_IRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS_M, - VALUE_TO_PMS_FIELD(PMS_ACCESS_NONE, - IRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS)); -} - -/**************************************************************************** - * Name: pms_configure_drom_access - * - * Description: - * Configure the access permissions to the DROM region. - * - * Input Parameters: - * None. - * - * Returned Value: - * None. - * - ****************************************************************************/ - -static void pms_configure_drom_access(void) -{ - /* Kernel permission to the DROM region */ - - modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, - SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS_M, - VALUE_TO_PMS_FIELD(PMS_ACCESS_ALL, - DRAM0_PMS_CONSTRAIN_ROM_WORLD_0_PMS)); - - /* User permission to the DROM region */ - - modifyreg32(SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_1_REG, - SENSITIVE_CORE_X_DRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS_M, - VALUE_TO_PMS_FIELD(PMS_ACCESS_NONE, - DRAM0_PMS_CONSTRAIN_ROM_WORLD_1_PMS)); -} - /**************************************************************************** * Name: pms_configure_iram_access * @@ -1417,36 +408,36 @@ static void pms_configure_iram_access(void) { /* Kernel permission to the Instruction Cache */ - pms_configure_icache_permission(PMS_AREA_0, PMS_WORLD_0, PMS_ACCESS_ALL); - pms_configure_icache_permission(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL); + 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); /* User permission to the Instruction Cache */ - pms_configure_icache_permission(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_icache(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_NONE); #ifdef CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB /* In case the Instruction Cache size is configured to 16KB, the other 16KB * block from Internal SRAM0 (Block1) will be used as IRAM. */ - pms_configure_icache_permission(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_ALL); + esp32s3_pms_configure_icache(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_ALL); #else /* CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB */ /* In case the Instruction Cache size is configured to 32KB, the WORLD1 * access permissions to the Internal SRAM0 must be revoked. */ - pms_configure_icache_permission(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_icache(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_NONE); #endif /* Set split lines to partition the IRAM into regions */ - pms_set_iram_split_line(PMS_SPLIT_LINE_0, UIRAM_END); - pms_set_iram_split_line(PMS_SPLIT_LINE_1, KIRAM_END); + esp32s3_pms_set_iram_split_line(PMS_SPLIT_LINE_0, UIRAM_END); + esp32s3_pms_set_iram_split_line(PMS_SPLIT_LINE_1, KIRAM_END); /* Configure Kernel access permissions to each split region */ - pms_configure_iram_split_region(PMS_AREA_0, PMS_WORLD_0, PMS_ACCESS_ALL); - pms_configure_iram_split_region(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL); - pms_configure_iram_split_region(PMS_AREA_2, PMS_WORLD_0, PMS_ACCESS_ALL); + 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); /* Configure User access permissions to each split region */ @@ -1456,24 +447,29 @@ static void pms_configure_iram_access(void) * we can safely revoke permissions to the shared Internal SRAM1. */ - pms_configure_iram_split_region(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_iram_region(PMS_AREA_0, PMS_WORLD_1, + PMS_ACCESS_NONE); #else /* CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB */ /* In case the Instruction Cache size is configured to 32KB, the first * block from the shared Internal SRAM1 (Block2) will be allocated to * WORLD1. */ - pms_configure_iram_split_region(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_ALL); + esp32s3_pms_configure_iram_region(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_ALL); #endif - pms_configure_iram_split_region(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_NONE); - pms_configure_iram_split_region(PMS_AREA_2, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_iram_region(PMS_AREA_1, PMS_WORLD_1, + PMS_ACCESS_NONE); + esp32s3_pms_configure_iram_region(PMS_AREA_2, PMS_WORLD_1, + PMS_ACCESS_NONE); /* PMS_AREA_3 corresponds to the region after the main split line, * i.e. the entire DRAM. */ - pms_configure_iram_split_region(PMS_AREA_3, PMS_WORLD_0, PMS_ACCESS_NONE); - pms_configure_iram_split_region(PMS_AREA_3, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_iram_region(PMS_AREA_3, PMS_WORLD_0, + PMS_ACCESS_NONE); + esp32s3_pms_configure_iram_region(PMS_AREA_3, PMS_WORLD_1, + PMS_ACCESS_NONE); } /**************************************************************************** @@ -1494,11 +490,11 @@ static void pms_configure_dram_access(void) { /* Kernel permission to the Data Cache */ - pms_configure_dcache_permission(PMS_WORLD_0, PMS_ACCESS_ALL); + esp32s3_pms_configure_dcache(PMS_WORLD_0, PMS_ACCESS_ALL); /* User permission to the Data Cache */ - pms_configure_dcache_permission(PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_dcache(PMS_WORLD_1, PMS_ACCESS_NONE); /* Split line to protect DRAM area reserved for ROM functions. * ALIGN_DOWN macro is used to align the address to 256 bit boundary. @@ -1509,27 +505,31 @@ static void pms_configure_dram_access(void) /* Set split lines to partition the DRAM into regions */ - pms_set_dram_split_line(PMS_SPLIT_LINE_0, UDRAM_START); - pms_set_dram_split_line(PMS_SPLIT_LINE_1, rom_reserve_aligned); + esp32s3_pms_set_dram_split_line(PMS_SPLIT_LINE_0, UDRAM_START); + esp32s3_pms_set_dram_split_line(PMS_SPLIT_LINE_1, rom_reserve_aligned); /* PMS_AREA_0 corresponds to the region before the main split line, * i.e entire IRAM. */ - pms_configure_dram_split_region(PMS_AREA_0, PMS_WORLD_0, PMS_ACCESS_NONE); - pms_configure_dram_split_region(PMS_AREA_0, PMS_WORLD_1, PMS_ACCESS_NONE); + esp32s3_pms_configure_dram_region(PMS_AREA_0, PMS_WORLD_0, + PMS_ACCESS_NONE); + esp32s3_pms_configure_dram_region(PMS_AREA_0, PMS_WORLD_1, + PMS_ACCESS_NONE); /* Configure Kernel access permissions to each split region */ - pms_configure_dram_split_region(PMS_AREA_1, PMS_WORLD_0, PMS_ACCESS_ALL); - pms_configure_dram_split_region(PMS_AREA_2, PMS_WORLD_0, PMS_ACCESS_ALL); - pms_configure_dram_split_region(PMS_AREA_3, PMS_WORLD_0, PMS_ACCESS_ALL); + 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); /* Configure User access permissions to each split region */ - pms_configure_dram_split_region(PMS_AREA_1, PMS_WORLD_1, PMS_ACCESS_NONE); - pms_configure_dram_split_region(PMS_AREA_2, PMS_WORLD_1, PMS_ACCESS_ALL); - pms_configure_dram_split_region(PMS_AREA_3, 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_ALL); + esp32s3_pms_configure_dram_region(PMS_AREA_3, PMS_WORLD_1, + PMS_ACCESS_NONE); } /**************************************************************************** @@ -1551,8 +551,8 @@ static IRAM_ATTR void pms_configure_flash_cache_access(void) { /* Invalidate Cache */ - uint32_t cache_state = dcache_suspend(true); - cache_invalidate_icache_all(); + uint32_t cache_state = esp32s3_dcache_suspend(true); + esp32s3_icache_invalidate_all(); size_t partition_offset = USER_IMAGE_OFFSET; @@ -1576,43 +576,43 @@ static IRAM_ATTR void pms_configure_flash_cache_access(void) uint32_t region3_start_addr = region2_start_addr + region2_size; uint32_t region3_size = remaining_size / 2; - pms_set_flash_cache_split_line(PMS_SPLIT_LINE_0, region0_start_addr, - region0_size); - pms_set_flash_cache_split_line(PMS_SPLIT_LINE_1, region1_start_addr, - region1_size); - pms_set_flash_cache_split_line(PMS_SPLIT_LINE_2, region2_start_addr, - region2_size); - pms_set_flash_cache_split_line(PMS_SPLIT_LINE_3, region3_start_addr, - region3_size); + esp32s3_pms_set_flash_cache_split_line(PMS_SPLIT_LINE_0, + region0_start_addr, region0_size); + esp32s3_pms_set_flash_cache_split_line(PMS_SPLIT_LINE_1, + region1_start_addr, region1_size); + esp32s3_pms_set_flash_cache_split_line(PMS_SPLIT_LINE_2, + region2_start_addr, region2_size); + esp32s3_pms_set_flash_cache_split_line(PMS_SPLIT_LINE_3, + region3_start_addr, region3_size); /* Configure Kernel access permissions to each split region */ - pms_configure_flash_cache_split_region(PMS_AREA_0, PMS_WORLD_0, - PMS_ACCESS_ALL); + esp32s3_pms_configure_flash_cache_region(PMS_AREA_0, PMS_WORLD_0, + PMS_ACCESS_ALL); /* WORLD0 requires access to WORLD1 to load the cache when returning to * WORLD1 from WORLD0. */ - pms_configure_flash_cache_split_region(PMS_AREA_1, PMS_WORLD_0, - PMS_ACCESS_ALL); - pms_configure_flash_cache_split_region(PMS_AREA_2, PMS_WORLD_0, - PMS_ACCESS_ALL); - pms_configure_flash_cache_split_region(PMS_AREA_3, 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); /* Configure User access permissions to each split region */ - pms_configure_flash_cache_split_region(PMS_AREA_0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_flash_cache_split_region(PMS_AREA_1, PMS_WORLD_1, - PMS_ACCESS_ALL); - pms_configure_flash_cache_split_region(PMS_AREA_2, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_flash_cache_split_region(PMS_AREA_3, PMS_WORLD_1, - PMS_ACCESS_NONE); + 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_ALL); + 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); - dcache_resume(cache_state); + esp32s3_dcache_resume(cache_state); } /**************************************************************************** @@ -1634,114 +634,81 @@ static void pms_configure_peripheral_access(void) { /* Revoke User access permission to every peripheral */ - pms_configure_peripheral_permission(PMS_UART1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_I2C, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_MISC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_IO_MUX, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_RTC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_FE, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_FE2, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_GPIO, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_G0SPI_0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_G0SPI_1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_UART, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SYSTIMER, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_TIMERGROUP1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_TIMERGROUP, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_BB, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_LEDC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_RMT, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_UHCI0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_I2C_EXT0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_BT, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_PWR, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_WIFIMAC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_RWBT, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_I2S1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_CAN, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_APB_CTRL, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SPI_2, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_WORLD_CONTROLLER, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_DIO, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_AD, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_CACHE_CONFIG, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_DMA_COPY, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_INTERRUPT, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SENSITIVE, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SYSTEM, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_BT_PWR, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_APB_ADC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_CRYPTO_DMA, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_CRYPTO_PERI, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_USB_WRAP, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_USB_DEVICE, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_I2S0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_HINF, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_PWM0, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_BACKUP, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SLC, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_PCNT, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SLCHOST, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_UART2, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_PWM1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SDIO_HOST, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_I2C_EXT1, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_SPI_3, PMS_WORLD_1, - PMS_ACCESS_NONE); - pms_configure_peripheral_permission(PMS_USB, PMS_WORLD_1, - PMS_ACCESS_NONE); + 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); } /**************************************************************************** @@ -1768,7 +735,7 @@ static void configure_mpu(void) * split lines. */ - pms_set_sram_main_split_line(KIRAM_END); + esp32s3_pms_set_sram_main_split_line(KIRAM_END); /* Configure Kernel and Userspace permissions for accessing the internal * memories. @@ -1776,11 +743,11 @@ static void configure_mpu(void) /* IROM */ - pms_configure_irom_access(); + esp32s3_pms_configure_irom_access(); /* DROM */ - pms_configure_drom_access(); + esp32s3_pms_configure_drom_access(); /* IRAM */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_wcl.c b/arch/xtensa/src/esp32s3/esp32s3_wcl.c new file mode 100644 index 0000000000000..d66e836b321c0 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_wcl.c @@ -0,0 +1,127 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_wcl.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include + +#include "chip.h" +#include "xtensa.h" +#include "hardware/esp32s3_sensitive.h" +#include "hardware/esp32s3_soc.h" +#include "hardware/esp32s3_wcl_core.h" + +#include "esp32s3_wcl.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Maximum number of supported entry addresses */ + +#define WCL_ENTRY_MAX 13 + +/* Last value of the agreed sequence to be written to the address configured + * in WCL_CORE_0_MESSAGE_ADDR register. + */ + +#define WCL_SEQ_LAST_VAL 6 + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_wcl_set_vecbase + ****************************************************************************/ + +void esp32s3_wcl_set_vecbase(enum esp32s3_pms_world_e world, + uintptr_t vecbase) +{ + switch (world) + { + case PMS_WORLD_0: + { + modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_1_REG, + SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD0_VALUE_M, + VALUE_TO_FIELD(vecbase >> 10, + SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD0_VALUE)); + } + break; + case PMS_WORLD_1: + { + modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_2_REG, + SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD1_VALUE_M, + VALUE_TO_FIELD(vecbase >> 10, + SENSITIVE_CORE_0_VECBASE_OVERRIDE_WORLD1_VALUE)); + } + break; + default: + { + PANIC(); + } + break; + } + + modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_1_REG, + SENSITIVE_CORE_0_VECBASE_OVERRIDE_SEL_M, + VALUE_TO_FIELD(0x3, SENSITIVE_CORE_0_VECBASE_OVERRIDE_SEL)); + + modifyreg32(SENSITIVE_CORE_0_VECBASE_OVERRIDE_0_REG, + SENSITIVE_CORE_0_VECBASE_WORLD_MASK_M, 0); +} + +/**************************************************************************** + * Name: esp32s3_wcl_set_world0_entry + ****************************************************************************/ + +void esp32s3_wcl_set_world0_entry(uint32_t entry, uintptr_t addr) +{ + ASSERT(entry > 0 && entry <= WCL_ENTRY_MAX); + + /* Configure registers required for cleaning the World Controller write + * buffer upon World0 entry. + * + * Refer to ESP32-S3 Technical Reference Manual, section 16.4.3, for a + * detailed description of the write buffer clearing process. + */ + + putreg32(SOC_RTC_DATA_LOW, WCL_CORE_0_MESSAGE_ADDR_REG); + putreg32(WCL_SEQ_LAST_VAL, WCL_CORE_0_MESSAGE_MAX_REG); + + uint32_t reg = WCL_CORE_0_ENTRY_1_ADDR_REG + ((entry - 1) * 4); + + /* Write ENTRY address */ + + putreg32(addr, reg); + + /* Enable check for that particular address. When fetched, World will + * switch to World 0. + */ + + modifyreg32(WCL_CORE_0_ENTRY_CHECK_REG, 0, BIT(entry)); +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_wcl.h b/arch/xtensa/src/esp32s3/esp32s3_wcl.h new file mode 100644 index 0000000000000..b4be24467a71f --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_wcl.h @@ -0,0 +1,90 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_wcl.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_WCL_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_WCL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* The ESP32-S3 World Controller provides a binary privileged (WORLD_0, + * kernel) / non-privileged (WORLD_1, user) split. The world identifier is + * shared vocabulary between the World Controller and the PMS permission + * subsystem, so it is defined here at the lowest layer. + */ + +enum esp32s3_pms_world_e +{ + PMS_WORLD_0 = 0, + PMS_WORLD_1 +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_wcl_set_vecbase + * + * Description: + * Override the Vector Table base address for a given world via the World + * Controller. + * + * Input Parameters: + * world - World to which the vector table base address will apply. + * vecbase - Vector table base address to set. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void esp32s3_wcl_set_vecbase(enum esp32s3_pms_world_e world, + uintptr_t vecbase); + +/**************************************************************************** + * Name: esp32s3_wcl_set_world0_entry + * + * Description: + * Configure the World Controller to switch to World 0 whenever the CPU + * performs an instruction fetch from a given address. + * + * Input Parameters: + * entry - Entry number. Up to 13 entry addresses are supported. Entry 0 + * is reserved and must be skipped. + * addr - Vector fetch address that triggers the switch to World 0. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void esp32s3_wcl_set_world0_entry(uint32_t entry, uintptr_t addr); + +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_WCL_H */ diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/.gitignore b/boards/xtensa/esp32s3/esp32s3-devkit/.gitignore new file mode 100644 index 0000000000000..2f370e942d567 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/.gitignore @@ -0,0 +1 @@ +src/romfs_boot.c From c6456f8a45242cf70fe47b0bf349dc56d2682aef Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 14:16:03 +0200 Subject: [PATCH 07/22] boards/esp32s3-devkit: add a kernel-build configuration kernel_oct, with the user-program layout and the boot ROMFS a kernel build loads its programs from. The ROMFS placeholder is rebuilt with the image, the generated copy is ignored, and the programs are given stack sizes and room for a fork() child. Folds in: esp32s3-devkit: user-program layout and boot ROMFS for kernel builds boards/esp32s3-devkit: add a kernel-build configuration boards/esp32s3-devkit: give kernel_oct's programs their stacks back esp32s3-devkit: ignore the generated boot ROMFS boards/esp32s3-devkit: rebuild the ROMFS placeholder with the image boards/esp32s3-devkit: leave kernel_oct room for a fork() child Signed-off-by: Marco Casaroli Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- .../configs/kernel_oct/defconfig | 101 +++++++++++ .../esp32s3/esp32s3-devkit/scripts/gnu-elf.ld | 159 ++++++++++++++++++ .../esp32s3/esp32s3-devkit/src/.gitignore | 1 + .../esp32s3/esp32s3-devkit/src/Make.defs | 27 +++ .../esp32s3-devkit/src/esp32s3_bringup.c | 39 +++++ .../xtensa/esp32s3/esp32s3-devkit/src/romfs.h | 39 +++++ .../esp32s3/esp32s3-devkit/src/romfs_stub.c | 56 ++++++ 7 files changed, 422 insertions(+) create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/src/.gitignore create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/src/romfs.h create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.c diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig new file mode 100644 index 0000000000000..0e494c08f5cc5 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig @@ -0,0 +1,101 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_LEDS is not set +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +CONFIG_ARCH="xtensa" +CONFIG_ARCH_ADDRENV=y +CONFIG_ARCH_BOARD="esp32s3-devkit" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32S3_DEVKIT=y +CONFIG_ARCH_CHIP="esp32s3" +CONFIG_ARCH_CHIP_ESP32S3=y +CONFIG_ARCH_CHIP_ESP32S3WROOM2N32R8V=y +CONFIG_ARCH_DATA_NPAGES=8 +CONFIG_ARCH_DATA_VBASE=0x3d000000 +CONFIG_ARCH_HEAP_NPAGES=8 +CONFIG_ARCH_HEAP_VBASE=0x3d200000 +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_IRQ_TO_NDX=y +CONFIG_ARCH_KERNEL_STACKSIZE=8192 +CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y +CONFIG_ARCH_NUSER_INTERRUPTS=2 +CONFIG_ARCH_PGPOOL_MAPPING=y +CONFIG_ARCH_PGPOOL_PBASE=0x360000 +CONFIG_ARCH_PGPOOL_SIZE=4194304 +CONFIG_ARCH_PGPOOL_VBASE=0x3c400000 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_TEXT_NPAGES=8 +CONFIG_ARCH_TEXT_VBASE=0x42800000 +CONFIG_ARCH_USE_MMU=y +CONFIG_ARCH_XTENSA=y +CONFIG_BINFMT_ELF_EXECUTABLE=y +CONFIG_BOARD_INITTHREAD_STACKSIZE=2048 +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILD_KERNEL=y +CONFIG_DEBUG_BINFMT=y +CONFIG_DEBUG_BINFMT_ERROR=y +CONFIG_DEBUG_BINFMT_INFO=y +CONFIG_DEBUG_BINFMT_WARN=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SCHED=y +CONFIG_DEBUG_SCHED_ERROR=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEFAULT_TASK_STACKSIZE=8192 +CONFIG_ELF=y +CONFIG_ESP32S3_FLASH_MODE_OCT=y +CONFIG_ESP32S3_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESP32S3_SPIFLASH=y +CONFIG_ESP32S3_SPIRAM=y +CONFIG_ESP32S3_SPIRAM_MODE_OCT=y +CONFIG_ESP32S3_UART0=y +CONFIG_ESP32S3_WCL=y +CONFIG_FS_PROCFS=y +CONFIG_FS_ROMFS=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_HOST_MACOS=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_FILEPATH="/system/bin/init" +CONFIG_INIT_MOUNT=y +CONFIG_INIT_MOUNT_FLAGS=0x1 +CONFIG_INIT_MOUNT_TARGET="/system/bin" +CONFIG_INTELHEX_BINARY=y +CONFIG_IRQ_WORK_STACKSIZE=2048 +CONFIG_LIBC_ENVPATH=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LINE_MAX=64 +CONFIG_MM_PGALLOC=y +CONFIG_MM_PGSIZE=65536 +CONFIG_NSH_DISABLE_LOSMART=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_FILE_APPS=y +CONFIG_NSH_READLINE=y +CONFIG_PATH_INITIAL="/system/bin" +CONFIG_PREALLOC_TIMERS=4 +CONFIG_PTHREAD_STACK_DEFAULT=2048 +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RAW_BINARY=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_HAVE_PARENT=y +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_LPWORKSTACKSIZE=2048 +CONFIG_SCHED_WAITPID=y +CONFIG_STACK_COLORATION=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_NSH_PROGNAME="init" +CONFIG_TESTING_GETPRIME=y +CONFIG_TESTING_OSTEST=y +CONFIG_TESTING_OSTEST_FPUTESTDISABLE=y +CONFIG_UART0_SERIAL_CONSOLE=y diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld b/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld new file mode 100644 index 0000000000000..b6eb89085abf6 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld @@ -0,0 +1,159 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld + * + * 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. + * + ****************************************************************************/ + +/* Link script for the user programs of a kernel build. A fully linked + * executable is loaded at the addresses it was linked for -- binfmt takes + * the entry point straight out of the ELF header -- so these must match the + * address environment the ESP32-S3 gives a user process: + * + * .text goes to CONFIG_ARCH_TEXT_VBASE, a slot of the instruction-bus + * cache-MMU window (0x42000000-0x44000000). + * .data goes to CONFIG_ARCH_DATA_VBASE plus one page: up_addrenv_create() + * keeps the first page of the data window for the OS reserve (the + * heap bookkeeping and the signal trampoline pointer). The window + * is a slot of the data-bus range (0x3C000000-0x3E000000). + * + * .rodata sits with the data, not with the text: the ESP32-S3 selects + * CONFIG_ARCH_HAVE_TEXT_HEAP_WORD_ALIGNED_READ, so the loader places every + * section that is not executable into the data region, and unaligned reads + * through the instruction bus are not reliable anyway. + * + * The loader packs allocatable sections back to back in section-header + * order, honouring only each section's own alignment, so the layout below + * must not introduce gaps of its own. + * + * Keep these addresses in step with CONFIG_ARCH_TEXT_VBASE, + * CONFIG_ARCH_DATA_VBASE and CONFIG_MM_PGSIZE in the board defconfig. + */ + +SECTIONS +{ + . = 0x42800000; + + .text : + { + _stext = . ; + *(.literal .text .literal.* .text.*) + *(.gnu.warning) + *(.stub) + *(.jcr) + + /* C++ support: The .init and .fini sections contain specific logic + * to manage static constructors and destructors. + */ + + *(.gnu.linkonce.t.*) + *(.init) + *(.fini) + . = ALIGN(4); + _etext = . ; + } + + . = 0x3d010000; + + .rodata : + { + _srodata = . ; + *(.rodata) + *(.rodata1) + *(.rodata.*) + *(.gnu.linkonce.r*) + . = ALIGN(4); + _erodata = . ; + } + + /* Placed explicitly, and here, because the loader assigns it to the data + * region -- it is allocatable but not executable -- and packs it directly + * after .rodata. + */ + + .eh_frame : + { + KEEP (*(.eh_frame)) + *(.eh_frame.*) + } + + .data : + { + _sdata = . ; + *(.data) + *(.data1) + *(.data.*) + *(.gnu.linkonce.d*) + . = ALIGN(4); + _edata = . ; + } + + .init_array : + { + _sinit = .; + _sctors = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array .ctors)) + . = ALIGN(4); + _einit = .; + _ectors = .; + } + + .fini_array : + { + _sfini = .; + _sdtors = .; + KEEP (*(.dtors)) + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + . = ALIGN(4); + _efini = .; + _edtors = .; + } + + .bss : + { + _sbss = . ; + *(.bss) + *(.bss.*) + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.b*) + *(COMMON) + . = ALIGN(4); + _ebss = . ; + } + + .got : + { + *(.got*) + } + + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_info 0 : { *(.debug_info) } + .debug_line 0 : { *(.debug_line) } + .debug_pubnames 0 : { *(.debug_pubnames) } + .debug_aranges 0 : { *(.debug_aranges) } +} diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/.gitignore b/boards/xtensa/esp32s3/esp32s3-devkit/src/.gitignore new file mode 100644 index 0000000000000..a33bdfee20028 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/.gitignore @@ -0,0 +1 @@ +romfs_boot.c diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/Make.defs b/boards/xtensa/esp32s3/esp32s3-devkit/src/Make.defs index 496bb5d4323ae..6b51a1941245e 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/src/Make.defs +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/Make.defs @@ -22,6 +22,33 @@ CSRCS = esp32s3_boot.c esp32s3_bringup.c +# A kernel build boots its init process out of a ROMFS image built from +# apps/bin by apps/tools/mkromfsimg.sh. Until that has been generated, +# romfs_stub.c supplies an empty placeholder so the kernel still builds. +# +# The placeholder is always compiled, and defines the image away once the +# real one exists, rather than being swapped out for it: both define +# romfs_img, so an object left behind by an earlier build would otherwise +# still satisfy the link and quietly produce a kernel with no init process. + +ifeq ($(CONFIG_BUILD_KERNEL),y) +CSRCS += romfs_stub.c +ifneq ($(wildcard $(BOARD_DIR)$(DELIM)src$(DELIM)romfs_boot.c),) +CSRCS += romfs_boot.c +CFLAGS += -DHAVE_ROMFS_BOOT + +# Defining the placeholder away is a change in CFLAGS, and make cannot see +# one: an object compiled before the image was generated is newer than its +# source and is not rebuilt. It still defines romfs_img, and being the +# earlier member of libboard.a it is the one that satisfies the link -- so +# the kernel boots with a one-byte ROMFS and cannot start init, in a build +# where nothing failed. Tie the placeholder to the generated image so that +# it is recompiled whenever the image is regenerated. + +romfs_stub$(OBJEXT): $(BOARD_DIR)$(DELIM)src$(DELIM)romfs_boot.c +endif +endif + ifeq ($(CONFIG_BOARDCTL_RESET),y) CSRCS += esp32s3_reset.c endif diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c b/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c index 7be79bcc86f8c..c3234c6b8d423 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c @@ -163,8 +163,22 @@ # endif #endif +#ifdef CONFIG_BUILD_KERNEL +# include +# include "romfs.h" +#endif + #include "esp32s3-devkit.h" +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_BUILD_KERNEL +# define SECTORSIZE 512 +# define NSECTORS(b) (((b) + SECTORSIZE - 1) / SECTORSIZE) +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -189,6 +203,31 @@ int esp32s3_bringup(void) bool i2s_enable_rx; #endif +#ifdef CONFIG_BUILD_KERNEL + /* A kernel build has no built-in applications: the init process and every + * other user program is a separate ELF living in the boot ROMFS image. + * Publish it as a RAM disk here so that nx_start_application() can mount + * it at CONFIG_INIT_MOUNT_TARGET and exec CONFIG_INIT_FILEPATH from it. + * An unpopulated image means romfs_boot.c was never generated -- see + * apps/tools/mkromfsimg.sh -- so there is nothing to register. + */ + + if (NSECTORS(romfs_img_len) > 1) + { + ret = romdisk_register(0, romfs_img, NSECTORS(romfs_img_len), + SECTORSIZE); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to register boot ROMFS: %d\n", ret); + } + } + else + { + syslog(LOG_ERR, "ERROR: Boot ROMFS image is empty; the init process " + "cannot be started\n"); + } +#endif + #if defined(CONFIG_ESP32S3_SPIRAM) && \ defined(CONFIG_ESP32S3_SPIRAM_BANKSWITCH_ENABLE) ret = esp_himem_init(); diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs.h b/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs.h new file mode 100644 index 0000000000000..658fad3143862 --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-devkit/src/romfs.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __BOARDS_XTENSA_ESP32S3_ESP32S3_DEVKIT_SRC_ROMFS_H +#define __BOARDS_XTENSA_ESP32S3_ESP32S3_DEVKIT_SRC_ROMFS_H + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* The boot ROMFS image holding the user-space programs of a kernel build. + * The real image is generated from apps/bin by apps/tools/mkromfsimg.sh into + * romfs_boot.c; romfs_stub.c provides a weak, empty placeholder so that the + * kernel still links before that step has been run. + */ + +extern const unsigned char romfs_img[]; +extern const unsigned int romfs_img_len; + +#endif /* __BOARDS_XTENSA_ESP32S3_ESP32S3_DEVKIT_SRC_ROMFS_H */ diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.c b/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.c new file mode 100644 index 0000000000000..f01bd6f1c383d --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.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 + +#ifndef HAVE_ROMFS_BOOT + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* Placeholder for the boot ROMFS image of a kernel build. It lets the + * kernel link before apps/tools/mkromfsimg.sh has generated the real + * romfs_boot.c. A kernel built against this placeholder has no user-space + * programs and so cannot start its init process; esp32s3_bringup() says so + * on the console. + * + * HAVE_ROMFS_BOOT is defined by src/Make.defs once the generated image is in + * place, and this file then contributes nothing. + */ + +const unsigned char aligned_data(4) romfs_img[] = +{ + 0x00 +}; + +const unsigned int romfs_img_len = 1; + +#endif /* !HAVE_ROMFS_BOOT */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ From a7a86ce8f0eae859ccc01a107aab8c070dc1496e Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 15:26:20 +0200 Subject: [PATCH 08/22] xtensa/esp32s3: Reach a page pool page through a scratch mapping. The page pool is carved out of the PSRAM that user processes run from, and the external memory permissions are indexed by physical address, so a permanent kernel window onto the pool is a window onto every process, which no permission setting can close. Stop mapping the pool. The kernel reaches a pool page through a small scratch region instead, mapped for one operation and invalidated afterwards. esp32s3_pgmap() takes a slot, esp32s3_pgunmap() releases it, and ARCH_KMAP_VBASE and ARCH_KMAP_NPAGES describe the region. Two slots are enough, because the deepest user is up_addrenv_fork(), which holds a source and a destination page at once. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/Kconfig | 42 +++- arch/xtensa/src/esp32s3/esp32s3_addrenv.c | 41 ++-- arch/xtensa/src/esp32s3/esp32s3_addrenv.h | 166 ++++++++++---- .../src/esp32s3/esp32s3_addrenv_utils.c | 208 +++++++++++++++++- arch/xtensa/src/esp32s3/esp32s3_mmu.c | 136 ++++++++++++ arch/xtensa/src/esp32s3/esp32s3_mmu.h | 86 ++++++++ arch/xtensa/src/esp32s3/esp32s3_pgalloc.c | 194 ++++++++++++++-- .../common/scripts/esp32s3_sections.ld | 20 +- .../configs/kernel_oct/defconfig | 11 +- .../esp32s3/esp32s3-devkit/scripts/gnu-elf.ld | 9 +- 10 files changed, 819 insertions(+), 94 deletions(-) diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index 62d580abaeabf..02ca3b6e62297 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -343,6 +343,44 @@ config ESP32S3_RUN_IRAM This loads all of NuttX inside IRAM. Used to test somewhat small images that can fit entirely in IRAM. +config ESP32S3_PGPOOL_SCRATCH + bool + default y + depends on BUILD_KERNEL && ARCH_ADDRENV && MM_PGALLOC + select ARCH_KVMA_MAPPING + ---help--- + The page pool is deliberately not kept mapped into the kernel + address space: it is carved out of the PSRAM the user processes + themselves run from, and the external memory permissions are indexed + by physical address, so a kernel window onto the pool is a window + onto every process's memory that no permission setting can close. + + The kernel reaches a pool page through a small scratch region + instead, mapped for the duration of one operation and invalidated + afterwards. ARCH_KMAP_VBASE and ARCH_KMAP_NPAGES describe it. + +if ESP32S3_PGPOOL_SCRATCH + +config ESP32S3_PGPOOL_PBASE + hex "Page pool physical address" + default 0x350000 + ---help--- + The physical base address of the page pool. For the cache MMU that + is a zero-based offset into the PSRAM device, not a CPU address. + + This replaces ARCH_PGPOOL_PBASE, which only exists when the pool is + statically mapped. The pool is still described physically because + mm_pgalloc() hands out physical page addresses; what goes away is + the permanent virtual mapping, not the pool itself. + +config ESP32S3_PGPOOL_SIZE + int "Page pool size (bytes)" + default 4194304 + ---help--- + The size of the page pool in bytes. Decimal, not hex. + +endif # ESP32S3_PGPOOL_SCRATCH + menu "ESP32-S3 Peripheral Selection" source "arch/xtensa/src/common/espressif/Kconfig" @@ -923,12 +961,12 @@ config ESP32S3_WCL bool "World Controller" default n select ARCH_USE_MPU - select XTENSA_HAVE_GENERAL_EXCEPTION_HOOKS if BUILD_PROTECTED + select XTENSA_HAVE_GENERAL_EXCEPTION_HOOKS if !BUILD_FLAT config ESP32S3_PAGEFAULT bool "Recoverable PMS permission faults" default n - depends on BUILD_PROTECTED + depends on !BUILD_FLAT ---help--- Route the precise Load/Store/InstrFetch Prohibited exceptions raised by PMS (memory-protection) permission violations through a diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.c b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c index ccf665917d3e9..61b62c9353f46 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_addrenv.c +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c @@ -317,9 +317,16 @@ ssize_t up_addrenv_heapsize(const arch_addrenv_t *addrenv) * is no page-table-base register to load; instead the shared user * cache-MMU windows (.text on the instruction bus, .data/.bss and heap on * the data bus) are reprogrammed to point at this environment's PSRAM - * pages. Only one user environment can be resident at a time, so - * isolation between groups is provided by this remap: while a group runs, - * only its own pages are visible in the windows. + * pages. Only one user environment can be resident at a time, so while a + * group runs only its own pages are visible *in the windows*: the entries + * it uses are pointed at its pages and the rest are invalidated. + * + * That is not by itself isolation between groups. Every user page comes + * from the one page pool, which the kernel keeps permanently mapped at + * CONFIG_ARCH_PGPOOL_VBASE, and the external-memory permissions are + * indexed by physical address, so they cannot deny the unprivileged world + * that window without also denying it its own pages. Closing that is a + * separate piece of work on the kernel's side of the map. * * The remap is skipped when 'addrenv' is already the resident environment * (thread<->thread within a group, ISRs, syscalls), which pays nothing. @@ -356,17 +363,6 @@ int up_addrenv_select(const arch_addrenv_t *addrenv) cache_state = esp32s3_dcache_suspend(false); - /* TODO(Unit F hardening): only the pages this group actually uses are - * remapped below. Window entries beyond ntext/ndata/nheap still point at - * the previously-resident group's pages, so a buggy or malicious task that - * touches its window above its own allocation could reach stale mappings. - * A well-behaved task never does, and the guard-page/SIGSEGV abort - * (CONFIG_ESP32S3_PAGEFAULT_ABORT) is the backstop, but full isolation - * needs the unused window entries invalidated here. Deferred to on-target - * bring-up because invalidating cache-MMU entries has documented sharp - * edges (an invalid in-window entry reads 0 silently, it does not fault). - */ - /* Point the instruction-bus (.text) window at this group's pages */ for (i = 0; i < addrenv->ntext; i++) @@ -394,6 +390,23 @@ int up_addrenv_select(const arch_addrenv_t *addrenv) addrenv->heappages[i], 1); } + /* Take away what this group does not use. Only as many entries as the + * incoming group has pages were rewritten above; the rest of each window + * would otherwise still point at the pages of whoever was resident before, + * which a task that ran off the end of its own allocation could read. + * + * A group's page count only ever grows (the heap window, through + * pgalloc()), so this cannot invalidate an entry that is about to be + * needed again without a select in between. + */ + + esp32s3_mmu_unmap(ESP32S3_TEXT_VBASE + addrenv->ntext * MM_PGSIZE, + CONFIG_ARCH_TEXT_NPAGES - addrenv->ntext); + esp32s3_mmu_unmap(ESP32S3_DATA_VBASE + addrenv->ndata * MM_PGSIZE, + CONFIG_ARCH_DATA_NPAGES - addrenv->ndata); + esp32s3_mmu_unmap(ESP32S3_HEAP_VBASE + addrenv->nheap * MM_PGSIZE, + CONFIG_ARCH_HEAP_NPAGES - addrenv->nheap); + /* Drop stale instruction lines from the previous mapping and resume */ esp32s3_icache_invalidate_all(); diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.h b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h index 5de0eab46494e..ca77ce5f702e8 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_addrenv.h +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h @@ -42,10 +42,56 @@ * Pre-processor Definitions ****************************************************************************/ -#ifndef CONFIG_ARCH_PGPOOL_MAPPING -# error "ESP32-S3 address environments need CONFIG_ARCH_PGPOOL_MAPPING" +/* The page pool must NOT be statically mapped into the kernel. It is carved + * out of the same PSRAM the user processes run from, and the external memory + * permissions (APB_CTRL_SRAM_ACEn_*) are indexed by physical address, so a + * permanent kernel window onto the pool is a window onto every process's + * memory that no permission setting can close: a process's own pages are + * pool pages. This was measured, not assumed -- a user task read another + * process's .bss through it. The kernel uses the scratch region below + * instead. + */ + +#ifdef CONFIG_ARCH_PGPOOL_MAPPING +# error "ESP32-S3 must not map the page pool; see esp32s3_pgmap()" +#endif + +#ifndef CONFIG_ARCH_KMAP_VBASE +# error "ESP32-S3 address environments need CONFIG_ARCH_KMAP_VBASE" +#endif + +#if CONFIG_ARCH_KMAP_NPAGES < 2 +# error "CONFIG_ARCH_KMAP_NPAGES must be at least 2 (fork() copies " \ + "a source and a destination page at once)" +#endif + +/* CONFIG_MM_KMAP cannot work here. kmm_map()'s single-page path is + * up_addrenv_page_vaddr(), which asks for a kernel address that stays valid + * after the call returns -- exactly what a scratch mapping cannot promise. + */ + +#ifdef CONFIG_MM_KMAP +# error "CONFIG_MM_KMAP needs a permanently mapped page pool" #endif +/* The kernel's scratch region. ARCH_KMAP_VEND is only defined by + * when CONFIG_MM_KMAP is set, which it is not. + */ + +#define ESP32S3_KMAP_VBASE (CONFIG_ARCH_KMAP_VBASE) +#define ESP32S3_KMAP_NPAGES (CONFIG_ARCH_KMAP_NPAGES) +#define ESP32S3_KMAP_VEND (CONFIG_ARCH_KMAP_VBASE + \ + CONFIG_ARCH_KMAP_NPAGES * MM_PGSIZE) + +/* The page pool, described physically. mm_pgalloc() hands out physical page + * addresses; only the virtual mapping went away, not the pool. + */ + +#define ESP32S3_PGPOOL_PBASE (CONFIG_ESP32S3_PGPOOL_PBASE) +#define ESP32S3_PGPOOL_SIZE (CONFIG_ESP32S3_PGPOOL_SIZE) +#define ESP32S3_PGPOOL_PEND (CONFIG_ESP32S3_PGPOOL_PBASE + \ + CONFIG_ESP32S3_PGPOOL_SIZE) + /* The user address space is split across two disjoint cache-MMU windows: * .text lives in the instruction-bus window, .data/.bss and the heap in the * data-bus window. Each window is described by its base and page count. @@ -66,45 +112,16 @@ ****************************************************************************/ /**************************************************************************** - * Name: esp32s3_pgvaddr + * Name: esp32s3_pgpool_page * * Description: - * Get the kernel-addressable virtual address of a page-pool physical - * address. The page pool (PSRAM) is permanently mapped into the kernel - * (WORLD0) address space, so a page allocated by mm_pgalloc() can be - * reached directly through this fixed offset. Returns 0 if the physical - * address is not inside the page pool. + * Return true if paddr is a page-pool physical address. * ****************************************************************************/ -static inline uintptr_t esp32s3_pgvaddr(uintptr_t paddr) +static inline bool esp32s3_pgpool_page(uintptr_t paddr) { - if (paddr >= CONFIG_ARCH_PGPOOL_PBASE && paddr < CONFIG_ARCH_PGPOOL_PEND) - { - return paddr - CONFIG_ARCH_PGPOOL_PBASE + CONFIG_ARCH_PGPOOL_VBASE; - } - - return 0; -} - -/**************************************************************************** - * Name: esp32s3_pgpaddr - * - * Description: - * Inverse of esp32s3_pgvaddr(): translate a kernel page-pool virtual - * address back to its physical address. Returns 0 if the virtual address - * is not inside the mapped page pool. - * - ****************************************************************************/ - -static inline uintptr_t esp32s3_pgpaddr(uintptr_t vaddr) -{ - if (vaddr >= CONFIG_ARCH_PGPOOL_VBASE && vaddr < CONFIG_ARCH_PGPOOL_VEND) - { - return vaddr - CONFIG_ARCH_PGPOOL_VBASE + CONFIG_ARCH_PGPOOL_PBASE; - } - - return 0; + return (paddr >= ESP32S3_PGPOOL_PBASE && paddr < ESP32S3_PGPOOL_PEND); } /**************************************************************************** @@ -123,27 +140,86 @@ static inline bool esp32s3_uservaddr(uintptr_t vaddr) (vaddr >= ESP32S3_HEAP_VBASE && vaddr < ESP32S3_HEAP_VEND)); } +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_pgmap + * + * Description: + * Map a page-pool physical page into the kernel's scratch region and + * return the virtual address it can be reached at. + * + * This replaces the arithmetic esp32s3_pgvaddr() the port used while the + * whole pool was mapped, and unlike it the result has a *lifetime*: it is + * valid until the matching esp32s3_pgunmap(), and not one instruction + * longer. It must not be stored anywhere that outlives the operation. + * + * The caller must hold sched_lock() across the whole map/use/unmap + * sequence. A scratch address is ordinary external memory as far as the + * permission control is concerned, so an unprivileged task that ran while + * the mapping was live could read the page through it -- which is the leak + * this whole arrangement exists to close. Interrupts do not need to be + * disabled: no interrupt handler reaches these paths. + * + * Input Parameters: + * paddr - Physical (page pool) address of the page, page-aligned. + * + * Returned Value: + * The kernel virtual address of the page, or 0 if 'paddr' is not a pool + * page or no scratch slot is free. + * + ****************************************************************************/ + +uintptr_t esp32s3_pgmap(uintptr_t paddr); + +/**************************************************************************** + * Name: esp32s3_pgunmap + * + * Description: + * Release a mapping made by esp32s3_pgmap(), writing back anything written + * through it and leaving the scratch entry invalid. + * + * Input Parameters: + * vaddr - The address esp32s3_pgmap() returned. + * + ****************************************************************************/ + +void esp32s3_pgunmap(uintptr_t vaddr); + /**************************************************************************** * Name: esp32s3_pgwipe * * Description: - * Zero a page-pool physical page through its kernel virtual mapping. + * Zero a page-pool physical page, mapping it into the kernel's scratch + * region for as long as that takes. + * + * Input Parameters: + * paddr - Physical (page pool) address of the page. * ****************************************************************************/ -static inline void esp32s3_pgwipe(uintptr_t paddr) -{ - uintptr_t vaddr = esp32s3_pgvaddr(paddr); - if (vaddr) - { - memset((void *)vaddr, 0, MM_PGSIZE); - } -} +void esp32s3_pgwipe(uintptr_t paddr); /**************************************************************************** - * Public Function Prototypes + * Name: esp32s3_pgpool_unmap + * + * Description: + * Tear down the boot-time cache-MMU mapping of the page pool, leaving the + * pool reachable only a page at a time through esp32s3_pgmap(). Called + * once, from up_allocate_pgheap(), after its consistency checks -- which + * have to run first, since they ask the cache MMU where the pool actually + * is rather than deriving it. + * + * Input Parameters: + * vbase - Virtual base the boot-time mapping put the pool at. + * size - Size of the pool in bytes. + * ****************************************************************************/ +void esp32s3_pgpool_unmap(uintptr_t vbase, size_t size); + /**************************************************************************** * Name: esp32s3_addrenv_mapnew * diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c b/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c index c44fc4732e85a..08a2a2554217d 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv_utils.c @@ -27,21 +27,177 @@ #include #include +#include +#include +#include #include #include +#include #include #include #include #include "esp32s3_addrenv.h" +#include "esp32s3_mmu.h" #ifdef CONFIG_ARCH_ADDRENV +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* The kernel's scratch region: the only way it can reach a page-pool page, + * one page at a time and only while it is working on it. A slot holds the + * physical page it currently maps, or 0 when it is free. + * + * There is no lock here on purpose. Callers hold sched_lock() for the whole + * map/use/unmap sequence -- which they must anyway, so that a live mapping + * cannot be observed by an unprivileged task -- and that also makes this + * table single-threaded. No interrupt handler reaches these paths. + */ + +static uintptr_t g_scratch[ESP32S3_KMAP_NPAGES]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: scratch_slot + * + * Description: + * Return the scratch slot index a scratch virtual address belongs to, or + * -1 if the address is not in the scratch region. + * + ****************************************************************************/ + +static int scratch_slot(uintptr_t vaddr) +{ + if (vaddr < ESP32S3_KMAP_VBASE || vaddr >= ESP32S3_KMAP_VEND) + { + return -1; + } + + return (int)((vaddr - ESP32S3_KMAP_VBASE) >> MM_PGSHIFT); +} + /**************************************************************************** * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: esp32s3_pgmap + ****************************************************************************/ + +uintptr_t esp32s3_pgmap(uintptr_t paddr) +{ + uintptr_t vaddr; + int i; + + DEBUGASSERT(!up_interrupt_context()); + DEBUGASSERT(MM_ISALIGNED(paddr)); + + if (!esp32s3_pgpool_page(paddr)) + { + return 0; + } + + for (i = 0; i < ESP32S3_KMAP_NPAGES; i++) + { + if (g_scratch[i] == 0) + { + break; + } + } + + if (i >= ESP32S3_KMAP_NPAGES) + { + return 0; + } + + g_scratch[i] = paddr; + vaddr = ESP32S3_KMAP_VBASE + i * MM_PGSIZE; + + esp32s3_mmu_scratch_map(vaddr, paddr); + + return vaddr; +} + +/**************************************************************************** + * Name: esp32s3_pgunmap + ****************************************************************************/ + +void esp32s3_pgunmap(uintptr_t vaddr) +{ + int i = scratch_slot(vaddr); + + DEBUGASSERT(i >= 0 && g_scratch[i] != 0); + + esp32s3_mmu_scratch_unmap(ESP32S3_KMAP_VBASE + i * MM_PGSIZE); + + g_scratch[i] = 0; +} + +/**************************************************************************** + * Name: esp32s3_pgwipe + ****************************************************************************/ + +void esp32s3_pgwipe(uintptr_t paddr) +{ + uintptr_t vaddr; + + /* Hold off the scheduler for the whole sequence. Not for mutual exclusion + * against another wipe -- there is none to worry about here -- but because + * a scratch address is reachable by the unprivileged world like any other + * external memory address, so no user task may run while a pool page is + * parked at one. + */ + + sched_lock(); + + vaddr = esp32s3_pgmap(paddr); + if (vaddr == 0) + { + /* Not recoverable, and much too dangerous to let pass: an unwiped page + * carries its previous tenant's data into a new process. This is + * exactly how the CONFIG_ARCH_PGPOOL_PBASE drift stayed hidden. + */ + + _err("ERROR: no scratch mapping for page %08" PRIxPTR "\n", paddr); + PANIC(); + } + + memset((void *)vaddr, 0, MM_PGSIZE); + esp32s3_pgunmap(vaddr); + + sched_unlock(); +} + +/**************************************************************************** + * Name: esp32s3_pgpool_unmap + ****************************************************************************/ + +void esp32s3_pgpool_unmap(uintptr_t vbase, size_t size) +{ + uint32_t cache_state; + + /* Take away the boot-time mapping of the pool in one go. esp32s3_spiram.c + * mapped the whole PSRAM device; only the pool's share of that window is + * withdrawn, because the rest maps no page a process will ever own and is + * the aperture a kernel-side PSRAM allocation (a loaded library's text + * heap, say) would have to come from. + * + * Write back before invalidating the entries: this runs after the PSRAM + * memory test, which leaves the window's lines dirty. + */ + + cache_state = esp32s3_dcache_suspend(true); + esp32s3_mmu_unmap(vbase, size / MM_PGSIZE); + esp32s3_icache_invalidate_all(); + esp32s3_dcache_resume(cache_state); +} + /**************************************************************************** * Name: up_addrenv_find_page * @@ -104,14 +260,22 @@ uintptr_t up_addrenv_find_page(arch_addrenv_t *addrenv, uintptr_t vaddr) * * Description: * Get the kernel virtual address of a physical page allocated for an - * address environment. Since the PSRAM page pool is permanently mapped - * into the kernel, this is a fixed offset translation. + * address environment. + * + * The ESP32-S3 cannot answer this. The contract is a kernel address that + * stays valid after the call returns, and the page pool is deliberately + * not mapped: a pool page is reachable only for the duration of an + * esp32s3_pgmap()/esp32s3_pgunmap() pair. Returning an address that is + * about to stop meaning anything would be worse than refusing, so this + * fails, and CONFIG_MM_KMAP -- whose single-page path is built on this + * function -- is rejected at compile time in esp32s3_addrenv.h. * ****************************************************************************/ uintptr_t up_addrenv_page_vaddr(uintptr_t page) { - return esp32s3_pgvaddr(page); + UNUSED(page); + return 0; } /**************************************************************************** @@ -148,11 +312,35 @@ void up_addrenv_page_wipe(uintptr_t page) * Map a physical page-pool address to the kernel virtual address that * currently maps it. * + * Which is now literally what this does: only a page held in a scratch + * slot is mapped at all, so the answer comes from the slot table rather + * than from arithmetic over a window that no longer exists. A page nobody + * is working on has no kernel virtual address, and 0 says so. + * ****************************************************************************/ void *up_addrenv_pa_to_va(uintptr_t pa) { - return (void *)esp32s3_pgvaddr(pa); + uintptr_t page = MM_PGALIGNDOWN(pa); + int i; + + /* A free slot holds 0, so page 0 could otherwise match one of them */ + + if (!esp32s3_pgpool_page(page)) + { + return NULL; + } + + for (i = 0; i < ESP32S3_KMAP_NPAGES; i++) + { + if (g_scratch[i] == page) + { + return (void *)(ESP32S3_KMAP_VBASE + i * MM_PGSIZE + + (pa & MM_PGMASK)); + } + } + + return NULL; } /**************************************************************************** @@ -160,12 +348,22 @@ void *up_addrenv_pa_to_va(uintptr_t pa) * * Description: * Map a kernel page-pool virtual address back to its physical address. + * The inverse of the above, and just as narrow: scratch addresses are the + * only kernel virtual addresses that name a pool page. * ****************************************************************************/ uintptr_t up_addrenv_va_to_pa(void *va) { - return esp32s3_pgpaddr((uintptr_t)va); + uintptr_t vaddr = (uintptr_t)va; + int i = scratch_slot(vaddr); + + if (i < 0 || g_scratch[i] == 0) + { + return 0; + } + + return g_scratch[i] + (vaddr & MM_PGMASK); } #endif /* CONFIG_ARCH_ADDRENV */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_mmu.c b/arch/xtensa/src/esp32s3/esp32s3_mmu.c index cf54eb0c55de1..9e801d28249c4 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_mmu.c +++ b/arch/xtensa/src/esp32s3/esp32s3_mmu.c @@ -27,11 +27,13 @@ #include #include +#include #include #include "xtensa.h" #include "esp_attr.h" +#include "soc/ext_mem_defs.h" #include "soc/extmem_reg.h" #include "esp32s3_mmu.h" @@ -51,6 +53,35 @@ extern int cache_dbus_mmu_set(uint32_t ext_ram, uint32_t vaddr, extern int cache_ibus_mmu_set(uint32_t ext_ram, uint32_t vaddr, uint32_t paddr, uint32_t psize, uint32_t num, uint32_t fixed); +extern int cache_invalidate_addr(uint32_t addr, uint32_t size); +extern int cache_writeback_addr(uint32_t addr, uint32_t size); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: dcache_unshut + * + * Description: + * Re-open the data cache bus that cache_suspend_dcache() shut. Taken from + * esp_spiram_map(): the range cache maintenance that follows a remap has + * to run with the bus open but the cache still suspended, so this is the + * half of esp32s3_dcache_resume() that comes before cache_resume_dcache(). + * + ****************************************************************************/ + +static void IRAM_ATTR dcache_unshut(void) +{ + uint32_t regval; + + regval = getreg32(EXTMEM_DCACHE_CTRL1_REG); + regval &= ~EXTMEM_DCACHE_SHUT_CORE0_BUS; +#ifdef CONFIG_SMP + regval &= ~EXTMEM_DCACHE_SHUT_CORE1_BUS; +#endif + putreg32(regval, EXTMEM_DCACHE_CTRL1_REG); +} /**************************************************************************** * Public Functions @@ -130,3 +161,108 @@ int IRAM_ATTR esp32s3_mmu_map_ibus(uint32_t ext_ram, uint32_t vaddr, { return cache_ibus_mmu_set(ext_ram, vaddr, paddr, 64, (int)npages, 0); } + +/**************************************************************************** + * Name: esp32s3_mmu_paddr + ****************************************************************************/ + +bool esp32s3_mmu_paddr(uint32_t vaddr, uint32_t *paddr) +{ + uint32_t entry = FLASH_MMU_TABLE[MMU_ENTRY_OF(vaddr)]; + + if ((entry & MMU_TABLE_INVALID_VAL) != 0) + { + return false; + } + + *paddr = (entry & MMU_ADDRESS_MASK) * MMU_PAGE_SIZE + + (vaddr & (MMU_PAGE_SIZE - 1)); + return true; +} + +/**************************************************************************** + * Name: esp32s3_mmu_unmap + ****************************************************************************/ + +void IRAM_ATTR esp32s3_mmu_unmap(uint32_t vaddr, uint32_t npages) +{ + uint32_t entry = MMU_ENTRY_OF(vaddr); + uint32_t i; + + /* One table, one entry per 64 KB, shared by the instruction and the data + * bus: the IBUS and DBUS linear addresses are asserted equal in + * ext_mem_defs.h, so a single write covers both views of the page. + */ + + for (i = 0; i < npages && entry + i < SOC_MMU_ENTRY_NUM; i++) + { + FLASH_MMU_TABLE[entry + i] = MMU_TABLE_INVALID_VAL; + } +} + +/**************************************************************************** + * Name: esp32s3_mmu_scratch_map + ****************************************************************************/ + +void esp32s3_mmu_scratch_map(uint32_t vaddr, uint32_t paddr) +{ + irqstate_t flags; + uint32_t cache_state; + + flags = enter_critical_section(); + + /* Suspend the cache, point the entry at the page, then invalidate just + * this page's lines rather than the whole cache. The difference matters: + * esp32s3_dcache_suspend() invalidates everything, which would discard the + * resident process's dirty lines, and its write-back variant would put a + * whole-cache write-back inside this critical section. Neither is + * acceptable at the rate this is called -- once per page of every process + * created. + */ + + cache_state = cache_suspend_dcache(); + + esp32s3_mmu_map_dbus(SOC_MMU_ACCESS_SPIRAM, vaddr, paddr, 1); + + dcache_unshut(); + cache_invalidate_addr(vaddr, MMU_PAGE_SIZE); + + cache_resume_dcache(cache_state); + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: esp32s3_mmu_scratch_unmap + ****************************************************************************/ + +void esp32s3_mmu_scratch_unmap(uint32_t vaddr) +{ + irqstate_t flags; + uint32_t cache_state; + + /* Push whatever was written through this window out to the page it maps, + * while it still maps it. The lines are tagged by virtual address, so + * repointing the entry with them still dirty would write them back to + * whichever page the scratch slot is used for next. + * + * This runs with interrupts enabled on purpose -- it is a write-back of up + * to a page, and the caller holds sched_lock(), so nothing else can reach + * the slot in the meantime. + */ + + cache_writeback_addr(vaddr, MMU_PAGE_SIZE); + + flags = enter_critical_section(); + + cache_state = cache_suspend_dcache(); + + esp32s3_mmu_unmap(vaddr, 1); + + dcache_unshut(); + cache_invalidate_addr(vaddr, MMU_PAGE_SIZE); + + cache_resume_dcache(cache_state); + + leave_critical_section(flags); +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_mmu.h b/arch/xtensa/src/esp32s3/esp32s3_mmu.h index 6553c9e77cf1e..5dc2f1baf65d5 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_mmu.h +++ b/arch/xtensa/src/esp32s3/esp32s3_mmu.h @@ -40,6 +40,15 @@ #define MMU_FLASH_MASK (~(MMU_PAGE_SIZE - 1)) +/* The cache MMU entry a virtual address resolves to. There is one table for + * both buses -- ext_mem_defs.h asserts that the IRAM0 and DRAM0 linear + * addresses are equal -- so an instruction-bus and a data-bus address 64 MB + * apart share an entry, and comparing entries is the only way to tell + * whether two windows collide. + */ + +#define MMU_ENTRY_OF(vaddr) (((vaddr) & SOC_MMU_VADDR_MASK) >> 16) + /**************************************************************************** * Public Function Prototypes ****************************************************************************/ @@ -153,4 +162,81 @@ int esp32s3_mmu_map_dbus(uint32_t ext_ram, uint32_t vaddr, uint32_t paddr, int esp32s3_mmu_map_ibus(uint32_t ext_ram, uint32_t vaddr, uint32_t paddr, uint32_t npages); +/**************************************************************************** + * Name: esp32s3_mmu_paddr + * + * Description: + * Ask the cache MMU what a virtual address currently resolves to. This is + * ground truth rather than arithmetic, which matters because where the + * kernel's PSRAM window lands is decided at run time (see + * up_allocate_pgheap()). + * + * Input Parameters: + * vaddr - A virtual address inside one of the external memory windows. + * paddr - Receives the physical/flash address it maps to. + * + * Returned Value: + * True if the entry is valid and *paddr was written; false if the entry + * maps nothing. + * + ****************************************************************************/ + +bool esp32s3_mmu_paddr(uint32_t vaddr, uint32_t *paddr); + +/**************************************************************************** + * Name: esp32s3_mmu_unmap + * + * Description: + * Invalidate a range of 64 KB cache MMU entries, so that the virtual + * addresses they covered map nothing at all. Note that an access to an + * invalidated entry is not necessarily a fault: unless the cache reject + * monitors are armed it reads back as zero and swallows writes. The + * caller is responsible for suspending/resuming the data cache around the + * change and for invalidating the instruction cache afterwards -- entries + * being taken away is exactly when stale instruction lines are left + * behind. + * + * Input Parameters: + * vaddr - 64 KB-aligned virtual base address. + * npages - Number of 64 KB pages to invalidate. + * + ****************************************************************************/ + +void esp32s3_mmu_unmap(uint32_t vaddr, uint32_t npages); + +/**************************************************************************** + * Name: esp32s3_mmu_scratch_map + * + * Description: + * Point one 64 KB data-bus entry at a PSRAM page and make it usable, doing + * the cache maintenance itself and confining it to that page. This is the + * kernel's way of reaching a page-pool page, which is otherwise not mapped + * at all (see esp32s3_pgmap()). + * + * The caller must already hold the scratch slot -- these entries are + * reachable by the unprivileged world like any other external memory + * address, so a mapping must not outlive the operation that needs it. + * + * Input Parameters: + * vaddr - 64 KB-aligned scratch virtual address. + * paddr - 64 KB-aligned PSRAM physical address. + * + ****************************************************************************/ + +void esp32s3_mmu_scratch_map(uint32_t vaddr, uint32_t paddr); + +/**************************************************************************** + * Name: esp32s3_mmu_scratch_unmap + * + * Description: + * Take a scratch mapping away again: write back what was written through + * it, invalidate the entry, and drop the page's cache lines. + * + * Input Parameters: + * vaddr - The scratch virtual address returned when it was mapped. + * + ****************************************************************************/ + +void esp32s3_mmu_scratch_unmap(uint32_t vaddr); + #endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_MMU_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c b/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c index 6b3e5d0f1d81f..381c7050e200b 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pgalloc.c @@ -29,15 +29,18 @@ #include #include #include +#include #include #include +#include #include #include #include "sched/sched.h" #include "esp32s3_addrenv.h" +#include "esp32s3_mmu.h" #ifdef CONFIG_ESP32S3_SPIRAM # include "esp32s3_spiram.h" @@ -45,6 +48,129 @@ #ifdef CONFIG_MM_PGALLOC +#if defined(CONFIG_ESP32S3_SPIRAM) && defined(CONFIG_ARCH_ADDRENV) + +/* The page pool and a PSRAM kernel heap cannot coexist. xtensa_add_region() + * hands the *whole* allocable PSRAM window to the kernel heap, which + * necessarily includes the pool -- and a kernel heap that contains pool + * pages is a permanently mapped view of every process's memory, arriving + * from the other side of the same problem esp32s3_pgmap() exists to solve. + * + * This is not hypothetical: up_textheap_memalign() falls back to the kernel + * heap and derives an instruction-bus alias for anything it finds outside + * internal RAM, which is the path a dlopen()ed shared library would take. + * Kernel-side PSRAM has to come from outside the pool. + */ + +#ifdef CONFIG_ESP32S3_SPIRAM_COMMON_HEAP +# error "the page pool cannot be shared with a PSRAM kernel heap" +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct window_s +{ + FAR const char *name; + uintptr_t vbase; + uint32_t npages; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Every cache-MMU window this configuration uses, checked against each other + * and against the kernel's PSRAM window at boot. The scratch region is one + * of them: it is a real window now, and the heap window growing into it + * would be as silent as every other mapping mistake in this port. + */ + +static const struct window_s g_windows[] = +{ + { + .name = "text", + .vbase = ESP32S3_TEXT_VBASE, + .npages = CONFIG_ARCH_TEXT_NPAGES + }, + { + .name = "data", + .vbase = ESP32S3_DATA_VBASE, + .npages = CONFIG_ARCH_DATA_NPAGES + }, + { + .name = "heap", + .vbase = ESP32S3_HEAP_VBASE, + .npages = CONFIG_ARCH_HEAP_NPAGES + }, + { + .name = "scratch", + .vbase = ESP32S3_KMAP_VBASE, + .npages = ESP32S3_KMAP_NPAGES + } +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: check_windows_clear + * + * Description: + * Panic if any two cache-MMU windows overlap, or if one of them overlaps + * the kernel's PSRAM window. They would otherwise do it silently: the + * instruction and the data bus share one entry per 64 KB, so two windows + * 64 MB apart are the same entries, and mapping one over another simply + * takes the first one's pages away. + * + * The comparison is by entry, since that is what actually collides, and it + * belongs at run time because the kernel PSRAM window starts wherever the + * flash mappings end and so moves as the image grows. + * + * Input Parameters: + * kfirst - First cache-MMU entry of the kernel's PSRAM window. + * klast - Last cache-MMU entry of the kernel's PSRAM window. + * + ****************************************************************************/ + +static void check_windows_clear(uint32_t kfirst, uint32_t klast) +{ + size_t i; + size_t j; + + for (i = 0; i < nitems(g_windows); i++) + { + uint32_t first = MMU_ENTRY_OF(g_windows[i].vbase); + uint32_t last = first + g_windows[i].npages - 1; + + if (first <= klast && last >= kfirst) + { + _err("ERROR: %s window (MMU entries %" PRIu32 "-%" PRIu32 ") " + "overlaps the kernel PSRAM window (entries %" PRIu32 "-%" + PRIu32 ")\n", g_windows[i].name, first, last, kfirst, klast); + PANIC(); + } + + for (j = 0; j < i; j++) + { + uint32_t ofirst = MMU_ENTRY_OF(g_windows[j].vbase); + uint32_t olast = ofirst + g_windows[j].npages - 1; + + if (first <= olast && last >= ofirst) + { + _err("ERROR: %s window (MMU entries %" PRIu32 "-%" PRIu32 ") " + "overlaps the %s window (entries %" PRIu32 "-%" PRIu32 + ")\n", g_windows[i].name, first, last, g_windows[j].name, + ofirst, olast); + PANIC(); + } + } + } +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -61,10 +187,14 @@ * On the ESP32-S3 the page pool is a slice of the external octal PSRAM. * The pool is described by its *physical* base -- for the cache MMU that * is the zero-based offset into the PSRAM device -- because mm_pgalloc() - * hands out physical page addresses. The whole pool is also permanently - * mapped into the kernel (WORLD0) data-bus window at - * CONFIG_ARCH_PGPOOL_VBASE so the kernel can reach any page it allocates - * (see esp32s3_pgvaddr()). + * hands out physical page addresses. + * + * It is deliberately not mapped into the kernel. esp32s3_spiram.c maps + * the whole PSRAM device at boot, and this function is where the pool's + * share of that mapping is withdrawn again, after the checks that need it. + * The kernel reaches a pool page one at a time through esp32s3_pgmap(); + * see esp32s3_addrenv.h for why a permanent window cannot be allowed to + * stand. * * Input Parameters: * heap_start - Receives the physical base address of the page pool. @@ -76,40 +206,62 @@ void up_allocate_pgheap(void **heap_start, size_t *heap_size) { DEBUGASSERT(heap_start && heap_size); -#ifdef CONFIG_ESP32S3_SPIRAM +#if defined(CONFIG_ESP32S3_SPIRAM) && defined(CONFIG_ARCH_ADDRENV) /* Where the kernel's PSRAM window lands is decided at run time: * esp32s3_spiram.c maps PSRAM immediately after the last cache-MMU entry * the flash mappings occupy, so it moves as the kernel image grows. The - * page pool is described to the OS by compile-time constants, so the two - * have to be checked against each other -- and loudly, because getting it - * wrong is otherwise silent: an unmapped cache window swallows writes and - * reads back as zero without faulting, so a misplaced pool would simply - * lose every page handed out of it. + * pool is described by a compile-time *physical* base, so ask the cache + * MMU where that physical page currently is rather than deriving it from + * a constant -- which is how CONFIG_ARCH_PGPOOL_PBASE went stale twice, + * and the second time by exactly one page, which left one unwiped page in + * every region of every new process. */ { uintptr_t ramstart = (uintptr_t)esp_spiram_allocable_vaddr_start(); uintptr_t ramend = (uintptr_t)esp_spiram_allocable_vaddr_end(); + uintptr_t poolvbase; + uint32_t rampbase; - _info("PSRAM window %08" PRIxPTR "-%08" PRIxPTR ", " - "page pool %08x-%08x\n", - ramstart, ramend, - CONFIG_ARCH_PGPOOL_VBASE, CONFIG_ARCH_PGPOOL_VEND); + if (!esp32s3_mmu_paddr(ramstart, &rampbase)) + { + _err("ERROR: PSRAM window base %08" PRIxPTR " maps nothing\n", + ramstart); + PANIC(); + } - if ((uintptr_t)CONFIG_ARCH_PGPOOL_VBASE < ramstart || - (uintptr_t)CONFIG_ARCH_PGPOOL_VEND > ramend) + if (ESP32S3_PGPOOL_PBASE < rampbase || + ESP32S3_PGPOOL_PEND > rampbase + (ramend - ramstart)) { _err("ERROR: page pool %08x-%08x is outside the mapped PSRAM " - "window %08" PRIxPTR "-%08" PRIxPTR "\n", - CONFIG_ARCH_PGPOOL_VBASE, CONFIG_ARCH_PGPOOL_VEND, - ramstart, ramend); + "%08" PRIx32 "-%08" PRIxPTR "\n", + ESP32S3_PGPOOL_PBASE, ESP32S3_PGPOOL_PEND, + rampbase, rampbase + (ramend - ramstart)); PANIC(); } + + poolvbase = ramstart + (ESP32S3_PGPOOL_PBASE - rampbase); + + _info("PSRAM window %08" PRIxPTR "-%08" PRIxPTR " (phys %08" PRIx32 + "), page pool phys %08x-%08x at %08" PRIxPTR "\n", + ramstart, ramend, rampbase, + ESP32S3_PGPOOL_PBASE, ESP32S3_PGPOOL_PEND, poolvbase); + + check_windows_clear(MMU_ENTRY_OF(ramstart), MMU_ENTRY_OF(ramend - 1)); + + /* Everything above has been established while the pool was still + * mapped, which is the only time it can be. Now take that mapping + * away: from here the kernel reaches a pool page only through + * esp32s3_pgmap(), and an unprivileged task reaches one only if it is + * its own. + */ + + esp32s3_pgpool_unmap(poolvbase, ESP32S3_PGPOOL_SIZE); } #endif - *heap_start = (void *)CONFIG_ARCH_PGPOOL_PBASE; - *heap_size = (size_t)CONFIG_ARCH_PGPOOL_SIZE; + *heap_start = (void *)ESP32S3_PGPOOL_PBASE; + *heap_size = (size_t)ESP32S3_PGPOOL_SIZE; } #ifdef CONFIG_BUILD_KERNEL diff --git a/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld b/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld index e0384d88f4200..84f48a4c93ede 100644 --- a/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld +++ b/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld @@ -342,7 +342,25 @@ SECTIONS _iram_text = ABSOLUTE(.); } >iram0_0_seg - /* Marks the end of IRAM code segment */ + #ifdef CONFIG_BUILD_KERNEL + /* The vector table the World Controller gives to WORLD1, the unprivileged + * world. It needs the 1 KB alignment a vector base requires, and it is + * kept in a region of its own so that the permission control can make it + * the only instruction memory a user task may fetch. + * + * It has to land in Internal SRAM1, past 0x40378000: the permission control + * splits SRAM0 into two 16 KB blocks and can say nothing finer, while SRAM1 + * is divided by split lines at 256-byte granularity. Following the IRAM + * code puts it there; esp32s3_isolation_permissions() checks that it did. + */ + + .world1.vectors : ALIGN(1024) + { + KEEP (*(.world1_vectors.text)); + } >iram0_0_seg AT>ROM +#endif + + /* Marks the end of IRAM code segment */ .iram0.text_end (NOLOAD) : { diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig index 0e494c08f5cc5..7482b72a44f7d 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig @@ -23,15 +23,13 @@ CONFIG_ARCH_HEAP_VBASE=0x3d200000 CONFIG_ARCH_INTERRUPTSTACK=2048 CONFIG_ARCH_IRQ_TO_NDX=y CONFIG_ARCH_KERNEL_STACKSIZE=8192 +CONFIG_ARCH_KMAP_NPAGES=2 +CONFIG_ARCH_KMAP_VBASE=0x3d400000 CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y CONFIG_ARCH_NUSER_INTERRUPTS=2 -CONFIG_ARCH_PGPOOL_MAPPING=y -CONFIG_ARCH_PGPOOL_PBASE=0x360000 -CONFIG_ARCH_PGPOOL_SIZE=4194304 -CONFIG_ARCH_PGPOOL_VBASE=0x3c400000 CONFIG_ARCH_STACKDUMP=y CONFIG_ARCH_TEXT_NPAGES=8 -CONFIG_ARCH_TEXT_VBASE=0x42800000 +CONFIG_ARCH_TEXT_VBASE=0x42c00000 CONFIG_ARCH_USE_MMU=y CONFIG_ARCH_XTENSA=y CONFIG_BINFMT_ELF_EXECUTABLE=y @@ -51,7 +49,10 @@ CONFIG_DEFAULT_TASK_STACKSIZE=8192 CONFIG_ELF=y CONFIG_ESP32S3_FLASH_MODE_OCT=y CONFIG_ESP32S3_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESP32S3_PAGEFAULT=y CONFIG_ESP32S3_SPIFLASH=y +CONFIG_ESP32S3_PGPOOL_PBASE=0x350000 +CONFIG_ESP32S3_PGPOOL_SIZE=4194304 CONFIG_ESP32S3_SPIRAM=y CONFIG_ESP32S3_SPIRAM_MODE_OCT=y CONFIG_ESP32S3_UART0=y diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld b/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld index b6eb89085abf6..68d481c7c571d 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld +++ b/boards/xtensa/esp32s3/esp32s3-devkit/scripts/gnu-elf.ld @@ -32,6 +32,13 @@ * heap bookkeeping and the signal trampoline pointer). The window * is a slot of the data-bus range (0x3C000000-0x3E000000). * + * Both windows have to sit above the kernel's own PSRAM window, which the + * two buses share entry for entry: 0x42C00000 and 0x3C000000+0xC00000 are + * the same cache-MMU entry, so a user window that overlapped it would take + * away the kernel's view of the pages underneath. The kernel window starts + * wherever the flash mappings end, so it moves as the image grows; + * up_allocate_pgheap() checks the two against each other at boot. + * * .rodata sits with the data, not with the text: the ESP32-S3 selects * CONFIG_ARCH_HAVE_TEXT_HEAP_WORD_ALIGNED_READ, so the loader places every * section that is not executable into the data region, and unaligned reads @@ -47,7 +54,7 @@ SECTIONS { - . = 0x42800000; + . = 0x42c00000; .text : { From 8c5e993404e11b283fe65d617eae00c7d846b2b8 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 15:17:25 +0200 Subject: [PATCH 09/22] arch/xtensa: Provide POSIX fork() on the ESP32-S3. up_addrenv_fork() duplicates an address environment into freshly allocated pages mapped at the same virtual addresses. The text, data and heap regions of the source are walked one page at a time and copied into fresh pages hung off the child's own directory, using the two kmap slots that CONFIG_ARCH_KMAP_NPAGES reserves for exactly this. xtensa_fork.c already took both paths: a child that keeps the parent's stack addresses needs no relocation, which is what a duplicated address environment gives it. Only the hook and the Kconfig default were missing. fork() is offered on a kernel build, which is the only mode with per-process address environments. Verified on an ESP32-S3-WROOM-2 with esp32s3-devkit:kernel_oct. ostest reports "Parent and child had independent memory" and exits with status 0. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/Kconfig | 1 + arch/xtensa/src/esp32s3/esp32s3_addrenv.c | 149 ++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index e5df9c22a3393..30d4e3e8253d8 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -493,6 +493,7 @@ config ARCH_HAVE_VFORK config ARCH_HAVE_FORK bool + default y if ARCH_XTENSA && BUILD_KERNEL default n depends on ARCH_ADDRENV ---help--- diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.c b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c index 61b62c9353f46..f1c85e45a413d 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_addrenv.c +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -103,6 +104,7 @@ static int alloc_region(uintptr_t *pages, unsigned int maxpages, size_t size, uintptr_t paddr = mm_pgalloc(1); if (paddr == 0) { + berr("ERROR: page pool exhausted at page %u of %u\n", i, npages); *count = i; return -ENOMEM; } @@ -577,6 +579,153 @@ int up_addrenv_clone(const arch_addrenv_t *src, arch_addrenv_t *dest) return OK; } +/**************************************************************************** + * Name: copy_region + * + * Description: + * Copy one region's pages from a parent environment into a child's. Both + * sides are reached through the kernel's scratch region, which is why it + * has two slots: source and destination are mapped at the same time so + * this is one memcpy rather than a bounce through kernel memory. + * + * sched_lock() is held across each page pair for the reason every scratch + * mapping is: those addresses are ordinary external memory to the + * permission control, so no unprivileged task may run while a page of + * somebody's memory is parked at one. + * + ****************************************************************************/ + +static int copy_region(const uintptr_t *src, uintptr_t *dest, uint16_t count) +{ + uint16_t i; + + for (i = 0; i < count; i++) + { + uintptr_t svaddr; + uintptr_t dvaddr; + + sched_lock(); + + svaddr = esp32s3_pgmap(src[i]); + dvaddr = esp32s3_pgmap(dest[i]); + + if (svaddr == 0 || dvaddr == 0) + { + if (svaddr != 0) + { + esp32s3_pgunmap(svaddr); + } + + if (dvaddr != 0) + { + esp32s3_pgunmap(dvaddr); + } + + sched_unlock(); + berr("ERROR: no scratch mapping for page %u\n", i); + return -EFAULT; + } + + memcpy((void *)dvaddr, (const void *)svaddr, MM_PGSIZE); + + esp32s3_pgunmap(dvaddr); + esp32s3_pgunmap(svaddr); + + sched_unlock(); + } + + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_fork + * + * Description: + * Duplicate an address environment for fork(): allocate the child pages + * to match the parent's regions and copy the parent's contents into them. + * + * The copy is eager and complete. There is no copy-on-write and no demand + * fill, because this chip provides no synchronous restartable write fault + * to build them on -- proven, not assumed. So a fork costs a full copy of + * the process image. + * + * The child's pages land at the same *virtual* addresses as the parent's, + * which is the property the whole thing rests on: a copied stack is full + * of pointers into itself, and they are only still correct because the + * copy is addressed identically. + * + ****************************************************************************/ + +int up_addrenv_fork(const arch_addrenv_t *src, arch_addrenv_t *dest) +{ + int ret; + + DEBUGASSERT(src && dest); + + memset(dest, 0, sizeof(arch_addrenv_t)); + + dest->textvbase = src->textvbase; + dest->datavbase = src->datavbase; + dest->heapvbase = src->heapvbase; + dest->heapsize = src->heapsize; + + /* Allocate the child's pages. alloc_region() takes a size, and the + * parent's page counts are the exact sizes wanted. + */ + + ret = alloc_region(dest->textpages, CONFIG_ARCH_TEXT_NPAGES, + (size_t)src->ntext * MM_PGSIZE, &dest->ntext); + if (ret < 0) + { + goto errout; + } + + ret = alloc_region(dest->datapages, CONFIG_ARCH_DATA_NPAGES, + (size_t)src->ndata * MM_PGSIZE, &dest->ndata); + if (ret < 0) + { + goto errout; + } + + ret = alloc_region(dest->heappages, CONFIG_ARCH_HEAP_NPAGES, + (size_t)src->nheap * MM_PGSIZE, &dest->nheap); + if (ret < 0) + { + goto errout; + } + + /* Then fill them from the parent */ + + ret = copy_region(src->textpages, dest->textpages, dest->ntext); + if (ret < 0) + { + goto errout; + } + + ret = copy_region(src->datapages, dest->datapages, dest->ndata); + if (ret < 0) + { + goto errout; + } + + ret = copy_region(src->heappages, dest->heappages, dest->nheap); + if (ret < 0) + { + goto errout; + } + + /* The text pages were written through the data bus. Make them visible to + * instruction fetch before anything runs from them. + */ + + esp32s3_addrenv_coherent(); + return OK; + +errout: + up_addrenv_destroy(dest); + return ret; +} + /**************************************************************************** * Name: up_addrenv_attach * From be6dcf31c222c84d4d77c09177bab2999f23b7f7 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 21:12:55 +0200 Subject: [PATCH 10/22] boards/esp32s3-devkit: Add kernel_n8r2, a kernel build for 2 MB PSRAM. kernel_oct targets a WROOM-2 N32R8V: octal flash, and 8 MB of PSRAM for the page pool. The defaults size the pool for that part, with 8 pages of 64 KiB for each of the text, data and heap regions, so 1.5 MB per process. fork() duplicates the address environment, so a parent and a child need 3 MB at once and a module with 2 MB of PSRAM cannot do it. kernel_n8r2 sizes the same build for such a module. Each region is 2 pages, so a process takes 384 KiB and a fork() peaks at 768 KiB, inside a 1.5 MB pool placed at 0x80000 to leave the start of the PSRAM alone. The flash is quad and runs in DIO mode, so this configuration also exercises the CONFIG_ESP32S3_FLASH_MODE_OCT guard in kernel-space.ld from the quad side, which kernel_oct cannot. This is tight by construction. ostest has 115 KiB of text against a 128 KiB text region. A larger program needs a module with more PSRAM, not a larger pool. Verified on an ESP32-S3-DevKitC with an N8R2 module, 8 MB flash in DIO mode and 2 MB of embedded quad PSRAM. ostest reports "Parent and child had independent memory" and exits with status 0. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- .../configs/kernel_n8r2/defconfig | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig new file mode 100644 index 0000000000000..a92a137cb3a1c --- /dev/null +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig @@ -0,0 +1,100 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_LEDS is not set +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +CONFIG_ARCH="xtensa" +CONFIG_ARCH_ADDRENV=y +CONFIG_ARCH_BOARD="esp32s3-devkit" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32S3_DEVKIT=y +CONFIG_ARCH_CHIP="esp32s3" +CONFIG_ARCH_CHIP_ESP32S3=y +CONFIG_ARCH_CHIP_ESP32S3WROOM1N8R2=y +CONFIG_ARCH_DATA_NPAGES=2 +CONFIG_ARCH_DATA_VBASE=0x3d000000 +CONFIG_ARCH_HEAP_NPAGES=2 +CONFIG_ARCH_HEAP_VBASE=0x3d200000 +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_IRQ_TO_NDX=y +CONFIG_ARCH_KERNEL_STACKSIZE=8192 +CONFIG_ARCH_KMAP_NPAGES=2 +CONFIG_ARCH_KMAP_VBASE=0x3d400000 +CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y +CONFIG_ARCH_NUSER_INTERRUPTS=2 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_TEXT_NPAGES=2 +CONFIG_ARCH_TEXT_VBASE=0x42c00000 +CONFIG_ARCH_USE_MMU=y +CONFIG_ARCH_XTENSA=y +CONFIG_BINFMT_ELF_EXECUTABLE=y +CONFIG_BOARD_INITTHREAD_STACKSIZE=2048 +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILD_KERNEL=y +CONFIG_DEBUG_BINFMT=y +CONFIG_DEBUG_BINFMT_ERROR=y +CONFIG_DEBUG_BINFMT_INFO=y +CONFIG_DEBUG_BINFMT_WARN=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_FULLOPT=y +CONFIG_DEBUG_SCHED=y +CONFIG_DEBUG_SCHED_ERROR=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEFAULT_TASK_STACKSIZE=8192 +CONFIG_ELF=y +CONFIG_ESP32S3_PAGEFAULT=y +CONFIG_ESP32S3_PGPOOL_PBASE=0x80000 +CONFIG_ESP32S3_PGPOOL_SIZE=1572864 +CONFIG_ESP32S3_PSRAM_8M=y +CONFIG_ESP32S3_SPIFLASH=y +CONFIG_ESP32S3_SPIRAM=y +CONFIG_ESP32S3_UART0=y +CONFIG_ESP32S3_WCL=y +CONFIG_FS_PROCFS=y +CONFIG_FS_ROMFS=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_HOST_MACOS=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_FILEPATH="/system/bin/init" +CONFIG_INIT_MOUNT=y +CONFIG_INIT_MOUNT_FLAGS=0x1 +CONFIG_INIT_MOUNT_TARGET="/system/bin" +CONFIG_INTELHEX_BINARY=y +CONFIG_IRQ_WORK_STACKSIZE=2048 +CONFIG_LIBC_ENVPATH=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LINE_MAX=64 +CONFIG_MM_PGALLOC=y +CONFIG_MM_PGSIZE=65536 +CONFIG_NSH_DISABLE_LOSMART=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_FILE_APPS=y +CONFIG_NSH_READLINE=y +CONFIG_PATH_INITIAL="/system/bin" +CONFIG_PREALLOC_TIMERS=4 +CONFIG_PTHREAD_STACK_DEFAULT=2048 +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RAW_BINARY=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_HAVE_PARENT=y +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_LPWORKSTACKSIZE=2048 +CONFIG_SCHED_WAITPID=y +CONFIG_STACK_COLORATION=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_NSH_PROGNAME="init" +CONFIG_TESTING_GETPRIME=y +CONFIG_TESTING_OSTEST=y +CONFIG_TESTING_OSTEST_FPUTESTDISABLE=y +CONFIG_UART0_SERIAL_CONSOLE=y From f83f5313bea002fad9b2f8a2066fee82d673194c Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 22:01:57 +0200 Subject: [PATCH 11/22] Documentation/esp32s3-devkit: Describe the kernel build configurations. Describe kernel_oct and kernel_n8r2 next to the other configurations of this board. The entry for kernel_oct carries what a user needs and cannot guess: a KERNEL build is the only mode with fork() on this chip, the page pool is reached through a scratch mapping rather than a permanent window, the ROMFS is linked into the kernel image so a change to an application needs the whole export-import-mkromfsimg-relink chain, how to confirm that the ROMFS is really in the image, and that the shell needs the full path of a program. The entry for kernel_n8r2 states its limit. Each region of a process is 2 pages, ostest has 115 KiB of text against a 128 KiB text region, and a larger program needs a module with more PSRAM. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- .../esp32s3/boards/esp32s3-devkit/index.rst | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-devkit/index.rst b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-devkit/index.rst index dae236dbfada8..77b01e97d026c 100644 --- a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-devkit/index.rst +++ b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-devkit/index.rst @@ -372,6 +372,63 @@ After successfully built and flashed, run on the boards's terminal:: The corresponding output should show related debug information. +kernel_n8r2 +----------- + +A KERNEL build for a module with 2 MB of PSRAM, such as the +ESP32-S3-WROOM-1-N8R2. The flash is quad and runs in DIO mode. + +The page pool is smaller than in ``kernel_oct``, and each of the text, data +and heap regions of a process is 2 pages of 64 KiB. A process therefore takes +384 KiB, and a ``fork()`` needs 768 KiB while the parent and the child both +exist. + +This is tight on purpose. ``ostest`` has 115 KiB of text against a 128 KiB +text region. A larger program needs a module with more PSRAM, not a larger +page pool. + +kernel_oct +---------- + +A KERNEL build for a module with octal flash and 8 MB of PSRAM, such as the +ESP32-S3-WROOM-2-N32R8V. + +A KERNEL build gives each process its own address environment. This is the +only build mode in which ``fork()`` is available on this chip: the child +receives its own copy of the memory of the parent, at the same virtual +addresses. ``vfork()`` is available in every build mode. + +The page pool holds the pages of every process. It is carved out of the +PSRAM, and it is not kept mapped into the kernel address space. The kernel +reaches a page of the pool through a small scratch region, mapped for one +operation and invalidated afterwards. + +Build the kernel first, then the applications, then the boot ROMFS, and then +link the kernel again. The ROMFS is linked into the kernel image, so a change +to an application needs the whole chain:: + + make -j + make export + cd ../apps + ./tools/mkimport.sh -z -x ../nuttx/nuttx-export-*.tar.gz + make import -j + ./tools/mkromfsimg.sh ../nuttx/arch/xtensa/src/board/board/romfs_boot.c + cd ../nuttx + make -j + +Confirm that the ROMFS is in the image. ``romfs_img`` must be a strong symbol +of a few hundred kilobytes:: + + xtensa-esp32s3-elf-nm -S nuttx | grep " romfs_img$" + +A one byte symbol means that the linker took the stub. Delete +``boards/xtensa/esp32s3/esp32s3-devkit/src/romfs_stub.o`` and ``libboard.a``, +then link again. + +The shell needs the full path of a program in this build mode:: + + nsh> /system/bin/ostest + knsh ---- From 6d648f6ac5ff33e441494260529ec35d003fc4c6 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 15:13:11 +0200 Subject: [PATCH 12/22] arch/xtensa: Address the review of the fork series. Two points from the review of #19772 that belong with the ESP32-S3 work. ARCH_HAVE_FORK is now selected by the architecture rather than defaulted from inside its own definition, so the condition sits where a reader of arch/Kconfig will look for it. It repeats the ARCH_ADDRENV dependency, because a select bypasses depends on and without that an architecture could offer fork() with no address environment to duplicate. The page pool no longer carries chip-specific copies of settings the common address environment already defines. ARCH_PGPOOL_PBASE and ARCH_PGPOOL_SIZE were only reachable under ARCH_PGPOOL_MAPPING, which does not apply here: the pool is deliberately left unmapped, because it is carved out of the PSRAM the user processes run from and the external memory permissions are indexed by physical address. But a physical base and a size describe the pool whether or not it is mapped -- only a virtual base needs the mapping -- so those two move out of that block and the chip uses them. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/Kconfig | 26 +++++++++++-------- arch/xtensa/src/esp32s3/Kconfig | 24 +++-------------- arch/xtensa/src/esp32s3/esp32s3_addrenv.h | 8 +++--- .../configs/kernel_n8r2/defconfig | 4 +-- .../configs/kernel_oct/defconfig | 4 +-- 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 30d4e3e8253d8..c04a428a52c11 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -159,6 +159,7 @@ config ARCH_XTENSA bool "Xtensa" select ARCH_HAVE_BACKTRACE select ARCH_HAVE_VFORK + select ARCH_HAVE_FORK if BUILD_KERNEL && ARCH_ADDRENV select ARCH_HAVE_CPUINFO select ARCH_HAVE_INTERRUPTSTACK select ARCH_HAVE_STACKCHECK @@ -493,7 +494,6 @@ config ARCH_HAVE_VFORK config ARCH_HAVE_FORK bool - default y if ARCH_XTENSA && BUILD_KERNEL default n depends on ARCH_ADDRENV ---help--- @@ -1036,8 +1036,6 @@ config ARCH_PGPOOL_MAPPING Otherwise, a temporary mapping will have to be established each time it is necessary to modify the contents of a page. -if ARCH_PGPOOL_MAPPING - config ARCH_PGPOOL_PBASE hex "Page pool physical address" default 0x0 @@ -1047,14 +1045,9 @@ config ARCH_PGPOOL_PBASE but is required again in order to modularize the common address environment logic. -config ARCH_PGPOOL_VBASE - hex "Page pool virtual address" - default 0x0 - ---help--- - The virtual address of the start of the page pool memory. This - setting is probably equivalent to other platform specific definitions - but is required again in order to modularize the common address - environment logic. + This applies whether or not the pool is statically mapped: the page + allocator hands out physical addresses either way. Only + ARCH_PGPOOL_VBASE depends on ARCH_PGPOOL_MAPPING. config ARCH_PGPOOL_SIZE int "Page pool size (bytes)" @@ -1064,6 +1057,17 @@ config ARCH_PGPOOL_SIZE equivalent to other platform specific definitions but is required again in order to modularize the common address environment logic. +if ARCH_PGPOOL_MAPPING + +config ARCH_PGPOOL_VBASE + hex "Page pool virtual address" + default 0x0 + ---help--- + The virtual address of the start of the page pool memory. This + setting is probably equivalent to other platform specific definitions + but is required again in order to modularize the common address + environment logic. + endif # ARCH_PGPOOL_MAPPING endif # ARCH_ADDRENV && ARCH_NEED_ADDRENV_MAPPING diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index 02ca3b6e62297..b0b392d3b99b6 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -359,27 +359,9 @@ config ESP32S3_PGPOOL_SCRATCH instead, mapped for the duration of one operation and invalidated afterwards. ARCH_KMAP_VBASE and ARCH_KMAP_NPAGES describe it. -if ESP32S3_PGPOOL_SCRATCH - -config ESP32S3_PGPOOL_PBASE - hex "Page pool physical address" - default 0x350000 - ---help--- - The physical base address of the page pool. For the cache MMU that - is a zero-based offset into the PSRAM device, not a CPU address. - - This replaces ARCH_PGPOOL_PBASE, which only exists when the pool is - statically mapped. The pool is still described physically because - mm_pgalloc() hands out physical page addresses; what goes away is - the permanent virtual mapping, not the pool itself. - -config ESP32S3_PGPOOL_SIZE - int "Page pool size (bytes)" - default 4194304 - ---help--- - The size of the page pool in bytes. Decimal, not hex. - -endif # ESP32S3_PGPOOL_SCRATCH + The pool itself is described by ARCH_PGPOOL_PBASE and + ARCH_PGPOOL_SIZE as usual. Only ARCH_PGPOOL_VBASE does not apply, + because there is no permanent virtual mapping to name. menu "ESP32-S3 Peripheral Selection" diff --git a/arch/xtensa/src/esp32s3/esp32s3_addrenv.h b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h index ca77ce5f702e8..1242d0de18add 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_addrenv.h +++ b/arch/xtensa/src/esp32s3/esp32s3_addrenv.h @@ -87,10 +87,10 @@ * addresses; only the virtual mapping went away, not the pool. */ -#define ESP32S3_PGPOOL_PBASE (CONFIG_ESP32S3_PGPOOL_PBASE) -#define ESP32S3_PGPOOL_SIZE (CONFIG_ESP32S3_PGPOOL_SIZE) -#define ESP32S3_PGPOOL_PEND (CONFIG_ESP32S3_PGPOOL_PBASE + \ - CONFIG_ESP32S3_PGPOOL_SIZE) +#define ESP32S3_PGPOOL_PBASE (CONFIG_ARCH_PGPOOL_PBASE) +#define ESP32S3_PGPOOL_SIZE (CONFIG_ARCH_PGPOOL_SIZE) +#define ESP32S3_PGPOOL_PEND (CONFIG_ARCH_PGPOOL_PBASE + \ + CONFIG_ARCH_PGPOOL_SIZE) /* The user address space is split across two disjoint cache-MMU windows: * .text lives in the instruction-bus window, .data/.bss and the heap in the diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig index a92a137cb3a1c..881e0d84a6cdc 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_n8r2/defconfig @@ -48,8 +48,8 @@ CONFIG_DEBUG_SYMBOLS=y CONFIG_DEFAULT_TASK_STACKSIZE=8192 CONFIG_ELF=y CONFIG_ESP32S3_PAGEFAULT=y -CONFIG_ESP32S3_PGPOOL_PBASE=0x80000 -CONFIG_ESP32S3_PGPOOL_SIZE=1572864 +CONFIG_ARCH_PGPOOL_PBASE=0x80000 +CONFIG_ARCH_PGPOOL_SIZE=1572864 CONFIG_ESP32S3_PSRAM_8M=y CONFIG_ESP32S3_SPIFLASH=y CONFIG_ESP32S3_SPIRAM=y diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig index 7482b72a44f7d..5e11bbe7726e4 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig @@ -51,8 +51,8 @@ CONFIG_ESP32S3_FLASH_MODE_OCT=y CONFIG_ESP32S3_FLASH_SAMPLE_MODE_STR=y CONFIG_ESP32S3_PAGEFAULT=y CONFIG_ESP32S3_SPIFLASH=y -CONFIG_ESP32S3_PGPOOL_PBASE=0x350000 -CONFIG_ESP32S3_PGPOOL_SIZE=4194304 +CONFIG_ARCH_PGPOOL_PBASE=0x350000 +CONFIG_ARCH_PGPOOL_SIZE=4194304 CONFIG_ESP32S3_SPIRAM=y CONFIG_ESP32S3_SPIRAM_MODE_OCT=y CONFIG_ESP32S3_UART0=y From 3be3fe293a393ea88984f311a7c22d9f49cde01a Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 15:14:42 +0200 Subject: [PATCH 13/22] xtensa/esp32s3: Wire the chip into the kernel build. The common Xtensa BUILD_KERNEL support needs the chip to say what it can do and where its memory goes. The chip selects the address environment options it now implements, keeps the kernel and user heaps apart, and the linker scripts separate kernel from user text and data so the two worlds can be given different permissions. Split out of the same change as the common code, so that arch/xtensa/src/common can be reviewed without the chip in the way. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/Kconfig | 1 + arch/xtensa/src/esp32s3/chip_macros.h | 4 +- .../xtensa/src/esp32s3/esp32s3_allocateheap.c | 21 + .../common/scripts/esp32s3_sections.ld | 415 +++++++++--------- .../esp32s3/common/scripts/legacy_sections.ld | 111 ++--- 5 files changed, 300 insertions(+), 252 deletions(-) diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index 6f2d955653254..ed5a5fe4f323f 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -88,6 +88,7 @@ config ARCH_CHIP_ESP32S3 select ARCH_HAVE_MMU select ARCH_HAVE_ADDRENV select ARCH_NEED_ADDRENV_MAPPING + select ARCH_HAVE_ELF_EXECUTABLE select ARCH_HAVE_MULTICPU select ARCH_HAVE_RESET select ARCH_HAVE_TEXT_HEAP diff --git a/arch/xtensa/src/esp32s3/chip_macros.h b/arch/xtensa/src/esp32s3/chip_macros.h index 652d95a3a3120..963fa91d174fb 100644 --- a/arch/xtensa/src/esp32s3/chip_macros.h +++ b/arch/xtensa/src/esp32s3/chip_macros.h @@ -33,7 +33,7 @@ #endif #endif -#if defined(CONFIG_ESP32S3_WCL) && defined(CONFIG_BUILD_PROTECTED) +#if defined(CONFIG_ESP32S3_WCL) && !defined(CONFIG_BUILD_FLAT) #include "hardware/esp32s3_wcl_core.h" #endif @@ -47,7 +47,7 @@ #define HANDLER_SECTION .iram1 -#if defined(CONFIG_ESP32S3_WCL) && defined(CONFIG_BUILD_PROTECTED) +#if defined(CONFIG_ESP32S3_WCL) && !defined(CONFIG_BUILD_FLAT) /* Definitions for the Worlds reserved for Kernel and Userspace */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c b/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c index fbab081a55068..11034a9b5efbe 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c +++ b/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c @@ -96,6 +96,17 @@ void up_allocate_heap(void **heap_start, size_t *heap_size) utop = ALIGN_DOWN(ets_rom_layout_p->dram0_rtos_reserved_start, 256); +# elif defined(CONFIG_BUILD_KERNEL) + /* There is no single user heap in a kernel build: each task group grows + * its own out of the page pool as it is created. nx_start() knows this + * and never asks -- MM_KERNEL_USRHEAP_INIT is undefined for BUILD_KERNEL + * -- so report an empty heap and let the assertion below catch it if this + * ever is reached. + */ + + ubase = 0; + utop = 0; + # elif defined(CONFIG_BUILD_FLAT) # ifdef MM_USER_HEAP_EXTRAM ubase = (uintptr_t)esp_spiram_allocable_vaddr_start(); @@ -160,6 +171,16 @@ void up_allocate_kheap(void **heap_start, size_t *heap_size) kbase = (uintptr_t)_sheap; ktop = KDRAM_END; +#elif defined(CONFIG_BUILD_KERNEL) + /* A kernel build has a single kernel heap and one user heap per task + * group, the latter grown out of the page pool by pgalloc(). So the whole + * internal DRAM heap belongs to the kernel here, whether or not PSRAM is + * fitted: PSRAM backs the page pool, not this heap. + */ + + kbase = (uintptr_t)_sheap + XTENSA_IMEM_REGION_SIZE; + ktop = (uintptr_t)ets_rom_layout_p->dram0_rtos_reserved_start; + #elif defined(CONFIG_BUILD_FLAT) # ifdef MM_USER_HEAP_IRAM /* Skip internal heap region if CONFIG_XTENSA_IMEM_USE_SEPARATE_HEAP is diff --git a/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld b/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld index 84f48a4c93ede..5fa66fd9f3706 100644 --- a/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld +++ b/boards/xtensa/esp32s3/common/scripts/esp32s3_sections.ld @@ -22,6 +22,19 @@ #include +/* A kernel build archives the architecture objects into libkarch.a rather + * than libarch.a, so the placement rules below have to name the right one. + * Getting this wrong is silent and fatal: the octal-flash bring-up functions + * stay in mapped flash and are called while that mapping is being + * reconfigured. + */ + +#ifdef CONFIG_BUILD_KERNEL +# define ARCHLIB *libkarch.a +#else +# define ARCHLIB *libarch.a +#endif + /* Default entry point: */ ENTRY(__start); @@ -165,20 +178,20 @@ SECTIONS *libpp.a:wifi_slp_iram.*(.literal .text .literal.* .text.*) *libpp.a:wifi_or_slp_iram.*(.literal .text .literal.* .text.*) *libpp.a:wifi_slp_rx_iram.*(.literal .text .literal.* .text.*) - *libarch.a:*esp_loader.*(.literal .text .literal.* .text.*) - *libarch.a:esp32s3_cpuindex.*(.literal .text .literal.* .text.*) - *libarch.a:esp32s3_user.*(.literal .text .literal.* .text.*) - *libarch.a:esp32s3_spiflash.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_assert.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_cpuint.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_cpupause.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_irqdispatch.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_modifyreg32.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_testset.*(.literal .text .literal.* .text.*) - *libarch.a:esp_app_desc.*(.literal .text .literal.* .text.*) - *libarch.a:intr_alloc.*(.literal.esp_intr_get_intno .text.esp_intr_get_intno) - *libarch.a:xtensa_intr.*(.literal.xt_get_interrupt_handler .text.xt_get_interrupt_handler) - *libarch.a:xtensa_intr.*(.literal.xt_get_interrupt_handler_arg .text.xt_get_interrupt_handler_arg) + ARCHLIB:*esp_loader.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_cpuindex.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_user.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_spiflash.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_assert.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_cpuint.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_cpupause.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_irqdispatch.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_modifyreg32.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_testset.*(.literal .text .literal.* .text.*) + ARCHLIB:esp_app_desc.*(.literal .text .literal.* .text.*) + ARCHLIB:intr_alloc.*(.literal.esp_intr_get_intno .text.esp_intr_get_intno) + ARCHLIB:xtensa_intr.*(.literal.xt_get_interrupt_handler .text.xt_get_interrupt_handler) + ARCHLIB:xtensa_intr.*(.literal.xt_get_interrupt_handler_arg .text.xt_get_interrupt_handler_arg) *libc.a:sq_remlast.*(.literal .text .literal.* .text.*) @@ -199,11 +212,11 @@ SECTIONS *libsched.a:stack_monitor.*(.literal .text .literal.* .text.*) #ifdef CONFIG_ESP32S3_SPEED_UP_ISR - *libarch.a:xtensa_switchcontext.*(.literal.up_switch_context .text.up_switch_context) + ARCHLIB:xtensa_switchcontext.*(.literal.up_switch_context .text.up_switch_context) - *libarch.a:esp32s3_timerisr.*(.literal.systimer_isr .text.systimer_isr) - *libarch.a:esp32s3_idle.*(.literal.up_idle .text.up_idle) - *libarch.a:esp32s3_dma.*(.literal.esp32s3_dma_load .text.esp32s3_dma_load \ + ARCHLIB:esp32s3_timerisr.*(.literal.systimer_isr .text.systimer_isr) + ARCHLIB:esp32s3_idle.*(.literal.up_idle .text.up_idle) + ARCHLIB:esp32s3_dma.*(.literal.esp32s3_dma_load .text.esp32s3_dma_load \ .literal.esp32s3_dma_enable .text.esp32s3_dma_enable) *libsched.a:sched_processtimer.*(.literal.nxsched_process_timer .text.nxsched_process_timer) @@ -219,12 +232,12 @@ SECTIONS *libc.a:sq_remfirst.*(.literal.sq_remfirst .text.sq_remfirst) #endif - *libarch.a:esp32s3_spi_timing.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_spi_timing.*(.literal .text .literal.* .text.*) #ifdef CONFIG_ESP32S3_SPIRAM_MODE_QUAD - *libarch.a:esp32s3_psram_quad.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_psram_quad.*(.literal .text .literal.* .text.*) #endif #ifdef CONFIG_ESP32S3_SPIRAM_MODE_OCT - *libarch.a:esp32s3_psram_octal.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_psram_octal.*(.literal .text .literal.* .text.*) #endif #if defined(CONFIG_STACK_CANARIES) && \ (defined(CONFIG_ESP32S3_SPIFLASH) || \ @@ -232,98 +245,98 @@ SECTIONS *libc.a:lib_stackchk.*(.literal .text .literal.* .text.*) #endif - *libarch.a:*brownout_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*cpu.*(.text .text.* .literal .literal.*) - *libarch.a:*gpio_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*periph_ctrl.*(.text .text.* .literal .literal.*) - *libarch.a:*clk.*(.text .text.* .literal .literal.*) - *libarch.a:*efuse_hal.*(.literal.is_eco0 .text.is_eco0) - *libarch.a:*efuse_utility.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_clk.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_clk_tree.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_clk_tree_common.*(.text .text.* .literal .literal.*) - *libarch.a:*clk_tree_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*rtc_init.*(.text .text.* .literal .literal.*) - *libarch.a:*rtc_clk.*(.text .text.* .literal .literal.*) - *libarch.a:*rtc_clk_init.*(.text .text.* .literal .literal.*) - *libarch.a:*rtc_sleep.*(.text .text.* .literal .literal.*) - *libarch.a:*rtc_time.*(.text .text.* .literal .literal.*) - *libarch.a:*regi2c_ctrl.*(.text .text.* .literal .literal.*) - *libarch.a:*uart_hal_iram.*(.text .text.* .literal .literal.*) - *libarch.a:*wdt_hal_iram.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_banner_wrap.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_init.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_common.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_common_loader.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_console.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_console_loader.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_esp32s3.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_flash.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_flash_config_esp32s3.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_clock_init.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_clock_loader.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_efuse.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_panic.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_mem.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_random.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_random*.*(.literal.bootloader_random_disable .text.bootloader_random_disable) - *libarch.a:*bootloader_random*.*(.literal.bootloader_random_enable .text.bootloader_random_enable) - *libarch.a:*bootloader_random_esp32s3.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_image_format.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_soc.*(.text .text.* .literal .literal.*) - *libarch.a:*bootloader_sha.*(.text .text.* .literal .literal.*) - *libarch.a:*flash_encrypt.*(.text .text.* .literal .literal.*) - *libarch.a:*cache_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*uart_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*mpu_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*mmu_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*efuse_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*uart_periph.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_uart.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_sys.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_spiflash.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_cache_esp32s2_esp32s3.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_wdt.*(.text .text.* .literal .literal.*) - *libarch.a:*log.*(.text .text.* .literal .literal.*) - *libarch.a:*log_lock.*(.literal .literal.* .text .text.*) - *libarch.a:*log_print.*(.literal .literal.* .text .text.*) - *libarch.a:*log_timestamp.*(.literal.esp_log_early_timestamp .text.esp_log_early_timestamp) - *libarch.a:*log_timestamp.*(.literal.esp_log_timestamp .text.esp_log_timestamp) - *libarch.a:*log_timestamp_common.*(.literal .literal.* .text .text.*) - *libarch.a:*log_write.*(.literal.esp_log_write .text.esp_log_write) - *libarch.a:*log_write.*(.literal.esp_log_writev .text.esp_log_writev) - *libarch.a:*cpu_region_protect.*(.text .text.* .literal .literal.*) - *libarch.a:*mspi_timing_tuning.*(.text .text.* .literal .literal.*) - *libarch.a:*mspi_timing_config.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) - *libarch.a:*flash_qio_mode.*(.text .text.* .literal .literal.*) - *libarch.a:*spi_flash_wrap.*(.text .text.* .literal .literal.*) - *libarch.a:*spi_flash_oct_flash_init.*(.text .text.* .literal .literal.*) - *libarch.a:*spi_flash_hpm_enable.*(.text .text.* .literal .literal.*) - *libarch.a:esp_spiflash.*(.literal .text .literal.* .text.*) - *libarch.a:esp_flash_api.*(.text .text.* .literal .literal.*) - *libarch.a:esp_flash_spi_init.*(.text .text.* .literal .literal.*) - *libarch.a:spi_flash_hal_iram.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_encrypt_hal_iram.*(.text .text.* .literal .literal.*) - *libarch.a:spi_flash_hal_gpspi.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_chip*.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_wrap.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_os_func_noos.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_os_func_app.*(.literal .literal.* .text .text.*) - *libarch.a:flash_brownout_hook.*(.literal .literal.* .text .text.*) - *libarch.a:esp_cache_utils.*(.literal .literal.* .text .text.*) - *libarch.a:cache_utils.*(.literal .literal.* .text .text.*) - *libarch.a:memspi_host_driver.*(.literal .literal.* .text .text.*) - *libarch.a:critical_section.*(.literal .literal.* .text .text.*) - *libarch.a:os.*(.literal.nuttx_enter_critical .text.nuttx_enter_critical) - *libarch.a:os.*(.literal.nuttx_exit_critical .text.nuttx_exit_critical) - *libarch.a:*sleep_modes.*(.literal.esp_sleep_sub_mode_force_disable* .text.esp_sleep_sub_mode_force_disable*) - *libarch.a:intr_alloc.*(.literal.esp_intr_get_intno .text.esp_intr_get_intno) - *libarch.a:intr_alloc.*(.literal.esp_intr_get_cpu .text.esp_intr_get_cpu) - *libarch.a:interrupt.*(.literal.intr_handler_get .text.intr_handler_get) - *libarch.a:interrupt.*(.literal.intr_handler_get_arg .text.intr_handler_get_arg) - *libarch.a:interrupt.*(.literal.intr_get_item .text.intr_get_item) - *libarch.a:interrupt.*(.literal.intr_handler_get_arg .text.intr_handler_get_arg) + ARCHLIB:*brownout_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*cpu.*(.text .text.* .literal .literal.*) + ARCHLIB:*gpio_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*periph_ctrl.*(.text .text.* .literal .literal.*) + ARCHLIB:*clk.*(.text .text.* .literal .literal.*) + ARCHLIB:*efuse_hal.*(.literal.is_eco0 .text.is_eco0) + ARCHLIB:*efuse_utility.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_clk.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_clk_tree.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_clk_tree_common.*(.text .text.* .literal .literal.*) + ARCHLIB:*clk_tree_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*rtc_init.*(.text .text.* .literal .literal.*) + ARCHLIB:*rtc_clk.*(.text .text.* .literal .literal.*) + ARCHLIB:*rtc_clk_init.*(.text .text.* .literal .literal.*) + ARCHLIB:*rtc_sleep.*(.text .text.* .literal .literal.*) + ARCHLIB:*rtc_time.*(.text .text.* .literal .literal.*) + ARCHLIB:*regi2c_ctrl.*(.text .text.* .literal .literal.*) + ARCHLIB:*uart_hal_iram.*(.text .text.* .literal .literal.*) + ARCHLIB:*wdt_hal_iram.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_banner_wrap.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_init.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_common.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_common_loader.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_console.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_console_loader.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_esp32s3.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_flash.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_flash_config_esp32s3.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_clock_init.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_clock_loader.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_efuse.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_panic.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_mem.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_random.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_random*.*(.literal.bootloader_random_disable .text.bootloader_random_disable) + ARCHLIB:*bootloader_random*.*(.literal.bootloader_random_enable .text.bootloader_random_enable) + ARCHLIB:*bootloader_random_esp32s3.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_image_format.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_soc.*(.text .text.* .literal .literal.*) + ARCHLIB:*bootloader_sha.*(.text .text.* .literal .literal.*) + ARCHLIB:*flash_encrypt.*(.text .text.* .literal .literal.*) + ARCHLIB:*cache_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*uart_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*mpu_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*mmu_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*efuse_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*uart_periph.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_uart.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_sys.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_spiflash.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_cache_esp32s2_esp32s3.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_wdt.*(.text .text.* .literal .literal.*) + ARCHLIB:*log.*(.text .text.* .literal .literal.*) + ARCHLIB:*log_lock.*(.literal .literal.* .text .text.*) + ARCHLIB:*log_print.*(.literal .literal.* .text .text.*) + ARCHLIB:*log_timestamp.*(.literal.esp_log_early_timestamp .text.esp_log_early_timestamp) + ARCHLIB:*log_timestamp.*(.literal.esp_log_timestamp .text.esp_log_timestamp) + ARCHLIB:*log_timestamp_common.*(.literal .literal.* .text .text.*) + ARCHLIB:*log_write.*(.literal.esp_log_write .text.esp_log_write) + ARCHLIB:*log_write.*(.literal.esp_log_writev .text.esp_log_writev) + ARCHLIB:*cpu_region_protect.*(.text .text.* .literal .literal.*) + ARCHLIB:*mspi_timing_tuning.*(.text .text.* .literal .literal.*) + ARCHLIB:*mspi_timing_config.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) + ARCHLIB:*flash_qio_mode.*(.text .text.* .literal .literal.*) + ARCHLIB:*spi_flash_wrap.*(.text .text.* .literal .literal.*) + ARCHLIB:*spi_flash_oct_flash_init.*(.text .text.* .literal .literal.*) + ARCHLIB:*spi_flash_hpm_enable.*(.text .text.* .literal .literal.*) + ARCHLIB:esp_spiflash.*(.literal .text .literal.* .text.*) + ARCHLIB:esp_flash_api.*(.text .text.* .literal .literal.*) + ARCHLIB:esp_flash_spi_init.*(.text .text.* .literal .literal.*) + ARCHLIB:spi_flash_hal_iram.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_encrypt_hal_iram.*(.text .text.* .literal .literal.*) + ARCHLIB:spi_flash_hal_gpspi.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_chip*.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_wrap.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_os_func_noos.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_os_func_app.*(.literal .literal.* .text .text.*) + ARCHLIB:flash_brownout_hook.*(.literal .literal.* .text .text.*) + ARCHLIB:esp_cache_utils.*(.literal .literal.* .text .text.*) + ARCHLIB:cache_utils.*(.literal .literal.* .text .text.*) + ARCHLIB:memspi_host_driver.*(.literal .literal.* .text .text.*) + ARCHLIB:critical_section.*(.literal .literal.* .text .text.*) + ARCHLIB:os.*(.literal.nuttx_enter_critical .text.nuttx_enter_critical) + ARCHLIB:os.*(.literal.nuttx_exit_critical .text.nuttx_exit_critical) + ARCHLIB:*sleep_modes.*(.literal.esp_sleep_sub_mode_force_disable* .text.esp_sleep_sub_mode_force_disable*) + ARCHLIB:intr_alloc.*(.literal.esp_intr_get_intno .text.esp_intr_get_intno) + ARCHLIB:intr_alloc.*(.literal.esp_intr_get_cpu .text.esp_intr_get_cpu) + ARCHLIB:interrupt.*(.literal.intr_handler_get .text.intr_handler_get) + ARCHLIB:interrupt.*(.literal.intr_handler_get_arg .text.intr_handler_get_arg) + ARCHLIB:interrupt.*(.literal.intr_get_item .text.intr_get_item) + ARCHLIB:interrupt.*(.literal.intr_handler_get_arg .text.intr_handler_get_arg) *libc.a:*lib_instrument.*(.text .text.* .literal .literal.*) *libc.a:arch_atomic.*(.text .text.* .literal .literal.*) @@ -470,7 +483,7 @@ SECTIONS esp32s3_region.*(.rodata .rodata.*) *libphy.a:(.rodata .rodata.*) - *libarch.a:xtensa_context.*(.rodata .rodata.*) + ARCHLIB:xtensa_context.*(.rodata .rodata.*) #if defined(CONFIG_STACK_CANARIES) && \ (defined(CONFIG_ESP32S3_SPIFLASH) || \ defined(CONFIG_ESP32S3_SPIRAM)) @@ -478,89 +491,89 @@ SECTIONS #endif *libsched.a:*sched_get_stackinfo.*(.rodata .rodata.*) - *libarch.a:*esp_loader.*(.rodata .rodata.*) - *libarch.a:esp32s3_spiflash.*(.rodata .rodata.*) - *libarch.a:esp_spiflash.*(.rodata .rodata.*) - *libarch.a:*efuse_utility.*(.rodata .rodata.*) - *libarch.a:*brownout.*(.rodata .rodata.*) - *libarch.a:*cpu.*(.rodata .rodata.*) - *libarch.a:*gpio_hal.*(.rodata .rodata.*) - *libarch.a:*periph_ctrl.*(.rodata .rodata.*) - *libarch.a:*clk.*(.rodata .rodata.*) - *libarch.a:*esp_clk.*(.rodata .rodata.*) - *libarch.a:*esp_clk_tree.*(.rodata .rodata.*) - *libarch.a:*esp_clk_tree_common.*(.rodata .rodata.*) - *libarch.a:*clk_tree_hal.*(.rodata .rodata.*) - *libarch.a:*rtc_init.*(.rodata .rodata.*) - *libarch.a:*rtc_clk.*(.rodata .rodata.*) - *libarch.a:*rtc_clk_init.*(.rodata .rodata.*) - *libarch.a:*rtc_sleep.*(.rodata .rodata.*) - *libarch.a:*rtc_time.*(.rodata .rodata.*) - *libarch.a:*regi2c_ctrl.*(.rodata .rodata.*) - *libarch.a:*uart_hal_iram.*(.rodata .rodata.*) - *libarch.a:*wdt_hal_iram.*(.rodata .rodata.*) - *libarch.a:*bootloader_banner_wrap.*(.rodata .rodata.*) - *libarch.a:*bootloader_init.*(.rodata .rodata.*) - *libarch.a:*bootloader_common.*(.rodata .rodata.*) - *libarch.a:*bootloader_common_loader.*(.rodata .rodata.*) - *libarch.a:*bootloader_console.*(.rodata .rodata.*) - *libarch.a:*bootloader_console_loader.*(.rodata .rodata.*) - *libarch.a:*bootloader_esp32s3.*(.rodata .rodata.*) - *libarch.a:*bootloader_flash.*(.rodata .rodata.*) - *libarch.a:*bootloader_flash_config_esp32s3.*(.rodata .rodata.*) - *libarch.a:*bootloader_clock_init.*(.rodata .rodata.*) - *libarch.a:*bootloader_clock_loader.*(.rodata .rodata.*) - *libarch.a:*bootloader_efuse.*(.rodata .rodata.*) - *libarch.a:*bootloader_panic.*(.rodata .rodata.*) - *libarch.a:*bootloader_mem.*(.rodata .rodata.*) - *libarch.a:*bootloader_random.*(.rodata .rodata.*) - *libarch.a:*bootloader_random_esp32s3.*(.rodata .rodata.*) - *libarch.a:*esp_image_format.*(.rodata .rodata.*) - *libarch.a:*bootloader_soc.*(.rodata .rodata.*) - *libarch.a:*bootloader_sha.*(.rodata .rodata.*) - *libarch.a:*flash_encrypt.*(.rodata .rodata.*) - *libarch.a:*cache_hal.*(.rodata .rodata.*) - *libarch.a:*uart_hal.*(.rodata .rodata.*) - *libarch.a:*mpu_hal.*(.rodata .rodata.*) - *libarch.a:*mmu_hal.*(.rodata .rodata.*) - *libarch.a:*uart_periph.*(.rodata .rodata.*) - *libarch.a:*esp_rom_uart.*(.rodata .rodata.*) - *libarch.a:*esp_rom_sys.*(.rodata .rodata.*) - *libarch.a:*esp_rom_spiflash.*(.rodata .rodata.*) - *libarch.a:*esp_rom_cache_esp32s2_esp32s3.*(.rodata .rodata.*) - *libarch.a:*esp_rom_wdt.*(.rodata .rodata.*) - *libarch.a:*efuse_hal.*(.rodata .rodata.*) - *libarch.a:*log.*(.rodata .rodata.*) - *libarch.a:*log_noos.*(.rodata .rodata.*) - *libarch.a:*cpu_region_protect.*(.rodata .rodata.*) - *libarch.a:*mspi_timing_tuning.*(.rodata .rodata.*) - *libarch.a:*mspi_timing_config.*(.rodata .rodata.*) + ARCHLIB:*esp_loader.*(.rodata .rodata.*) + ARCHLIB:esp32s3_spiflash.*(.rodata .rodata.*) + ARCHLIB:esp_spiflash.*(.rodata .rodata.*) + ARCHLIB:*efuse_utility.*(.rodata .rodata.*) + ARCHLIB:*brownout.*(.rodata .rodata.*) + ARCHLIB:*cpu.*(.rodata .rodata.*) + ARCHLIB:*gpio_hal.*(.rodata .rodata.*) + ARCHLIB:*periph_ctrl.*(.rodata .rodata.*) + ARCHLIB:*clk.*(.rodata .rodata.*) + ARCHLIB:*esp_clk.*(.rodata .rodata.*) + ARCHLIB:*esp_clk_tree.*(.rodata .rodata.*) + ARCHLIB:*esp_clk_tree_common.*(.rodata .rodata.*) + ARCHLIB:*clk_tree_hal.*(.rodata .rodata.*) + ARCHLIB:*rtc_init.*(.rodata .rodata.*) + ARCHLIB:*rtc_clk.*(.rodata .rodata.*) + ARCHLIB:*rtc_clk_init.*(.rodata .rodata.*) + ARCHLIB:*rtc_sleep.*(.rodata .rodata.*) + ARCHLIB:*rtc_time.*(.rodata .rodata.*) + ARCHLIB:*regi2c_ctrl.*(.rodata .rodata.*) + ARCHLIB:*uart_hal_iram.*(.rodata .rodata.*) + ARCHLIB:*wdt_hal_iram.*(.rodata .rodata.*) + ARCHLIB:*bootloader_banner_wrap.*(.rodata .rodata.*) + ARCHLIB:*bootloader_init.*(.rodata .rodata.*) + ARCHLIB:*bootloader_common.*(.rodata .rodata.*) + ARCHLIB:*bootloader_common_loader.*(.rodata .rodata.*) + ARCHLIB:*bootloader_console.*(.rodata .rodata.*) + ARCHLIB:*bootloader_console_loader.*(.rodata .rodata.*) + ARCHLIB:*bootloader_esp32s3.*(.rodata .rodata.*) + ARCHLIB:*bootloader_flash.*(.rodata .rodata.*) + ARCHLIB:*bootloader_flash_config_esp32s3.*(.rodata .rodata.*) + ARCHLIB:*bootloader_clock_init.*(.rodata .rodata.*) + ARCHLIB:*bootloader_clock_loader.*(.rodata .rodata.*) + ARCHLIB:*bootloader_efuse.*(.rodata .rodata.*) + ARCHLIB:*bootloader_panic.*(.rodata .rodata.*) + ARCHLIB:*bootloader_mem.*(.rodata .rodata.*) + ARCHLIB:*bootloader_random.*(.rodata .rodata.*) + ARCHLIB:*bootloader_random_esp32s3.*(.rodata .rodata.*) + ARCHLIB:*esp_image_format.*(.rodata .rodata.*) + ARCHLIB:*bootloader_soc.*(.rodata .rodata.*) + ARCHLIB:*bootloader_sha.*(.rodata .rodata.*) + ARCHLIB:*flash_encrypt.*(.rodata .rodata.*) + ARCHLIB:*cache_hal.*(.rodata .rodata.*) + ARCHLIB:*uart_hal.*(.rodata .rodata.*) + ARCHLIB:*mpu_hal.*(.rodata .rodata.*) + ARCHLIB:*mmu_hal.*(.rodata .rodata.*) + ARCHLIB:*uart_periph.*(.rodata .rodata.*) + ARCHLIB:*esp_rom_uart.*(.rodata .rodata.*) + ARCHLIB:*esp_rom_sys.*(.rodata .rodata.*) + ARCHLIB:*esp_rom_spiflash.*(.rodata .rodata.*) + ARCHLIB:*esp_rom_cache_esp32s2_esp32s3.*(.rodata .rodata.*) + ARCHLIB:*esp_rom_wdt.*(.rodata .rodata.*) + ARCHLIB:*efuse_hal.*(.rodata .rodata.*) + ARCHLIB:*log.*(.rodata .rodata.*) + ARCHLIB:*log_noos.*(.rodata .rodata.*) + ARCHLIB:*cpu_region_protect.*(.rodata .rodata.*) + ARCHLIB:*mspi_timing_tuning.*(.rodata .rodata.*) + ARCHLIB:*mspi_timing_config.*(.rodata .rodata.*) #ifdef CONFIG_ESP32S3_SPIRAM_MODE_QUAD - *libarch.a:esp32s3_psram_quad.*(.rodata .rodata.*) + ARCHLIB:esp32s3_psram_quad.*(.rodata .rodata.*) #endif #ifdef CONFIG_ESP32S3_SPIRAM_MODE_OCT - *libarch.a:esp32s3_psram_octal.*(.rodata .rodata.*) + ARCHLIB:esp32s3_psram_octal.*(.rodata .rodata.*) #endif - *libarch.a:*flash_qio_mode.*(.rodata .rodata.*) - *libarch.a:*spi_flash_wrap.*(.rodata .rodata.*) - *libarch.a:*spi_flash_oct_flash_init.*(.rodata .rodata.*) - *libarch.a:*spi_flash_hpm_enable.*(.rodata .rodata.*) - *libarch.a:esp_flash_api.*(.rodata .rodata.*) - *libarch.a:esp_flash_spi_init.*(.rodata .rodata.*) - *libarch.a:spi_flash_hal_iram.*(.rodata .rodata.*) - *libarch.a:spi_flash_encrypt_hal_iram.*(.rodata .rodata.*) - *libarch.a:spi_flash_hal_gpspi.*(.rodata .rodata.*) - *libarch.a:spi_flash_chip*.*(.rodata .rodata.*) - *libarch.a:spi_flash_wrap.*(.rodata .rodata.*) - *libarch.a:spi_flash_os_func_noos.*(.rodata .rodata.*) - *libarch.a:spi_flash_os_func_app.*(.rodata .rodata.*) - *libarch.a:flash_brownout_hook.*(.rodata .rodata.*) - *libarch.a:esp_cache_utils.*(.rodata .rodata.*) - *libarch.a:cache_utils.*(.rodata .rodata.*) - *libarch.a:memspi_host_driver.*(.rodata .rodata.*) - *libarch.a:critical_section.*(.rodata .rodata.*) - *libarch.a:os.*(.rodata.g_int_flags_count .rodata.g_int_flags) - *libarch.a:*sleep_modes.*(.rodata.esp_sleep_sub_mode_force_disable*) + ARCHLIB:*flash_qio_mode.*(.rodata .rodata.*) + ARCHLIB:*spi_flash_wrap.*(.rodata .rodata.*) + ARCHLIB:*spi_flash_oct_flash_init.*(.rodata .rodata.*) + ARCHLIB:*spi_flash_hpm_enable.*(.rodata .rodata.*) + ARCHLIB:esp_flash_api.*(.rodata .rodata.*) + ARCHLIB:esp_flash_spi_init.*(.rodata .rodata.*) + ARCHLIB:spi_flash_hal_iram.*(.rodata .rodata.*) + ARCHLIB:spi_flash_encrypt_hal_iram.*(.rodata .rodata.*) + ARCHLIB:spi_flash_hal_gpspi.*(.rodata .rodata.*) + ARCHLIB:spi_flash_chip*.*(.rodata .rodata.*) + ARCHLIB:spi_flash_wrap.*(.rodata .rodata.*) + ARCHLIB:spi_flash_os_func_noos.*(.rodata .rodata.*) + ARCHLIB:spi_flash_os_func_app.*(.rodata .rodata.*) + ARCHLIB:flash_brownout_hook.*(.rodata .rodata.*) + ARCHLIB:esp_cache_utils.*(.rodata .rodata.*) + ARCHLIB:cache_utils.*(.rodata .rodata.*) + ARCHLIB:memspi_host_driver.*(.rodata .rodata.*) + ARCHLIB:critical_section.*(.rodata .rodata.*) + ARCHLIB:os.*(.rodata.g_int_flags_count .rodata.g_int_flags) + ARCHLIB:*sleep_modes.*(.rodata.esp_sleep_sub_mode_force_disable*) *libc.a:arch_atomic.*(.rodata .rodata.*) @@ -673,15 +686,15 @@ SECTIONS _srodata = ABSOLUTE(.); *(EXCLUDE_FILE (esp32s3_start.* esp32s3_region.* - *libarch.a:*esp_loader.* - *libarch.a:esp32s3_spiflash.* - *libarch.a:*cache_hal.* *libarch.a:*mmu_hal.* - *libarch.a:*mpu_hal.*) .rodata) + ARCHLIB:*esp_loader.* + ARCHLIB:esp32s3_spiflash.* + ARCHLIB:*cache_hal.* ARCHLIB:*mmu_hal.* + ARCHLIB:*mpu_hal.*) .rodata) *(EXCLUDE_FILE (esp32s3_start.* esp32s3_region.* - *libarch.a:*esp_loader.* - *libarch.a:esp32s3_spiflash.* - *libarch.a:*cache_hal.* *libarch.a:*mmu_hal.* - *libarch.a:*mpu_hal.*) .rodata.*) + ARCHLIB:*esp_loader.* + ARCHLIB:esp32s3_spiflash.* + ARCHLIB:*cache_hal.* ARCHLIB:*mmu_hal.* + ARCHLIB:*mpu_hal.*) .rodata.*) #ifdef CONFIG_ESPRESSIF_WIRELESS *(.rodata_wlog_verbose.*) diff --git a/boards/xtensa/esp32s3/common/scripts/legacy_sections.ld b/boards/xtensa/esp32s3/common/scripts/legacy_sections.ld index cdff5755c48c5..26d4a0890a7bd 100644 --- a/boards/xtensa/esp32s3/common/scripts/legacy_sections.ld +++ b/boards/xtensa/esp32s3/common/scripts/legacy_sections.ld @@ -22,6 +22,19 @@ #include +/* A kernel build archives the architecture objects into libkarch.a rather + * than libarch.a, so the placement rules below have to name the right one. + * Getting this wrong is silent and fatal: the octal-flash bring-up functions + * stay in mapped flash and are called while that mapping is being + * reconfigured. + */ + +#ifdef CONFIG_BUILD_KERNEL +# define ARCHLIB *libkarch.a +#else +# define ARCHLIB *libarch.a +#endif + /* Default entry point: */ ENTRY(__start); @@ -78,19 +91,19 @@ SECTIONS *(.iram1 .iram1.*) - *libarch.a:esp32s3_cpuindex.*(.literal .text .literal.* .text.*) - *libarch.a:esp32s3_user.*(.literal .text .literal.* .text.*) - *libarch.a:esp32s3_spiflash.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_assert.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_cpuint.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_cpupause.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_irqdispatch.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_modifyreg32.*(.literal .text .literal.* .text.*) - *libarch.a:xtensa_testset.*(.literal .text .literal.* .text.*) - *libarch.a:esp_app_desc.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_cpuindex.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_user.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_spiflash.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_assert.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_cpuint.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_cpupause.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_irqdispatch.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_modifyreg32.*(.literal .text .literal.* .text.*) + ARCHLIB:xtensa_testset.*(.literal .text .literal.* .text.*) + ARCHLIB:esp_app_desc.*(.literal .text .literal.* .text.*) - *libarch.a:*esp_rom_spiflash.*(.literal .text .literal.* .text.*) - *libarch.a:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) + ARCHLIB:*esp_rom_spiflash.*(.literal .text .literal.* .text.*) + ARCHLIB:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) *libc.a:sq_remlast.*(.literal .text .literal.* .text.*) @@ -112,11 +125,11 @@ SECTIONS *libc.a:arch_atomic.*(.literal .text .literal.* .text.*) #ifdef CONFIG_ESP32S3_SPEED_UP_ISR - *libarch.a:xtensa_switchcontext.*(.literal.up_switch_context .text.up_switch_context) + ARCHLIB:xtensa_switchcontext.*(.literal.up_switch_context .text.up_switch_context) - *libarch.a:esp32s3_timerisr.*(.literal.systimer_isr .text.systimer_isr) - *libarch.a:esp32s3_idle.*(.literal.up_idle .text.up_idle) - *libarch.a:esp32s3_dma.*(.literal.esp32s3_dma_load .text.esp32s3_dma_load \ + ARCHLIB:esp32s3_timerisr.*(.literal.systimer_isr .text.systimer_isr) + ARCHLIB:esp32s3_idle.*(.literal.up_idle .text.up_idle) + ARCHLIB:esp32s3_dma.*(.literal.esp32s3_dma_load .text.esp32s3_dma_load \ .literal.esp32s3_dma_enable .text.esp32s3_dma_enable) *libsched.a:sched_processtimer.*(.literal.nxsched_process_timer .text.nxsched_process_timer) @@ -132,33 +145,33 @@ SECTIONS *libc.a:sq_remfirst.*(.literal.sq_remfirst .text.sq_remfirst) #endif - *libarch.a:esp32s3_spi_timing.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_spi_timing.*(.literal .text .literal.* .text.*) #ifdef CONFIG_ESP32S3_SPIRAM_MODE_QUAD - *libarch.a:esp32s3_psram_quad.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_psram_quad.*(.literal .text .literal.* .text.*) #endif #ifdef CONFIG_ESP32S3_SPIRAM_MODE_OCT - *libarch.a:esp32s3_psram_octal.*(.literal .text .literal.* .text.*) + ARCHLIB:esp32s3_psram_octal.*(.literal .text .literal.* .text.*) #endif #if defined(CONFIG_STACK_CANARIES) && \ (defined(CONFIG_ESP32S3_SPIFLASH) || \ defined(CONFIG_ESP32S3_SPIRAM)) *libc.a:lib_stackchk.*(.literal .text .literal.* .text.*) #endif - *libarch.a:*cache_hal.*(.text .text.* .literal .literal.*) - *libarch.a:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) - *libarch.a:esp_cache.*(.literal .literal.* .text .text.*) - *libarch.a:cache_utils.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_hal_iram.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_hal_gpspi.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_encrypt_hal_iram.*(.text .text.* .literal .literal.*) - *libarch.a:spi_flash_chip*.*(.literal .literal.* .text .text.*) - *libarch.a:spi_flash_wrap.*(.literal .literal.* .text .text.*) - *libarch.a:esp_spiflash.*(.literal .text .literal.* .text.*) - *libarch.a:esp_flash_spi_init.*(.text .text.* .literal .literal.*) - *libarch.a:esp_flash_api.*(.text .text.* .literal .literal.*) - *libarch.a:spi_flash_os_func*.*(.literal .literal.* .text .text.*) - *libarch.a:*flash_brownout_hook.*(.literal .literal.* .text .text.*) - *libarch.a:memspi_host_driver.*(.literal .literal.* .text .text.*) + ARCHLIB:*cache_hal.*(.text .text.* .literal .literal.*) + ARCHLIB:*esp_rom_cache_esp32s2_esp32s3.*(.literal .text .literal.* .text.*) + ARCHLIB:esp_cache.*(.literal .literal.* .text .text.*) + ARCHLIB:cache_utils.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_hal_iram.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_hal_gpspi.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_encrypt_hal_iram.*(.text .text.* .literal .literal.*) + ARCHLIB:spi_flash_chip*.*(.literal .literal.* .text .text.*) + ARCHLIB:spi_flash_wrap.*(.literal .literal.* .text .text.*) + ARCHLIB:esp_spiflash.*(.literal .text .literal.* .text.*) + ARCHLIB:esp_flash_spi_init.*(.text .text.* .literal .literal.*) + ARCHLIB:esp_flash_api.*(.text .text.* .literal .literal.*) + ARCHLIB:spi_flash_os_func*.*(.literal .literal.* .text .text.*) + ARCHLIB:*flash_brownout_hook.*(.literal .literal.* .text .text.*) + ARCHLIB:memspi_host_driver.*(.literal .literal.* .text .text.*) *(.wifirxiram .wifirxiram.*) *(.wifi0iram .wifi0iram.*) @@ -242,27 +255,27 @@ SECTIONS *(.dram1 .dram1.*) *libphy.a:(.rodata .rodata.*) - *libarch.a:xtensa_context.*(.rodata .rodata.*) - *libarch.a:*cache_hal.*(.rodata .rodata.*) + ARCHLIB:xtensa_context.*(.rodata .rodata.*) + ARCHLIB:*cache_hal.*(.rodata .rodata.*) #if defined(CONFIG_STACK_CANARIES) && \ (defined(CONFIG_ESP32S3_SPIFLASH) || \ defined(CONFIG_ESP32S3_SPIRAM)) *libc.a:lib_stackchk.*(.rodata .rodata.*) #endif - *libarch.a:esp_cache.*(.rodata .rodata.*) - *libarch.a:cache_utils.*(.rodata .rodata.*) - *libarch.a:spi_flash_hal_iram.*(.rodata .rodata.*) - *libarch.a:spi_flash_hal_gpspi.*(.rodata .rodata.*) - *libarch.a:spi_flash_encrypt_hal_iram.*(.rodata .rodata.*) - *libarch.a:spi_flash_chip*.*(.rodata .rodata.*) - *libarch.a:spi_flash_wrap.*(.rodata .rodata.*) - *libarch.a:esp_spiflash.*(.rodata .rodata.*) - *libarch.a:esp_flash_spi_init.*(.rodata .rodata.*) - *libarch.a:esp_flash_api.*(.rodata .rodata.*) - *libarch.a:spi_flash_os_func*.*(.rodata .rodata.*) - *libarch.a:flash_brownout_hook.*(.rodata .rodata.*) - *libarch.a:memspi_host_driver.*(.rodata .rodata.*) + ARCHLIB:esp_cache.*(.rodata .rodata.*) + ARCHLIB:cache_utils.*(.rodata .rodata.*) + ARCHLIB:spi_flash_hal_iram.*(.rodata .rodata.*) + ARCHLIB:spi_flash_hal_gpspi.*(.rodata .rodata.*) + ARCHLIB:spi_flash_encrypt_hal_iram.*(.rodata .rodata.*) + ARCHLIB:spi_flash_chip*.*(.rodata .rodata.*) + ARCHLIB:spi_flash_wrap.*(.rodata .rodata.*) + ARCHLIB:esp_spiflash.*(.rodata .rodata.*) + ARCHLIB:esp_flash_spi_init.*(.rodata .rodata.*) + ARCHLIB:esp_flash_api.*(.rodata .rodata.*) + ARCHLIB:spi_flash_os_func*.*(.rodata .rodata.*) + ARCHLIB:flash_brownout_hook.*(.rodata .rodata.*) + ARCHLIB:memspi_host_driver.*(.rodata .rodata.*) *libc.a:arch_atomic.*(.rodata .rodata.*) _edata = ABSOLUTE(.); . = ALIGN(4); From e00232c16fa6c5b07b4545af0e82118f81288fa3 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 14:16:04 +0200 Subject: [PATCH 14/22] 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 Signed-off-by: Marco Casaroli Assisted-by: Claude Opus 5 (1M context) --- arch/xtensa/src/common/espressif/esp_irq.c | 4 +- arch/xtensa/src/esp32s3/Make.defs | 11 + arch/xtensa/src/esp32s3/esp32s3_isolation.c | 599 ++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_isolation.h | 116 ++++ arch/xtensa/src/esp32s3/esp32s3_pms.c | 149 ++++- arch/xtensa/src/esp32s3/esp32s3_pms.h | 24 + arch/xtensa/src/esp32s3/esp32s3_start.c | 14 + arch/xtensa/src/esp32s3/esp32s3_userspace.c | 156 +---- arch/xtensa/src/esp32s3/esp32s3_userspace.h | 20 - .../src/esp32s3/esp32s3_world1_vectors.S | 221 +++++++ .../configs/kernel_oct/defconfig | 1 + 11 files changed, 1119 insertions(+), 196 deletions(-) create mode 100644 arch/xtensa/src/esp32s3/esp32s3_isolation.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_isolation.h create mode 100644 arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S diff --git a/arch/xtensa/src/common/espressif/esp_irq.c b/arch/xtensa/src/common/espressif/esp_irq.c index 403bfe99a83bc..5117849de4b7c 100644 --- a/arch/xtensa/src/common/espressif/esp_irq.c +++ b/arch/xtensa/src/common/espressif/esp_irq.c @@ -81,7 +81,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 @@ -106,7 +106,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 6fd8e8087fbab..574a99a599ff2 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 0000000000000..5dcf576f1f19e --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -0,0 +1,599 @@ +/**************************************************************************** + * 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 + +#include + +#include +#include +#include + +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#include +#include +#include +#endif + +#include +#include + +#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 + +#ifndef CONFIG_BUILD_FLAT + +/**************************************************************************** + * 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; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_isolation_revoke_peripherals + ****************************************************************************/ + +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 + ****************************************************************************/ + +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: 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); +} + +/**************************************************************************** + * Name: esp32s3_isolation_permissions + ****************************************************************************/ + +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 + ****************************************************************************/ + +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_FLAT */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.h b/arch/xtensa/src/esp32s3/esp32s3_isolation.h new file mode 100644 index 0000000000000..5b0be7abf7b6c --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.h @@ -0,0 +1,116 @@ +/**************************************************************************** + * 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 + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ISOLATION_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_ISOLATION_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * 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_isolation_permissions + * + * Description: + * 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. + * + ****************************************************************************/ + +void esp32s3_isolation_permissions(void); +#endif + +/**************************************************************************** + * Name: esp32s3_pmsirqinitialize + * + * Description: + * Initialize interrupt handler for the PMS violation ISR. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void esp32s3_pmsirqinitialize(void); + +#else +# define esp32s3_pmsirqinitialize() +#endif /* !CONFIG_BUILD_FLAT */ + +#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 e68c284252a5e..2302c984d19c8 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pms.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pms.c @@ -471,32 +471,33 @@ void esp32s3_pms_configure_dram_region(enum pms_area_e area, * Name: esp32s3_pms_configure_flash_cache_region ****************************************************************************/ +/**************************************************************************** + * 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,6 +529,114 @@ 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 ****************************************************************************/ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.h b/arch/xtensa/src/esp32s3/esp32s3_pms.h index fe4bb7cc787c6..2afec694c0b4d 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 ecf6d42bdceca..4e99f62813761 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 4f9731bd667d1..f491f2b76203e 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" @@ -295,31 +295,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 * @@ -615,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 * @@ -765,7 +644,7 @@ static void configure_mpu(void) * peripherals. */ - pms_configure_peripheral_access(); + esp32s3_isolation_revoke_peripherals(); } /**************************************************************************** @@ -812,35 +691,4 @@ 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 0deff1d0f5c7c..2496042ed2a64 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_world1_vectors.S b/arch/xtensa/src/esp32s3/esp32s3_world1_vectors.S new file mode 100644 index 0000000000000..e6dcb31cc18d7 --- /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 + +#include +#include +#include +#include + +/**************************************************************************** + * 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 5e11bbe7726e4..4f967a69e4995 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 From 4b28fc26e79b59a0d4b232bbd663319be0b0ee32 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sat, 25 Jul 2026 00:14:05 +0200 Subject: [PATCH 15/22] xtensa/esp32s3: abort the faulting user task on an unrecoverable fault When an unprivileged (WORLD1) task takes an unrecoverable cache-attribute fault (Load/Store/InstrFetch Prohibited), deliver a fatal SIGSEGV to just that task instead of panicking the whole system; kernel-mode faults still panic. Gated by CONFIG_ESP32S3_PAGEFAULT_ABORT (default y under ESP32S3_PAGEFAULT), which selects SIG_DEFAULT + SIG_SIGKILL_ACTION so the signal's default action terminates the task. esp32s3_pagefault_abort() mirrors the interrupt-dispatch handshake: it records the exception frame as the task context, dispatches SIGSEGV (which redirects the task to the signal trampoline via up_schedule_sigaction()), and returns the redirected frame so the vector's RFE resumes the task in the trampoline, whose default action _exit()s it and reschedules. No kernel stack is required, so this works on the existing protected configs. Verified on the ESP32-S3-DevKitC WROOM-2: - "pffault r 0x0" / "pffault w 0x0" (NULL read/write, EXCCAUSE 28/29) terminate only the pffault task; nsh stays interactive. - Repeatable with no memory leak (free unchanged after 8 aborts) and no zombie tasks (ps shows none lingering). - No regression: ostest exits with status 0 and the RFE-restart self-test still passes. Assisted-by: Claude Opus 4.8 (1M context) (cherry picked from commit b160d7ad844aed8589b1c3cb399252c44284cf6a) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/Kconfig | 11 +++++ arch/xtensa/src/esp32s3/esp32s3_pagefault.c | 55 +++++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_pagefault.h | 14 ++++++ arch/xtensa/src/esp32s3/esp32s3_user.c | 13 +++++ 4 files changed, 93 insertions(+) diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index b0b392d3b99b6..a89d6f2fb7e19 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -958,6 +958,17 @@ config ESP32S3_PAGEFAULT if ESP32S3_PAGEFAULT +config ESP32S3_PAGEFAULT_ABORT + bool "Abort the faulting task instead of panicking" + default y + select SIG_DEFAULT + select SIG_SIGKILL_ACTION + ---help--- + When an unprivileged (WORLD1) task takes an unrecoverable + cache-attribute fault, deliver SIGSEGV to it so its default action + terminates only that task and the rest of the system keeps running, + instead of a whole-system panic. Kernel-mode faults still panic. + config ESP32S3_PAGEFAULT_SELFTEST bool "Recoverable-fault self-test" default n diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c index e9f9c64472f28..5c230f69f437e 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c @@ -29,12 +29,20 @@ #include #include +#include #include #include #include +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#include +#endif + #include "xtensa.h" #include "sched/sched.h" +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#include "signal/signal.h" +#endif /**************************************************************************** * Pre-processor Definitions @@ -133,3 +141,50 @@ int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs) return -EFAULT; } + +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +/**************************************************************************** + * Name: esp32s3_pagefault_abort + * + * Description: + * Terminate just the faulting unprivileged (WORLD1) task by delivering a + * fatal SIGSEGV, instead of panicking the whole system. Called from + * xtensa_user() for an unrecoverable user-mode cache-attribute fault. + * + * Mirrors the interrupt-dispatch handshake: record the exception frame as + * the task context, dispatch the signal -- which redirects the task to the + * signal trampoline via up_schedule_sigaction() -- then return the + * redirected frame so the vector's RFE resumes the task in the trampoline, + * whose SIGSEGV default action (_exit) tears the task down and resumes. + * + * Returned Value: + * The register frame to resume (the signal trampoline for the faulting + * task). + * + ****************************************************************************/ + +uint32_t *esp32s3_pagefault_abort(int exccause, uint32_t *regs) +{ + struct tcb_s *tcb = this_task(); + siginfo_t info; + + _alert("SIGSEGV task %s: EXCCAUSE=%d EXCVADDR=%08x PC=%08x\n", + get_task_name(tcb), exccause, (unsigned)regs[REG_EXCVADDR], + (unsigned)regs[REG_PC]); + + up_set_interrupt_context(true); + tcb->xcp.regs = regs; + + info.si_signo = SIGSEGV; + info.si_code = SI_USER; + info.si_errno = 0; + info.si_value.sival_ptr = (FAR void *)regs[REG_EXCVADDR]; + + nxsig_tcbdispatch(tcb, &info, false); + + regs = tcb->xcp.regs; + tcb->xcp.regs = NULL; + up_set_interrupt_context(false); + return regs; +} +#endif /* CONFIG_ESP32S3_PAGEFAULT_ABORT */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.h b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h index 13ed1dbbcf388..0b6943e509086 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.h +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h @@ -59,4 +59,18 @@ int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs); +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +/**************************************************************************** + * Name: esp32s3_pagefault_abort + * + * Description: + * Terminate just the faulting unprivileged (WORLD1) task via a fatal + * SIGSEGV instead of panicking the whole system. Returns the register + * frame to resume (the signal trampoline for the faulting task). + * + ****************************************************************************/ + +uint32_t *esp32s3_pagefault_abort(int exccause, uint32_t *regs); +#endif + #endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PAGEFAULT_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index 88050c5eb8976..95a097ebaaa99 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -98,6 +98,19 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) { return regs; } + +#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT + /* Unrecoverable, but a WORLD1 (user-mode) fault need not take down the + * whole system: terminate just the faulting task with SIGSEGV. The + * User Mode bit in the interruptee's saved PS distinguishes a user + * fault from a privileged one (which still panics below). + */ + + if ((regs[REG_PS] & PS_UM) != 0) + { + return esp32s3_pagefault_abort(exccause, regs); + } +#endif } #endif From bd0806a73954cbb1524f51ef6e09ed9e3e200487 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 28 Jul 2026 19:54:05 +0200 Subject: [PATCH 16/22] xtensa/esp32s3: contain an illegal instruction taken in user mode A denied external-memory access does not trap on this chip. TRM v1.8 p.699: an access without permission is "responded with 0 (for internal memory) or 0xdeadbeaf (for external memory)". So when an unprivileged task branches into kernel text -- in flash or in PSRAM -- the fetch is refused silently, the CPU executes the dummy word it was handed, and the refusal arrives at xtensa_user() as EXCCAUSE_ILLEGAL at the address that was branched to. It never arrives as EXCCAUSE_INSTR_PROHIBITED, which is what the existing recoverable path looks for. The result was that a correctly refused fetch panicked the system. Killing just the offender is the right answer however the illegal instruction arose -- a refused fetch or simply a corrupt user binary -- because a user task running garbage must not take the system down with it. It is not offered to esp32s3_pagefault_dispatch() first: re-executing cannot help, the instruction genuinely is not there. The User Mode bit in the interruptee's saved PS keeps this to unprivileged faults; an illegal instruction in the kernel still panics. Verified on an ESP32-S3 DevKitC with a WROOM-2 module (octal flash, 8 MB octal PSRAM), esp32s3-devkit:kernel_oct. examples/sandbox aimed at real kernel .text, both modes, at the same address: sandbox r 0x42011014 -> pms_violation_isr: SIGSEGV (PMS) sandbox x 0x42011014 -> esp32s3_pagefault_abort: SIGSEGV EXCCAUSE=0 held (read,call): 2 of 2 Before this commit the execute probe produced xtensa_user_panic and a full crash dump. Note the probe must be given an explicit address: this board's CONFIG_RAM_START is 0x20000000, which is not a kernel region at all. Signed-off-by: Marco Casaroli Assisted-by: Claude Opus 5 (1M context) (cherry picked from commit 08304b4f6f28a8a57579b2a054d1b3d391564b09) --- arch/xtensa/src/esp32s3/esp32s3_user.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index 95a097ebaaa99..4bd032129d7ab 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -114,6 +114,28 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) } #endif +#if defined(CONFIG_ESP32S3_PAGEFAULT) && defined(CONFIG_ESP32S3_PAGEFAULT_ABORT) + /* A *denied* external-memory access does not trap on this chip. TRM v1.8 + * p.699: an access without permission is "responded with 0 (for internal + * memory) or 0xdeadbeaf (for external memory)". So an unprivileged branch + * into kernel text in flash or PSRAM is refused silently, the CPU executes + * the dummy word it was handed instead, and the refusal surfaces here as + * EXCCAUSE_ILLEGAL at the address that was branched to -- never as + * EXCCAUSE_INSTR_PROHIBITED. + * + * Re-executing cannot help: the instruction genuinely is not there, so + * this is not offered to the dispatcher above. Terminate just the task. + * That is the right answer whichever way the illegal instruction arose -- + * a refused fetch or simply a corrupt user binary -- because a user task + * running garbage must not take the system down with it. + */ + + if (exccause == EXCCAUSE_ILLEGAL && (regs[REG_PS] & PS_UM) != 0) + { + return esp32s3_pagefault_abort(exccause, regs); + } +#endif + /* xtensa_user_panic never returns. */ xtensa_user_panic(exccause, regs); From 8dc85fb60e7a9812923d4620f31b68a3cdad78da Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 28 Jul 2026 23:57:28 +0200 Subject: [PATCH 17/22] xtensa/esp32s3: stop an unreportable cache fault from livelocking Reporting a fault can itself fault. syslog reaches memory that the very fault being reported may have made unreachable, so esp32s3_pagefault_dispatch() is re-entered from inside its own _alert() and never returns. The console fills with the same line severed part-way through EXCVADDR, forever, and nothing legible ever reaches it. Found by running esp32s3-devkit:kernel_oct under Espressif's QEMU, where PSRAM never initialises and the kernel build needs it -- the pgalloc pool that backs every user process lives there. 13 MB of half-printed lines in 60 s, no NSH, no way to see what went wrong. Hardware does not show this: the board's PSRAM works, so the path is never taken. Bound it. A fault repeating at the same address and PC is not going to be helped by reporting it again, so try three times and then halt with interrupts off. The lines may still be truncated -- the print is what faults, so it cannot be made to complete from here -- but a handful of severed lines followed by silence is diagnosable, and an endless stream of them is not. The counter is cleared in esp32s3_pagefault_abort(). Reaching there means the fault was contained and the system carried on, so only *unbroken* recursion should stop the machine; without the reset, a probe run three times at one address would trip the guard and halt a perfectly healthy board. That is the case verified on hardware below, and it is the reason the reset exists. Verified both ways. Under QEMU, where the report does fault: before: 12,958,521 bytes in 60 s, unbounded after: 1,567 bytes in 45 s, four reports then halt On an ESP32-S3 DevKitC with a WROOM-2 module, esp32s3-devkit:kernel_oct, three identical probes in a single boot -- one "Booting NuttX", no reset: sandbox r 0x42011014 x3 -> CONTAINED - the sandbox held (3 of 3) Signed-off-by: Marco Casaroli Assisted-by: Claude Opus 5 (1M context) (cherry picked from commit 222003254e4e0793e5593ef91ba3b2b2db1e14d4) --- arch/xtensa/src/esp32s3/esp32s3_pagefault.c | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c index 5c230f69f437e..8830e9f651a3f 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c @@ -72,6 +72,18 @@ static volatile int g_pf_selftest_hits; #endif +/* How many times to let the same fault be reported before giving up on + * reporting it. Note the report may not survive even once -- see the + * comment in the dispatcher -- so this bounds the damage rather than + * guaranteeing a legible message. + */ + +#define PF_REPEAT_LIMIT 3 + +static uintptr_t g_pf_last_vaddr; +static uintptr_t g_pf_last_pc; +static int g_pf_repeats; + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -131,6 +143,36 @@ int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs) } #endif + /* Reporting a fault can itself fault. syslog reaches memory that the + * very fault being reported may have made unreachable -- PSRAM that never + * initialised, say -- and then this handler is re-entered from inside its + * own _alert(). The console fills with the same line severed part-way + * through EXCVADDR, forever, and nothing legible ever reaches it. + * + * A fault repeating at the same address and PC is not going to be helped + * by reporting it again. Try a few times, then stop and halt. The lines + * may still be truncated -- the print is what faults, so it cannot be made + * to complete from here -- but a handful of severed lines followed by + * silence is diagnosable, and an endless stream of them is not. + */ + + if (vaddr == g_pf_last_vaddr && pc == g_pf_last_pc) + { + if (++g_pf_repeats >= PF_REPEAT_LIMIT) + { + up_irq_save(); + for (; ; ) + { + } + } + } + else + { + g_pf_last_vaddr = vaddr; + g_pf_last_pc = pc; + g_pf_repeats = 0; + } + /* Report the precise fault (with its tracking EXCVADDR) and decline to * service it, so the caller falls through to the panic / abort path. */ @@ -168,6 +210,17 @@ uint32_t *esp32s3_pagefault_abort(int exccause, uint32_t *regs) struct tcb_s *tcb = this_task(); siginfo_t info; + /* Reaching here means the fault was contained and the system carried on, + * so the repeat counter has served its purpose. Clear it, or a probe run + * three times at one address would trip the guard in the dispatcher and + * halt a perfectly healthy system. Only *unbroken* recursion -- a report + * that faults before the abort can happen -- should stop the machine. + */ + + g_pf_last_vaddr = 0; + g_pf_last_pc = 0; + g_pf_repeats = 0; + _alert("SIGSEGV task %s: EXCCAUSE=%d EXCVADDR=%08x PC=%08x\n", get_task_name(tcb), exccause, (unsigned)regs[REG_EXCVADDR], (unsigned)regs[REG_PC]); From c2a9f04fcaf19e5cae7bb952eebe9260d02d5b1f Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 30 Jul 2026 09:29:43 +0200 Subject: [PATCH 18/22] xtensa/esp32s3: contain any user fault, not a list of causes The user-exception handler recovered from three causes -- Load, Store and InstrFetch Prohibited -- plus an illegal instruction, and panicked on everything else. Gating on a list of causes means every cause left off the list is a way for an unprivileged task to stop the machine, and a task has plenty: a divide by zero (EXCCAUSE 6), a privileged instruction (8), a load/store error (3), an instruction-fetch error (2), a data access to an address the bus will not carry (9). None of these involve a boundary at all; they are things a correctly confined application does to itself. Measured on an ESP32-S3-WROOM-2, kernel build, before this change: a user task dividing by zero takes the whole system down, silently. So gate on the interruptee instead. The dispatcher still gets first refusal on 28/29/20, which are the precise, restartable causes and the only ones re-executing can help. Anything it does not service now goes to the abort path if the saved PS says the fault was taken in User Mode. That bit is also what excludes the cases with no safe task to kill: a kernel thread, a fault inside a system call made on the user's behalf, and a fault while handling an interrupt all run with PS.UM clear and still panic. This is the same shape riscv_fault_handler() has, where the cause is used for the message only. The abort itself moves to esp32s3_userfault.c and stops depending on the recoverable-fault dispatcher. CONFIG_ESP32S3_PAGEFAULT_ABORT was a child of CONFIG_ESP32S3_PAGEFAULT, which is default n, so "do not let a rogue task halt the machine" was unreachable unless you also opted into "retry the faulting instruction". Those are separate capabilities and the safety one is the one you always want, so it becomes CONFIG_ESP32S3_USERFAULT_ABORT, default y wherever there is an unprivileged world. A BUILD_PROTECTED configuration previously got neither. Verified on hardware with the new examples/misbehave. A user task that writes through NULL (29), reads a wild address (28), divides by zero (6) or calls into a buffer of garbage (20) is terminated on its own, with an unrelated task still counting either side of it. Two limits worth recording rather than hiding: * Stack overflow is not contained and does not even report -- the console goes silent. On a windowed ABI the overflow faults inside the window overflow handler, so it arrives as a double exception with PS.UM already clear and no gate can see it. Guard pages are the answer and are what CONFIG_ESP32S3_PAGEFAULT exists to make possible; that is separate work. * A 32-bit load from an unaligned address does not trap on this core, so EXCCAUSE 9 is reachable by other means but not that one. Signed-off-by: Marco Casaroli Assisted-by: Claude Opus 5 (1M context) (cherry picked from commit 3c11f3793dbae918dc6ebe3e99cbb60f70648b3e) --- arch/xtensa/src/esp32s3/Kconfig | 26 +++-- arch/xtensa/src/esp32s3/Make.defs | 8 ++ arch/xtensa/src/esp32s3/esp32s3_isolation.c | 8 +- arch/xtensa/src/esp32s3/esp32s3_pagefault.c | 58 +--------- arch/xtensa/src/esp32s3/esp32s3_pagefault.h | 11 +- arch/xtensa/src/esp32s3/esp32s3_user.c | 55 +++++---- arch/xtensa/src/esp32s3/esp32s3_userfault.c | 117 ++++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_userfault.h | 59 ++++++++++ 8 files changed, 242 insertions(+), 100 deletions(-) create mode 100644 arch/xtensa/src/esp32s3/esp32s3_userfault.c create mode 100644 arch/xtensa/src/esp32s3/esp32s3_userfault.h diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index a89d6f2fb7e19..0fa5ecc6ff03a 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -956,18 +956,28 @@ config ESP32S3_PAGEFAULT This is the foundation for guard pages, lazy stack/heap growth and, ultimately, demand paging / copy-on-write on the ESP32-S3. -if ESP32S3_PAGEFAULT - -config ESP32S3_PAGEFAULT_ABORT - bool "Abort the faulting task instead of panicking" +config ESP32S3_USERFAULT_ABORT + bool "Abort a faulting user task instead of panicking" default y + depends on !BUILD_FLAT select SIG_DEFAULT select SIG_SIGKILL_ACTION ---help--- - When an unprivileged (WORLD1) task takes an unrecoverable - cache-attribute fault, deliver SIGSEGV to it so its default action - terminates only that task and the rest of the system keeps running, - instead of a whole-system panic. Kernel-mode faults still panic. + When an unprivileged task takes a fault that cannot be serviced, + deliver SIGSEGV to it so its default action terminates only that task + and the rest of the system keeps running, instead of a whole-system + panic. Kernel-mode faults still panic: a kernel thread, a fault + inside a system call, and a fault while handling an interrupt all run + with PS.UM clear and there is no safe task to kill. + + This is deliberately independent of ESP32S3_PAGEFAULT. Servicing a + fault so the instruction can be re-executed and refusing to let a + rogue task halt the machine are separate capabilities, and the second + is one you always want: a user task can raise an unaligned load, a + divide by zero, a privileged instruction or a corrupt opcode, none of + which any dispatcher can service. + +if ESP32S3_PAGEFAULT config ESP32S3_PAGEFAULT_SELFTEST bool "Recoverable-fault self-test" diff --git a/arch/xtensa/src/esp32s3/Make.defs b/arch/xtensa/src/esp32s3/Make.defs index 574a99a599ff2..f299c2c6f54b4 100644 --- a/arch/xtensa/src/esp32s3/Make.defs +++ b/arch/xtensa/src/esp32s3/Make.defs @@ -50,6 +50,14 @@ ifneq ($(CONFIG_BUILD_FLAT),y) CHIP_CSRCS += esp32s3_isolation.c endif +# Terminating a faulting user task rather than panicking is independent of +# the recoverable-fault dispatcher, so it is built whenever there is an +# unprivileged world at all. + +ifeq ($(CONFIG_ESP32S3_USERFAULT_ABORT),y) +CHIP_CSRCS += esp32s3_userfault.c +endif + ifeq ($(CONFIG_BUILD_KERNEL),y) CHIP_ASRCS += esp32s3_world1_vectors.S endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c b/arch/xtensa/src/esp32s3/esp32s3_isolation.c index 5dcf576f1f19e..2474a22676ebb 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_isolation.c +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -37,7 +37,7 @@ #include #include -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT #include #include #include @@ -61,7 +61,7 @@ #include "soc/extmem_reg.h" -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT #include "sched/sched.h" #include "signal/signal.h" #endif @@ -109,7 +109,7 @@ extern void _xtensa_level3_vector(void); * Private Functions ****************************************************************************/ -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT /**************************************************************************** * Name: pms_clear_violations @@ -171,7 +171,7 @@ static void IRAM_ATTR pms_clear_violations(void) static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) { -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT uint32_t *regs = (uint32_t *)context; /* Acknowledge and re-arm the monitors first so the level-triggered diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c index 8830e9f651a3f..d2e96c8bb61a7 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.c +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.c @@ -34,15 +34,8 @@ #include #include -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT -#include -#endif - #include "xtensa.h" #include "sched/sched.h" -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT -#include "signal/signal.h" -#endif /**************************************************************************** * Pre-processor Definitions @@ -184,60 +177,19 @@ int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs) return -EFAULT; } -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT /**************************************************************************** - * Name: esp32s3_pagefault_abort + * Name: esp32s3_pagefault_clear_repeat * * Description: - * Terminate just the faulting unprivileged (WORLD1) task by delivering a - * fatal SIGSEGV, instead of panicking the whole system. Called from - * xtensa_user() for an unrecoverable user-mode cache-attribute fault. - * - * Mirrors the interrupt-dispatch handshake: record the exception frame as - * the task context, dispatch the signal -- which redirects the task to the - * signal trampoline via up_schedule_sigaction() -- then return the - * redirected frame so the vector's RFE resumes the task in the trampoline, - * whose SIGSEGV default action (_exit) tears the task down and resumes. - * - * Returned Value: - * The register frame to resume (the signal trampoline for the faulting - * task). + * Forget the last serviced fault. Called once a fault has been contained + * some other way -- the task terminated -- so that later, unrelated faults + * at the same address are not counted as runaway recursion. * ****************************************************************************/ -uint32_t *esp32s3_pagefault_abort(int exccause, uint32_t *regs) +void esp32s3_pagefault_clear_repeat(void) { - struct tcb_s *tcb = this_task(); - siginfo_t info; - - /* Reaching here means the fault was contained and the system carried on, - * so the repeat counter has served its purpose. Clear it, or a probe run - * three times at one address would trip the guard in the dispatcher and - * halt a perfectly healthy system. Only *unbroken* recursion -- a report - * that faults before the abort can happen -- should stop the machine. - */ - g_pf_last_vaddr = 0; g_pf_last_pc = 0; g_pf_repeats = 0; - - _alert("SIGSEGV task %s: EXCCAUSE=%d EXCVADDR=%08x PC=%08x\n", - get_task_name(tcb), exccause, (unsigned)regs[REG_EXCVADDR], - (unsigned)regs[REG_PC]); - - up_set_interrupt_context(true); - tcb->xcp.regs = regs; - - info.si_signo = SIGSEGV; - info.si_code = SI_USER; - info.si_errno = 0; - info.si_value.sival_ptr = (FAR void *)regs[REG_EXCVADDR]; - - nxsig_tcbdispatch(tcb, &info, false); - - regs = tcb->xcp.regs; - tcb->xcp.regs = NULL; - up_set_interrupt_context(false); - return regs; } -#endif /* CONFIG_ESP32S3_PAGEFAULT_ABORT */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_pagefault.h b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h index 0b6943e509086..3b268d9ef008b 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_pagefault.h +++ b/arch/xtensa/src/esp32s3/esp32s3_pagefault.h @@ -59,18 +59,15 @@ int esp32s3_pagefault_dispatch(int exccause, uint32_t *regs); -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT /**************************************************************************** - * Name: esp32s3_pagefault_abort + * Name: esp32s3_pagefault_clear_repeat * * Description: - * Terminate just the faulting unprivileged (WORLD1) task via a fatal - * SIGSEGV instead of panicking the whole system. Returns the register - * frame to resume (the signal trampoline for the faulting task). + * Forget the last serviced fault, so that later faults at the same address + * are not mistaken for runaway recursion. * ****************************************************************************/ -uint32_t *esp32s3_pagefault_abort(int exccause, uint32_t *regs); -#endif +void esp32s3_pagefault_clear_repeat(void); #endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_PAGEFAULT_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index 4bd032129d7ab..4a4183feb89bf 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -31,6 +31,7 @@ #ifdef CONFIG_ESP32S3_PAGEFAULT #include "esp32s3_pagefault.h" #endif +#include "esp32s3_userfault.h" #ifdef CONFIG_ESPRESSIF_SPIFLASH #include "esp_private/cache_utils.h" #endif @@ -98,41 +99,39 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) { return regs; } - -#ifdef CONFIG_ESP32S3_PAGEFAULT_ABORT - /* Unrecoverable, but a WORLD1 (user-mode) fault need not take down the - * whole system: terminate just the faulting task with SIGSEGV. The - * User Mode bit in the interruptee's saved PS distinguishes a user - * fault from a privileged one (which still panics below). - */ - - if ((regs[REG_PS] & PS_UM) != 0) - { - return esp32s3_pagefault_abort(exccause, regs); - } -#endif } #endif -#if defined(CONFIG_ESP32S3_PAGEFAULT) && defined(CONFIG_ESP32S3_PAGEFAULT_ABORT) - /* A *denied* external-memory access does not trap on this chip. TRM v1.8 - * p.699: an access without permission is "responded with 0 (for internal - * memory) or 0xdeadbeaf (for external memory)". So an unprivileged branch - * into kernel text in flash or PSRAM is refused silently, the CPU executes - * the dummy word it was handed instead, and the refusal surfaces here as - * EXCCAUSE_ILLEGAL at the address that was branched to -- never as - * EXCCAUSE_INSTR_PROHIBITED. +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT + /* Nothing serviced it, so this fault is not going away by re-executing. + * If the interruptee was unprivileged, terminate just that task. + * + * The test is on the interruptee's User Mode bit and NOT on the cause, and + * that is the whole point. A user task has many ways to raise a + * synchronous exception that no dispatcher can service -- an unaligned + * load (EXCCAUSE 9), a divide by zero (6), a privileged instruction (8), a + * load/store error (3), a corrupt opcode (0) -- and gating on a list of + * causes means every cause left off the list is a way for an unprivileged + * task to stop the machine. Whichever way it arose, a user task running + * garbage must not take the system down with it. + * + * PS.UM also excludes the cases where there is no safe task to kill: a + * kernel thread, a fault inside a system call made on the user's behalf, + * and a fault while handling an interrupt all run with it clear, and fall + * through to the panic below. * - * Re-executing cannot help: the instruction genuinely is not there, so - * this is not offered to the dispatcher above. Terminate just the task. - * That is the right answer whichever way the illegal instruction arose -- - * a refused fetch or simply a corrupt user binary -- because a user task - * running garbage must not take the system down with it. + * One cause deserves its own note. A *denied* external-memory access does + * not trap on this chip. TRM v1.8 p.699: an access without permission is + * "responded with 0 (for internal memory) or 0xdeadbeaf (for external + * memory)". So an unprivileged branch into kernel text in flash or PSRAM + * is refused silently, the CPU executes the dummy word it was handed + * instead, and the refusal surfaces here as EXCCAUSE_ILLEGAL at the + * address that was branched to -- never as EXCCAUSE_INSTR_PROHIBITED. */ - if (exccause == EXCCAUSE_ILLEGAL && (regs[REG_PS] & PS_UM) != 0) + if ((regs[REG_PS] & PS_UM) != 0) { - return esp32s3_pagefault_abort(exccause, regs); + return esp32s3_userfault_abort(exccause, regs); } #endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_userfault.c b/arch/xtensa/src/esp32s3/esp32s3_userfault.c new file mode 100644 index 0000000000000..e0f6ca343a88a --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_userfault.c @@ -0,0 +1,117 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_userfault.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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "xtensa.h" +#include "sched/sched.h" +#include "signal/signal.h" + +#include "esp32s3_userfault.h" +#ifdef CONFIG_ESP32S3_PAGEFAULT +#include "esp32s3_pagefault.h" +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_userfault_abort + * + * Description: + * Terminate just the faulting unprivileged task by delivering a fatal + * SIGSEGV, instead of panicking the whole system. + * + * This is deliberately independent of what the fault was. A user task + * must not be able to stop the machine, and it has many ways to raise a + * synchronous exception that no dispatcher can service: an unaligned + * load, a divide by zero, a privileged instruction, a corrupt opcode. All + * of them arrive here through the same vector with the same frame, so all + * of them get the same answer. Only the caller decides who is eligible -- + * see xtensa_user(), which gates on the interruptee's PS.UM. + * + * Mirrors the interrupt-dispatch handshake: record the exception frame as + * the task context, dispatch the signal -- which redirects the task to the + * signal trampoline via up_schedule_sigaction() -- then return the + * redirected frame so the vector's RFE resumes the task in the trampoline, + * whose SIGSEGV default action (_exit) tears the task down and resumes. + * + * Input Parameters: + * exccause - The EXCCAUSE of the user exception, for reporting + * regs - The register save area at the time of the exception + * + * Returned Value: + * The register frame to resume (the signal trampoline for the faulting + * task). + * + ****************************************************************************/ + +uint32_t *esp32s3_userfault_abort(int exccause, uint32_t *regs) +{ + struct tcb_s *tcb = this_task(); + siginfo_t info; + +#ifdef CONFIG_ESP32S3_PAGEFAULT + /* Reaching here means the fault was contained and the system carried on, + * so the dispatcher's repeat counter has served its purpose. Clear it, or + * a probe run three times at one address would trip that guard and halt a + * perfectly healthy system. Only *unbroken* recursion -- a report that + * faults before the abort can happen -- should stop the machine. + */ + + esp32s3_pagefault_clear_repeat(); +#endif + + _alert("SIGSEGV task %s: EXCCAUSE=%d EXCVADDR=%08x PC=%08x\n", + get_task_name(tcb), exccause, (unsigned)regs[REG_EXCVADDR], + (unsigned)regs[REG_PC]); + + up_set_interrupt_context(true); + tcb->xcp.regs = regs; + + info.si_signo = SIGSEGV; + info.si_code = SI_USER; + info.si_errno = 0; + info.si_value.sival_ptr = (FAR void *)regs[REG_EXCVADDR]; + + nxsig_tcbdispatch(tcb, &info, false); + + regs = tcb->xcp.regs; + tcb->xcp.regs = NULL; + up_set_interrupt_context(false); + return regs; +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_userfault.h b/arch/xtensa/src/esp32s3/esp32s3_userfault.h new file mode 100644 index 0000000000000..c3083e53e12aa --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_userfault.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_userfault.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef CONFIG_ESP32S3_USERFAULT_ABORT + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp32s3_userfault_abort + * + * Description: + * Terminate the faulting unprivileged task with SIGSEGV rather than + * panicking the system. Cause-agnostic; the caller decides eligibility. + * + * Input Parameters: + * exccause - The EXCCAUSE of the user exception, for reporting + * regs - The register save area at the time of the exception + * + * Returned Value: + * The register frame to resume. + * + ****************************************************************************/ + +uint32_t *esp32s3_userfault_abort(int exccause, uint32_t *regs); + +#endif /* CONFIG_ESP32S3_USERFAULT_ABORT */ +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_USERFAULT_H */ From cc02ca29445eaed07c1177a3c9a99749f50b706c Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 20:09:28 +0200 Subject: [PATCH 19/22] xtensa/esp32s3: Report an access through an invalid MMU entry. The PMS grants and refuses physical addresses, so it never sees an access that no MMU entry translates. The cache answered such an access with zeros and raised nothing, and the task carried on with a value it never should have had. Enable EXTMEM_MMU_ENTRY_FAULT and route the Cache Invalid Access interrupt to the handler that already serves the PMS monitors. An unprivileged task that makes the access is terminated with SIGSEGV; a privileged one still panics. The latch is level triggered, so it is cleared with the others. Read the cause before the clear, so the log tells the two apart: a PMS violation is a refused translation, an MMU entry fault is an access that was never translated. Give the kernel_oct configuration the addresses that examples/sandbox needs to name its targets. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/esp32s3_isolation.c | 37 ++++++++++++++++++- .../configs/kernel_oct/defconfig | 8 +++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c b/arch/xtensa/src/esp32s3/esp32s3_isolation.c index 2474a22676ebb..08ec34a7db283 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_isolation.c +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -148,6 +148,16 @@ static void IRAM_ATTR pms_clear_violations(void) modifyreg32(EXTMEM_CORE0_ACS_CACHE_INT_CLR_REG, EXTMEM_CORE0_IBUS_REJECT_INT_CLR_M | EXTMEM_CORE0_DBUS_REJECT_INT_CLR_M, 0); + + /* The invalid MMU entry monitor. An access that no entry translates never + * reaches the PMS, which checks physical addresses, so without this the + * cache answers it with zeros and nothing is reported. + */ + + modifyreg32(EXTMEM_CACHE_ILG_INT_CLR_REG, 0, + EXTMEM_MMU_ENTRY_FAULT_INT_CLR_M); + modifyreg32(EXTMEM_CACHE_ILG_INT_CLR_REG, + EXTMEM_MMU_ENTRY_FAULT_INT_CLR_M, 0); } #endif @@ -173,6 +183,16 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) { #ifdef CONFIG_ESP32S3_USERFAULT_ABORT uint32_t *regs = (uint32_t *)context; + const char *cause; + + /* Read why before acknowledging, because the clear below drops the latch. + * The two causes are answered the same way and are worth telling apart in + * the log: a PMS violation is a refused translation, an MMU entry fault + * is an access that was never translated at all. + */ + + cause = (getreg32(EXTMEM_CACHE_ILG_INT_ST_REG) & + EXTMEM_MMU_ENTRY_FAULT_ST_M) != 0 ? "MMU entry" : "PMS"; /* Acknowledge and re-arm the monitors first so the level-triggered * interrupt does not immediately re-fire while we handle it. @@ -192,8 +212,8 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) 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]); + _alert("SIGSEGV (%s) task %s: PC=%08x\n", + cause, get_task_name(tcb), (unsigned)regs[REG_PC]); info.si_signo = SIGSEGV; info.si_code = SI_USER; @@ -590,10 +610,23 @@ void esp32s3_pmsirqinitialize(void) VERIFY(esp_setup_irq(ESP32S3_PERIPH_CORE_0_PIF_PMS_MONITOR_VIOLATE, 1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL)); + /* Report an access that no MMU entry translates. The PMS grants and + * refuses physical addresses, so an untranslated access is invisible to + * it: the cache returns zeros and the task carries on with a value it + * never should have had. This monitor is what turns that into a fault. + */ + + VERIFY(esp_setup_irq(ESP32S3_PERIPH_CACHE_IA, + 1, ESP_IRQ_TRIGGER_LEVEL, pms_violation_isr, NULL)); + + modifyreg32(EXTMEM_CACHE_ILG_INT_ENA_REG, 0, + EXTMEM_MMU_ENTRY_FAULT_INT_ENA_M); + 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); + up_enable_irq(ESP32S3_IRQ_CACHE_IA); } #endif /* !CONFIG_BUILD_FLAT */ diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig index 4f967a69e4995..975ecb9fbf315 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig +++ b/boards/xtensa/esp32s3/esp32s3-devkit/configs/kernel_oct/defconfig @@ -27,6 +27,8 @@ CONFIG_ARCH_KMAP_NPAGES=2 CONFIG_ARCH_KMAP_VBASE=0x3d400000 CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y CONFIG_ARCH_NUSER_INTERRUPTS=2 +CONFIG_ARCH_PGPOOL_PBASE=0x350000 +CONFIG_ARCH_PGPOOL_SIZE=4194304 CONFIG_ARCH_STACKDUMP=y CONFIG_ARCH_TEXT_NPAGES=8 CONFIG_ARCH_TEXT_VBASE=0x42c00000 @@ -51,13 +53,15 @@ CONFIG_ESP32S3_FLASH_MODE_OCT=y CONFIG_ESP32S3_FLASH_SAMPLE_MODE_STR=y CONFIG_ESP32S3_PAGEFAULT=y CONFIG_ESP32S3_SPIFLASH=y -CONFIG_ARCH_PGPOOL_PBASE=0x350000 -CONFIG_ARCH_PGPOOL_SIZE=4194304 CONFIG_ESP32S3_SPIRAM=y CONFIG_ESP32S3_SPIRAM_MODE_OCT=y CONFIG_ESP32S3_UART0=y CONFIG_ESP32S3_WCL=y CONFIG_EXAMPLES_PFFAULT=y +CONFIG_EXAMPLES_SANDBOX=y +CONFIG_EXAMPLES_SANDBOX_KERNEL_ADDR=0x3fc98000 +CONFIG_EXAMPLES_SANDBOX_PERIPH_ADDR=0x600c5000 +CONFIG_EXAMPLES_SANDBOX_UNMAPPED_ADDR=0x3d800000 CONFIG_FS_PROCFS=y CONFIG_FS_ROMFS=y CONFIG_HAVE_CXX=y From 752787463424ae95fd22d8ab0d7b30d3339a0e6c Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 20:54:43 +0200 Subject: [PATCH 20/22] arch/xtensa: Use one name for the stack frame alignment. Two files each defined the same 16 byte constant, KSTACK_ALIGNMENT and SIGTRAMP_STACK_ALIGN. Use STACKFRAME_ALIGN, which arch/xtensa/include/irq.h already gives as 16, with the STACKFRAME_ALIGN_DOWN() of nuttx/irq.h. STACK_ALIGNMENT is not the name to use here. It is TLS_STACK_ALIGN when CONFIG_TLS_ALIGNED is set, which is the alignment of a thread stack and not of a frame. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/common/xtensa_addrenv_kstack.c | 15 +++------------ arch/xtensa/src/common/xtensa_swint.c | 3 +-- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/arch/xtensa/src/common/xtensa_addrenv_kstack.c b/arch/xtensa/src/common/xtensa_addrenv_kstack.c index 57bb749588062..eb98d3400397d 100644 --- a/arch/xtensa/src/common/xtensa_addrenv_kstack.c +++ b/arch/xtensa/src/common/xtensa_addrenv_kstack.c @@ -38,15 +38,6 @@ #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 ****************************************************************************/ @@ -78,7 +69,7 @@ int up_addrenv_kstackalloc(struct tcb_s *tcb) { DEBUGASSERT(tcb && tcb->xcp.kstack == NULL); - tcb->xcp.kstack = kmm_memalign(KSTACK_ALIGNMENT, ARCH_KERNEL_STACKSIZE); + tcb->xcp.kstack = kmm_memalign(STACKFRAME_ALIGN, ARCH_KERNEL_STACKSIZE); if (tcb->xcp.kstack == NULL) { berr("ERROR: Failed to allocate the kernel stack\n"); @@ -89,8 +80,8 @@ int up_addrenv_kstackalloc(struct tcb_s *tcb) * far end of the allocation. */ - tcb->xcp.ktopstk = (uint32_t *) - KSTACK_ALIGN_DOWN((uintptr_t)tcb->xcp.kstack + ARCH_KERNEL_STACKSIZE); + tcb->xcp.ktopstk = (uint32_t *)STACKFRAME_ALIGN_DOWN( + (uintptr_t)tcb->xcp.kstack + ARCH_KERNEL_STACKSIZE); return OK; } diff --git a/arch/xtensa/src/common/xtensa_swint.c b/arch/xtensa/src/common/xtensa_swint.c index 08eeb5b9726f3..d3bee5f24b6c0 100644 --- a/arch/xtensa/src/common/xtensa_swint.c +++ b/arch/xtensa/src/common/xtensa_swint.c @@ -49,7 +49,6 @@ * 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 @@ -386,7 +385,7 @@ int xtensa_swint(int irq, void *context, void *arg) */ usp = (usp - SIGTRAMP_SAVE_AREA - sizeof(siginfo_t)) & - ~(SIGTRAMP_STACK_ALIGN - 1); + ~(STACKFRAME_ALIGN - 1); memcpy((void *)usp, (void *)regs[REG_A4], sizeof(siginfo_t)); From 9906944c40f51f469ae829bfac21ef14e96be099 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 20:54:43 +0200 Subject: [PATCH 21/22] xtensa/esp32s3: Describe the permission and world functions. Each of these carried a name and nothing else. Give every one a description, its input parameters and its returned value, as asked in review. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/esp32s3_isolation.c | 32 +++++ arch/xtensa/src/esp32s3/esp32s3_pms.c | 135 ++++++++++++++++++++ arch/xtensa/src/esp32s3/esp32s3_wcl.c | 24 ++++ 3 files changed, 191 insertions(+) diff --git a/arch/xtensa/src/esp32s3/esp32s3_isolation.c b/arch/xtensa/src/esp32s3/esp32s3_isolation.c index 08ec34a7db283..ae7267c5606c8 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_isolation.c +++ b/arch/xtensa/src/esp32s3/esp32s3_isolation.c @@ -238,6 +238,14 @@ static int IRAM_ATTR pms_violation_isr(int cpuint, void *context, void *arg) /**************************************************************************** * 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) @@ -325,6 +333,14 @@ void esp32s3_isolation_revoke_peripherals(void) /**************************************************************************** * 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) @@ -483,6 +499,14 @@ static void isolation_configure_dram(void) /**************************************************************************** * 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) @@ -597,6 +621,14 @@ void esp32s3_isolation_permissions(void) /**************************************************************************** * Name: esp32s3_pmsirqinitialize + * + * Description: + * Install the handlers that report a permission violation and an access + * that no MMU entry translates. + * + * Returned Value: + * None. + * ****************************************************************************/ void esp32s3_pmsirqinitialize(void) diff --git a/arch/xtensa/src/esp32s3/esp32s3_pms.c b/arch/xtensa/src/esp32s3/esp32s3_pms.c index 2302c984d19c8..e71e232bded1a 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,6 +564,18 @@ 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. + * ****************************************************************************/ /**************************************************************************** @@ -639,6 +746,18 @@ void esp32s3_pms_configure_sram_region(enum pms_area_e area, /**************************************************************************** * 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, @@ -676,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) @@ -697,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_wcl.c b/arch/xtensa/src/esp32s3/esp32s3_wcl.c index d66e836b321c0..4e7a429e48cd4 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) From 5add8b345db8b601303f4766f8ad806d32fcd1c3 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Tue, 11 Aug 2026 21:56:46 +0200 Subject: [PATCH 22/22] xtensa/esp32s3: Say where a permission violation is served. The comment said "handled elsewhere" and did not say where. Name the handler and the function that installs it, and add the fault for an access that no MMU entry translates. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- arch/xtensa/src/esp32s3/esp32s3_user.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/xtensa/src/esp32s3/esp32s3_user.c b/arch/xtensa/src/esp32s3/esp32s3_user.c index 4a4183feb89bf..df0c8431e5e68 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_user.c +++ b/arch/xtensa/src/esp32s3/esp32s3_user.c @@ -86,9 +86,12 @@ uint32_t *xtensa_user(int exccause, uint32_t *regs) * these to the dispatcher; if serviced, return the register frame so that * the RFE in the exception vector re-executes the faulting instruction. * - * Note: ESP32-S3 PMS (World Controller) memory-protection violations are - * NOT delivered as these precise causes; they raise the asynchronous - * DRAM0/IRAM0 PMS-monitor interrupt instead (handled elsewhere). + * A PMS permission violation does not arrive as one of these causes. It + * raises the asynchronous DRAM0/IRAM0 PMS monitor interrupt, which + * pms_violation_isr() in esp32s3_isolation.c serves. So does an access + * that no MMU entry translates, which the cache reports separately. Both + * are installed by esp32s3_pmsirqinitialize(), in a protected build and in + * a kernel build alike. */ if (exccause == EXCCAUSE_LOAD_PROHIBITED ||