Skip to content

arch/xtensa: Provide vfork() and fork(), and BUILD_KERNEL on the ESP32-S3 - #19772

Open
casaroli wants to merge 11 commits into
apache:masterfrom
casaroli:xtensa-fork
Open

arch/xtensa: Provide vfork() and fork(), and BUILD_KERNEL on the ESP32-S3#19772
casaroli wants to merge 11 commits into
apache:masterfrom
casaroli:xtensa-fork

Conversation

@casaroli

@casaroli casaroli commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Xtensa selected neither fork primitive. fork() and vfork() were not available on any Xtensa target. This pull request gives the architecture both, and adds the kernel-build support that fork() needs.

vfork() needs no assembly entry point on this architecture. Every exception entry already runs SPILL_ALL_WINDOWS, so the whole context of the caller is in its exception frame. A flat build reaches that frame with SYS_save_context, issued inline. A build with system calls reaches it with xcp.sregs, which xtensa_swint() records.

A stack copy needs more than a new stack pointer here. A windowed ABI keeps the stack pointer of the caller in the base save area below each frame, so a copy at a different address still names the parent. xtensa_fork_rebase() walks that chain and adds the offset. The copy starts one base save area below the stack pointer, because the frame that the child resumes into keeps the spilled a0 to a3 of its caller there.

fork() needs per-process address environments, so the ESP32-S3 gets BUILD_KERNEL support. The chip has a 64 KiB MMU page, and pgalloc.h stopped at 16 KiB, so mm/pgalloc learns 32 KiB and 64 KiB pages. up_addrenv_fork() then duplicates an address environment into fresh pages at the same virtual addresses.

The page pool is not kept mapped into the kernel address space. It 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. The kernel reaches a pool page through a small scratch region instead, mapped for one operation. Two slots are sufficient, because the deepest user is up_addrenv_fork(), which holds a source page and a destination page at once.

Impact

The change is specific to Xtensa. It adds capability and removes none.

vfork() becomes available on Xtensa in all build modes. Code that tests CONFIG_ARCH_HAVE_VFORK now finds it on this architecture.

fork() becomes available on the ESP32-S3 in a kernel build only. That is the only mode with address environments. CONFIG_ARCH_HAVE_FORK stays unset everywhere else.

Two board configurations are new: kernel_oct for a WROOM-2 with octal flash, and kernel_n8r2 for a module with quad flash and 2 MB of PSRAM. Both were run on the hardware they name. No existing configuration changes.

The mm/pgalloc commit touches shared code. It adds two cases to a preprocessor selection and changes no existing case.

Testing

Board: ESP32-S3-DevKitC. Two modules, an ESP32-S3-WROOM-2 N32R8V with 32 MB octal flash and 8 MB octal PSRAM, and an ESP32-S3-WROOM-1 N8R2 with 8 MB quad flash and 2 MB embedded PSRAM.

Board: ESP32-DevKitC V4, with an ESP32-D0WD-V3 revision 3.1. This part has an LX6 core, so the change is now tested on both Xtensa cores that NuttX supports.

Host: macOS 15 on Apple Silicon, xtensa-esp32s3-elf-gcc and xtensa-esp32-elf-gcc, both 12.2.0.

module configuration build mode flash result
WROOM-2 esp32s3-devkit:ostest flat octal vfork() passes, status 0
WROOM-2 esp32s3-devkit:knsh protected octal vfork() passes, status 0
WROOM-2 esp32s3-devkit:kernel_oct kernel octal vfork() and fork() pass, status 0
WROOM-1 N8R2 esp32s3-devkit:ostest flat DIO vfork() passes
WROOM-1 N8R2 esp32s3-devkit:kernel_n8r2 kernel DIO vfork() and fork() pass, status 0
ESP32-D0WD-V3 esp32-devkitc:ostest flat DIO vfork() passes, status 0
user_main: vfork() test
vfork_test: Child 6 ran and exited before the parent resumed
user_main: fork() test
fork_test: Child running independently (child)
fork_test: Parent and child had independent memory
ostest_main: Exiting with status 0

tools/checkpatch.sh -c -u -m -g gives no errors.

The protected row needs #19764, which lets a protected build boot from simple boot. It is not necessary for the flat or the kernel rows.

Notes for a reviewer

kernel_n8r2 is sized tightly on purpose. Each of the text, data and heap regions is 2 pages of 64 KiB, so a process takes 384 KiB and a fork() peaks at 768 KiB. ostest has 115 KiB of text against a 128 KiB text region. A larger program needs a part with more PSRAM, not a larger pool. A module with 8 MB of PSRAM can keep the roomy defaults, as kernel_oct does.

QIO flash does not boot on the N8R2 that I have, in any configuration, and the cause is outside this patch. flash_qio_mode is not linked into the image, so the QE bit of the flash is never set. The quad configurations therefore use DIO.

I have not tested any Xtensa target other than the ESP32-S3. vfork() should work on the ESP32 and the ESP32-S2 without change, because the code is in arch/xtensa/src/common, but I have no board. A report from one would be welcome.

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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
…t 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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@github-actions github-actions Bot added Arch: xtensa Issues related to the Xtensa architecture Area: Memory Management Memory Management issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. Board: xtensa labels Aug 10, 2026

@acassis acassis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@casaroli please add Documentation, that table with information about the possible tests will be very helpful

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

esp32-devkitc

@github-actions github-actions Bot added Area: Documentation Improvements or additions to documentation and removed Area: Memory Management Memory Management issues labels Aug 10, 2026
@casaroli
casaroli requested a review from acassis August 10, 2026 20:04
@casaroli

Copy link
Copy Markdown
Contributor Author

@casaroli please add Documentation, that table with information about the possible tests will be very helpful

done 🫡

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 <marco.casaroli@gmail.com>

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@casaroli

Copy link
Copy Markdown
Contributor Author

@tmedicci can you please take a look?

* about to be started with.
*/

xcp->regs = (void *)((uint32_t)xcp->ktopstk - XCPTCONTEXT_SIZE);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change ALL uint32_t cast to uintptr_t

Comment thread arch/Kconfig

config ARCH_HAVE_FORK
bool
default y if ARCH_XTENSA && BUILD_KERNEL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's select under ARCH_XTENSA

*
****************************************************************************/

int up_addrenv_fork(const arch_addrenv_t *src, arch_addrenv_t *dest)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we move to common and generalize it instead

@casaroli casaroli Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ESP32 has no instruction-bus path to external RAM, so no page-backed text. Looks like this is specific to esp32s3 (and maybe possibly esp32s2)


if ((uintptr_t)CONFIG_ARCH_PGPOOL_VBASE < ramstart ||
(uintptr_t)CONFIG_ARCH_PGPOOL_VEND > ramend)
if (ESP32S3_PGPOOL_PBASE < rampbase ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not continue use the general Kconfig

****************************************************************************/

#define _SIGTRAMP_STR(x) #x
#define _SIGTRAMP_XSTR(x) _SIGTRAMP_STR(x)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use macro from nuttx/macro.h


#include "xtensa.h"

#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_ARCH_KERNEL_STACK)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

/* The Xtensa windowed ABI requires 16-byte stack alignment */

#define KSTACK_ALIGNMENT 16
#define KSTACK_ALIGN_DOWN(a) ((a) & ~(KSTACK_ALIGNMENT - 1))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use STACK_ALIGNMENT from xtensa_internal.h

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any xtensa_internal.h. I didn't find any STACK_ALIGNMENT in arch/. Are you talking about KSTACK_ALIGNMENT in arm_internal.h?

Comment thread arch/xtensa/src/common/xtensa_swint.c
*
****************************************************************************/

void up_allocate_pgheap(void **heap_start, size_t *heap_size)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move the most code into common folder

@casaroli casaroli Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LX6 cannot host a page pool at all. Maybe we can reuse this for esp32s2, but I am still not sure

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MMU seems to be specific to espressif, not xtensa, so maybe the correct home would be arch/xtensa/src/common/espressif/ if we can confirm esp32s2 can use it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, let's move

  1. arch common code to common
  2. lx? specific code to lx?
  3. esp32 specific code to arch/xtensa/src/common/espressif/

@@ -0,0 +1,488 @@
/****************************************************************************
* arch/xtensa/src/common/xtensa_fork.c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you split the pr into small ones:

  1. vfork
  2. mmu in common arch code
  3. code in esp32

casaroli added a commit to casaroli/nuttx that referenced this pull request Aug 11, 2026
Review of apache#19772 asked for this shape, and it applies to every architecture in
the series.

ARCH_HAVE_FORK described when it was available from inside its own definition,
which put the per-architecture condition somewhere nobody looks.  The
architecture now says so itself.

The condition repeats both dependencies rather than relying on them, because a
select bypasses depends on.  RISC-V also excludes dynamic stacks, so that is
named here too.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli added a commit to casaroli/nuttx that referenced this pull request Aug 11, 2026
Review of apache#19772 asked for this shape, and it applies to every architecture in
the series.

ARCH_HAVE_FORK described when it was available from inside its own definition,
which put the per-architecture condition somewhere nobody looks.  The
architecture now says so itself.

The condition repeats the ARCH_ADDRENV dependency rather than relying on it,
because a select bypasses depends on:  without that repetition an architecture
could offer fork() where there is no address environment to duplicate.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli added a commit to casaroli/nuttx that referenced this pull request Aug 11, 2026
Review of apache#19772 asked for this shape, and it applies to every architecture in
the series.

ARCH_HAVE_FORK described when it was available from inside its own definition,
which put the per-architecture condition somewhere nobody looks.  The
architecture now says so itself.

The condition repeats the ARCH_ADDRENV dependency rather than relying on it,
because a select bypasses depends on:  without that repetition an architecture
could offer fork() where there is no address environment to duplicate.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli added a commit to casaroli/nuttx that referenced this pull request Aug 11, 2026
Review of apache#19772 asked for this shape, and it applies to every architecture in
the series.

ARCH_HAVE_FORK described when it was available from inside its own definition,
which put the per-architecture condition somewhere nobody looks.  The
architecture now says so itself, next to the other things ARMv7-A provides.

The condition repeats the ARCH_ADDRENV dependency rather than relying on it,
because a select bypasses depends on:  without that repetition an architecture
could offer fork() where there is no address environment to duplicate.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Comment on lines +88 to +90
* 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).

@igrr igrr Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please clarify where is "elsewhere"?

Could you please also mention, what is the observed outcome (on ESP32-S3) when a process tries to access:

  1. An internal RAM region to which access hasn't been granted
  2. A PSRAM page to which access hasn't been granted

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at the code a bit more, I think the answers seem to be:

  1. no fault is generated, user process can freely read and write kernel RAM
  2. no fault is generated, reads silently return zero

Not sure it this would be expected behavior in NuttX.

# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PMS primitives are compiled in case of CONFIG_ARCH_ADDRENV, but they only seem to be called from esp32s3_userspace.c, which is included only if CONFIG_BUILD_PROTECTED=y.

So right now in case of BUILD_KERNEL, PMS doesn't get enabled, and user processes can freely access kernel memory.

(Seems like esp32s3_pmsirqinitialize doesn't get called in case of BUILD_KERNEL, either)

}

/****************************************************************************
* Name: esp32s3_wcl_set_world0_entry

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a complete description? (please check other functions)

*
****************************************************************************/

int up_addrenv_select(const arch_addrenv_t *addrenv)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although a single address environment is enabled at a time, nothing prevents the userspace process from accessing the MMU registers and changing the mapping. Additionally, there is no protection against accessing peripherals from the user process. That being said, can't we use WCL and PMS to prevent that? Userspace applications should never access peripherals (MMU mapping included) directly. That would bring better process isolation and make it adherent to NuttX's userspace.

casaroli added a commit to casaroli/nuttx that referenced this pull request Aug 11, 2026
Two points from the review of apache#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) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
@acassis

acassis commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@tmedicci and about: "QIO flash does not boot on the N8R2 that I have, in any configuration" could you please help to investigate it? @casaroli asked me to test on ESP32S3 WROOM1 and I faced the same issue.

@casaroli

casaroli commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@igrr @tmedicci Thank you for the comments. You are correct. I have the fix for this (we will rework and revisit, but the PoC worked). I can include here, however I was already asked to split this PR (which I did, please check #19795, #19796, #19797), and to prevent this from becoming larger, i propose we just make fork() work first and then we add the isolation next (as part of #19797 or a follow up PR).

It is worth mentioning that the other arches that implement BUILD_KERNEL (arm and rv), their MMU already isolate by design. xtensa is the only one that requires the MMU and the PMS to be configured together.

I want to add it, and I also want your review on how we will handle the fault and kill only the offending process, so the system does not go down (I have a PoC of that working, but I believe we should go step-by-step). If you prefer, we can put everything back together in a single PR.

I will put everything together in a branch (maybe a draft PR) so we can review all of this together. Thank you again for the comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: xtensa Issues related to the Xtensa architecture Area: Documentation Improvements or additions to documentation Board: xtensa Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants