NevynUK opened a new pull request, #20125:
URL: https://github.com/apache/nuttx/pull/20125

   ## Summary
   
   * Adds `CONFIG_BUILD_PROTECTED` for the ESP32-P4. Kernel and user 
applications
     link as two images, the kernel runs M-mode and user code U-mode, and the 
PMP
     enforces the split. Adds an `esp32p4-tab5:knsh` config.
   * Two commits, each of which builds and boots standalone:
     1. `Add BUILD_PROTECTED support.` — 34 files, +2898/-387
     2. `Add PSRAM to the user heap in a protected build.` — 3 files, +77/-14
   * No related NuttX or nuttx-apps issue.
   
   **Why the arch change is needed.** The P4 uses a CLIC, where `mcause` is not
   only a cause register: it carries `mpp[29:28]`, `mpie[27]`, `minhv[30]` and
   `mpil[23:16]`, and `mret` restores privilege from `mpp` and `mintstatus.mil`
   from `mpil`. Upstream reads `mcause` for dispatch and discards it — correct 
on
   a CLINT part, lossy here, because whatever the last trap left in the CSR is
   what the next `mret` consumes. Two changes follow, and both are required; 
with
   either alone the port does not reach NSH.
   
   1. `mcause` is saved and restored with the trap frame. `REG_MCAUSE_NDX = 33`,
      so `INT_XCPT_REGS` becomes 34. It is restored *before* `mstatus`:
      `mstatus.MPP/MPIE` alias `mcause[29:28]/[27]`, and synthesised frames 
leave
      the slot zero, so writing it after `mstatus` drops kernel threads to 
U-mode
      and faults on the first instruction fetch of kernel text.
   2. On a return to U-mode, `mcause.interrupt` (31) and `minhv` (30) are 
cleared.
      The frame's `mcause` is the value latched for the trap that *saved* it, 
but
      after a context switch the frame is restored by a different trap, so
      `interrupt = 1` tells the CLIC it is returning from an interrupt that is 
not
      in flight. Kernel returns are untouched.
   
   **PSRAM.** Three things kept it out of a protected build: `riscv_addregion()`
   guarded its PSRAM block with `!defined(CONFIG_MM_KERNEL_HEAP)`, never true
   here; `configure_mpu()` described UIROM/UDROM/ROM/UDRAM only, so PSRAM 
matched
   no PMP entry; and `knsh/defconfig` set neither `ESPRESSIF_SPIRAM` nor enough
   `MM_REGIONS`. `kumm_addregion()` resolves to `mm_addregion(USR_HEAP, ...)` 
and
   `USR_HEAP` is `(*USERSPACE->us_data->us_heap)` in the kernel phase, so no new
   syscall or userspace gateway is needed.
   
   **PMP entry order is load-bearing.** The region table is in ascending address
   order, not merely each TOR pair internally. PMP resolves an access to the
   *lowest-numbered* matching entry, so a low-numbered gap shadows any grant 
above
   it: appending PSRAM after UDRAM leaves the gap at entry 4 spanning 
`UDROM_END`
   (0x40380000) to `SOC_IROM_MASK_LOW` (0x4fc00000), which swallows 0x48000000 
and
   denies user access however the higher entries are programmed. It presents as 
a
   store access fault on the first user-mode touch, with the heap none the 
wiser.
   `configure_mpu()` carries this comment so the order is not "tidied" later.
   
   **Other notable points.**
   
   * `common/kernel/Makefile` pulls the ROM linker scripts for 
`memcpy`/`strlen`,
     but deliberately **not** `esp32p4.rom.newlib.ld`: it defines newlib's stdio
     symbols absolutely, so user mode would jump into ROM where it has no PMP
     grant. It surfaced as `exit()` -> `fflush(NULL)` faulting.
   * `kernel-space.ld` pulls in the flat sections script, so a board's 
`Make.defs`
     must not also add `esp32p4_sections.ld`, or `.flash.text` is emitted twice
     and `_stext`/`_etext` collapse onto the second, empty section.
   * `esp32p4_sections{,.rev3}.ld`: 189 lines of `*libarch.a:` -> `*arch.a:`. 
The
     protected kernel archive is `libkarch.a`; the widened pattern matches both,
     so flat builds are unaffected.
   * `ESPRESSIF_KERNEL_OWNS_PMP` compiles the HAL's `cpu_region_protect.c` with
     `PMP_L` cleared. The HAL locks every entry from `bootloader_init()` and the
     P4 has no Smepmp, so locked entries are unreclaimable — and one of them
     grants U-mode RW across all kernel data.
   
   ## Impact
   
   * **New feature added?** YES. `CONFIG_BUILD_PROTECTED` on ESP32-P4 plus the
     `esp32p4-tab5:knsh` config. Off by default.
   * **Impact on user?** NO for existing configs; a protected build is opt-in.
   * **Impact on build?** NO. No new host tools or build steps.
   * **Impact on hardware?** YES, scoped. Shared RISC-V files (`irq.h`,
     `riscv_swint.c`, `riscv_doirq.c`, `riscv_schedulesigaction.c`,
     `riscv_internal.h`, `riscv_exception_common.S`) gain lines only inside
     `CONFIG_RISCV_FRAME_TRACE`, `REG_MCAUSE` or `CONFIG_ARCH_CHIP_ESP32P4`, all
     false on every other port. The flat ESP32-P4 build comes out byte-for-byte
     identical in size with identical `free` output.
   * **Impact on documentation?** YES, provided. `esp32p4-tab5/index.rst` gains
     `knsh` and a protected-build flashing section.
   * **Impact on security?** YES, positive in a protected build: user code loses
     the blanket PMP grant over kernel data that the HAL leaves behind. Note the
     trade-off in `ESPRESSIF_KERNEL_OWNS_PMP` — unlocked entries do not 
constrain
     M-mode, so it is `depends on !BUILD_FLAT`.
   * **Impact on compatibility?** NO for other ports. The trap-frame ABI change
     (`INT_XCPT_REGS` 33 -> 34) is gated to `ARCH_CHIP_ESP32P4 && !BUILD_FLAT`.
   * **Anything else?** Two new Kconfig options, both `default n` and fully
     compiled out when off (0 symbols): `RISCV_FRAME_TRACE` (trap-frame ring,
     generic because the hooks are in shared files) and `ESPRESSIF_P4DBG`
     (IRQ/idle/timer counters plus bring-up markers).
   
   ## Testing
   
   I confirm that changes are verified on local setup and work as intended.
   
   * **Build host:** macOS (Darwin 25.6.0), arm64, `riscv32-esp-elf-gcc` 14.2.0
     from the ESP-IDF v5.5.4 toolchain.
   * **Target:** RISC-V, ESP32-P4 rev v1.0, M5Stack Tab5; `esp32p4-tab5:knsh`
     and `esp32p4-tab5:nsh`.
   * **Reproduce:** `./tools/configure.sh esp32p4-tab5:knsh && make`, flash
     `nuttx.bin` at 0x2000 and `nuttx_user.bin` at 0x110000, console UART0 
115200.
   
   All builds clean, zero compiler warnings.
   
   | config | image | ostest |
   | --- | --- | --- |
   | `knsh` commit 1 | 224,280 B kernel + 144,544 B user | status 0, 154 
sections, 99.5 s |
   | `knsh` commit 2 | 233,240 B kernel + 146,098 B user | status 0, 154 
sections, 99.3 s |
   | `knsh` + `P4DBG` + `FRAME_TRACE` | 233,876 B kernel | status 0, 154 
sections, 99.3 s |
   | `nsh` flat | 183,906 B text | status 0, 162 sections, 125.5 s |
   
   `ostest` does not prove the PMP grant on its own, so the PSRAM window is also
   exercised from user mode with `ramtest` at its base, middle and top
   (0x48000000, 0x49000000, 0x49ff0000). The same runs pass on the flat build as
   a control. `TESTING_RAMTEST` ships in `knsh`; it is not in the flat `nsh`
   defconfig and was enabled there only for that control run, as was
   `TESTING_OSTEST`.
   
   Testing logs before change (`esp32p4-tab5:nsh`, unchanged by this PR):
   
   ```
   NuttShell (NSH) NuttX-3.6.1
   nsh> free
         total       used       free    maxused    maxfree  nused  nfree name
      34039356      22988   34016368      23376   33554416     81      4 Umem
   nsh> ostest
   ...
   ostest_main: Exiting with status 0
   ```
   
   Testing logs after change (`esp32p4-tab5:knsh`):
   
   ```
   userspace: drom vma=0x40300080 lma=0x80 size=0xaa00
   userspace: irom vma=0x40210000 lma=0x10000 size=0x13ab2
   userspace: entry=0x4021091e heap=0x4ff40960..0x4ff80000
   userspace: ready
   I (875) cpu_start: cpu freq: 360000000 Hz
   I (879) esp_psram: Adding pool of 32768K of PSRAM memory to heap allocator
   
   NuttShell (NSH) NuttX-3.6.1
   nsh> free
         total       used       free    maxused    maxfree  nused  nfree name
        217924       2644     215280      20664     215080     30      2 Kmem
      33814172       4300   33809872      19768   33554416     10      3 Umem
   nsh> ostest
   ...
   ostest_main: Exiting with status 0
   nsh> ramtest -w -a 0x48000000 -s 65536
   RAMTest: Marching ones: 48000000 65536
   RAMTest: Marching zeroes: 48000000 65536
   RAMTest: Pattern test: 48000000 65536 55555555 aaaaaaaa
   RAMTest: Pattern test: 48000000 65536 66666666 99999999
   RAMTest: Pattern test: 48000000 65536 33333333 cccccccc
   RAMTest: Address-in-address test: 48000000 65536
   ```
   
   Known limitations, neither a regression:
   
   * The USB Serial/JTAG console does not come up in a protected build, so 
`knsh`
     uses UART0 at 115200. Flat `nsh` over USB is unaffected.
   * Chip revision v1.0 prints the usual `SELECTS_REV_LESS_V3` boot warning.
   
   ## PR verification Self-Check
   
   * [x] This PR introduces only one functional change.
   * [x] I have updated all required description fields above.
   * [x] My PR adheres to Contributing 
[Guidelines](https://github.com/apache/nuttx/blob/master/CONTRIBUTING.md) and 
[Documentation](https://nuttx.apache.org/docs/latest/contributing/index.html) 
(git commit title and message, coding standard, etc).
   * [ ] My PR is still work in progress (not ready for review).
   * [x] My PR is ready for review and can be safely merged into a codebase.
   
   ## Attribution
   
   This work was developed with AI assistance. Both commits carry
   `Assisted-by: Claude:claude-opus-5` above the signature, per CONTRIBUTING.md
   section 1.5 and the ASF generative tooling guidance, alongside a
   `Co-Authored-By` trailer. All changes were reviewed and tested on hardware by
   the author, who certifies them under the DCO via `Signed-off-by`.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to