Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2391a81
arch/xtensa: Provide vfork().
casaroli Aug 10, 2026
b1daf04
mm/pgalloc: support 32 KB and 64 KB page sizes
casaroli Jul 30, 2026
4f564e4
xtensa: Support BUILD_KERNEL.
casaroli Aug 10, 2026
0c05141
xtensa/esp32s3: add recoverable cache-attribute fault dispatcher (Uni…
casaroli Jul 30, 2026
f01eb70
xtensa/esp32s3: keep octal-flash init in IRAM for the protected build
casaroli Jul 30, 2026
512efe9
xtensa/esp32s3: Implement per-process address environments.
casaroli Aug 10, 2026
c6456f8
boards/esp32s3-devkit: add a kernel-build configuration
casaroli Jul 30, 2026
a7a86ce
xtensa/esp32s3: Reach a page pool page through a scratch mapping.
casaroli Aug 10, 2026
8c5e993
arch/xtensa: Provide POSIX fork() on the ESP32-S3.
casaroli Aug 10, 2026
be6dcf3
boards/esp32s3-devkit: Add kernel_n8r2, a kernel build for 2 MB PSRAM.
casaroli Aug 10, 2026
f83f531
Documentation/esp32s3-devkit: Describe the kernel build configurations.
casaroli Aug 10, 2026
6d648f6
arch/xtensa: Address the review of the fork series.
casaroli Aug 11, 2026
3be3fe2
xtensa/esp32s3: Wire the chip into the kernel build.
casaroli Aug 11, 2026
e00232c
xtensa/esp32s3: isolate the unprivileged world
casaroli Jul 30, 2026
4b28fc2
xtensa/esp32s3: abort the faulting user task on an unrecoverable fault
casaroli Jul 24, 2026
bd0806a
xtensa/esp32s3: contain an illegal instruction taken in user mode
casaroli Jul 28, 2026
8dc85fb
xtensa/esp32s3: stop an unreportable cache fault from livelocking
casaroli Jul 28, 2026
c2a9f04
xtensa/esp32s3: contain any user fault, not a list of causes
casaroli Jul 30, 2026
cc02ca2
xtensa/esp32s3: Report an access through an invalid MMU entry.
casaroli Aug 11, 2026
7527874
arch/xtensa: Use one name for the stack frame alignment.
casaroli Aug 11, 2026
9906944
xtensa/esp32s3: Describe the permission and world functions.
casaroli Aug 11, 2026
5add8b3
xtensa/esp32s3: Say where a permission violation is served.
casaroli Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
----

Expand Down
26 changes: 16 additions & 10 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ config ARCH_X86_64
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
Expand Down Expand Up @@ -1034,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
Expand All @@ -1045,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)"
Expand All @@ -1062,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

Expand Down
4 changes: 4 additions & 0 deletions arch/xtensa/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ 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_ELF_EXECUTABLE
select ARCH_HAVE_MULTICPU
select ARCH_HAVE_RESET
select ARCH_HAVE_TEXT_HEAP
Expand Down
50 changes: 50 additions & 0 deletions arch/xtensa/include/arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
****************************************************************************/
Expand Down
27 changes: 27 additions & 0 deletions arch/xtensa/include/irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,34 @@ 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
* 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.
*/
Expand Down
14 changes: 12 additions & 2 deletions arch/xtensa/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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; \
Expand Down
11 changes: 10 additions & 1 deletion arch/xtensa/src/common/Make.defs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -81,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
Expand Down
48 changes: 48 additions & 0 deletions arch/xtensa/src/common/crt0.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/macro.h>

#include <sys/types.h>
#include <stdlib.h>
Expand Down Expand Up @@ -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

/****************************************************************************
Expand Down
4 changes: 2 additions & 2 deletions arch/xtensa/src/common/espressif/esp_irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
# include "hardware/esp32s3_soc.h"
# include "esp_gpio.h"
# include "esp32s3_rtc_gpio.h"
# include "esp32s3_userspace.h"
# include "esp32s3_isolation.h"
# ifdef CONFIG_SMP
# include "esp32s3_smp.h"
# define ESP_FROMCPU1_PERIPH ESP32S3_PERIPH_INT_FROM_CPU1
Expand All @@ -106,7 +106,7 @@
# define ESP_NCPUS 1
#endif

#if defined(CONFIG_ARCH_CHIP_ESP32S3) && defined(CONFIG_BUILD_PROTECTED)
#if defined(CONFIG_ARCH_CHIP_ESP32S3) && !defined(CONFIG_BUILD_FLAT)
# define esp_pmsirqinitialize() esp32s3_pmsirqinitialize()
#else
# define esp_pmsirqinitialize()
Expand Down
Loading
Loading