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]