From 2391a81049461a0a310b75874b395592dcfda65f Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 10 Aug 2026 09:36:56 +0200 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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);