casaroli opened a new pull request, #19584:
URL: https://github.com/apache/nuttx/pull/19584
## Summary
Adds an FDPIC ELF loader so a downloadable module can execute **in place**
out of memory-mapped flash: the read-only segment is mapped where it already
sits on the media and never copied to RAM, and only the writable segment is
copied, once per running instance.
The in-tree ELF loader already does shared `.text` with private `.data` on a
no-MMU target (`CONFIG_PIC`, `mps3-an547:picostest`), so that part is not new.
What FDPIC adds is a function pointer that carries its own data base, as a
two-word descriptor `{entry, GOT}` rather than a bare code address. Two things
follow that a single base register per task cannot express: a module can be
called back on a thread it never created, such as the work-queue worker that
runs a `SIGEV_THREAD` notification; and two objects can hold distinct data
bases at once, which is what makes `DT_NEEDED` shared libraries work with
per-instance library data.
Compared with NXFLAT: FDPIC is standard ELF, needs no `mknxflat`/`ldnxflat`
and no linker script, puts `.rodata` in the RX segment on its own, supports
shared libraries, and needs no workaround for a pointer to a `static` function.
The cost is an `arm-uclinuxfdpiceabi` linker; the stock `arm-none-eabi`
compiler emits correct FDPIC objects for both C and C++, so only the link needs
it.
The loader is `binfmt/fdpic.c` under `CONFIG_FDPIC` (depends on
`CONFIG_ARCH_ARM`, selects `BINFMT_LOADABLE` and `PIC`). It requires a
filesystem that answers `BIOC_XIPBASE`, such as XIPFS or ROMFS.
`arch/arm/src/common/Toolchain.defs` adds `--fixed-r9` so the base firmware
reserves the FDPIC register; without it a firmware callback into module code
arrives with the wrong data base.
Nine commits: the `DT_*_ARRAY` tags in `include/elf.h`; the loader;
constructors and PLT relocation binding; descriptor resolution at the
libc/sched entry points that accept a module callback; `SIGEV_THREAD`
notifications; the developer guide in `Documentation/components/fdpic.rst`; the
out-of-tree module build tooling in `tools/fdpic`; and a board configuration
plus the demo's documentation.
Depends on the matching apps change: opened alongside this one in
apache/nuttx-apps, linked below
## Impact
New feature, off by default. `CONFIG_FDPIC` defaults to `n`, so a tree that
does not enable it is unaffected.
Existing code touched in two places, both no-ops without `CONFIG_FDPIC`.
First, ten libc/sched entry points that can accept a callback from a module now
resolve an FDPIC descriptor before storing or branching to it: `qsort`,
`bsearch`, `pthread_create`, `signal`, `sigaction`,
`task_create`/`task_create_with_stack`, `task_spawn`, `pthread_once`,
`scandir`, and `mq_notify`/`timer_create` with `SIGEV_THREAD`. Each is guarded
by `#ifdef CONFIG_FDPIC`. Second, `Toolchain.defs` adds `--fixed-r9` to
`ARCHCPUFLAGS`, again only under `CONFIG_FDPIC`, which costs the base firmware
one register in that configuration.
Adds one board configuration, `pimoroni-pico-2-plus:xipfs-fdpic`. It builds
with a plain `arm-none-eabi` toolchain because the test modules are committed
as prebuilt blobs, so CI covers it; it deliberately does not enable
`CONFIG_NXFLAT`, which would need `mknxflat` and require excluding the
configuration from the build list.
Documentation: one new page, `Documentation/components/fdpic.rst`, added to
the components toctree, plus the `fdpic` and `reject` sections in the xipfs
test suite page.
No change to any existing API, ABI, or default configuration. FDPIC is ARM
Thumb-2 only; RISC-V has no FDPIC ABI, so a RISC-V target cannot use this
loader.
## Testing
Host: macOS 26.5.1 on arm64. Arm GNU Toolchain 15.2.Rel1
(`arm-none-eabi-gcc` 15.2.1), `arm-uclinuxfdpiceabi-ld` from GNU Binutils 2.43,
QEMU 11.0.3.
**QEMU, `mps2-an500` (Cortex-M7, ARMv7E-M)** — the loader on a different
core generation from the hardware below, with xipfs on a `rammtd` device that
answers `BIOC_XIPBASE`. Built from `mps2-an500:xipfs` plus the FDPIC options.
Full `xipfs_test` suite:
```
==== 130 passed, 0 failed ====
```
The two FDPIC sections on their own:
```
nsh> xipfs_test fdpic
==== 33 passed, 0 failed ====
nsh> xipfs_test reject
-- FDPIC loader rejections --
PASS a non-32-bit ELF class is refused
PASS a non-ET_DYN object is refused
PASS a module for the wrong machine is refused
PASS a module with RELA PLT relocations is refused
PASS a module whose needed library is absent is refused
PASS a module importing an unexported symbol is refused
PASS a module with too many DT_NEEDED entries is refused
==== 7 passed, 0 failed ====
```
The `fdpicxip` demo, all four subcommands. `solib`, showing two instances
sharing one copy of a library's text in flash while each gets its own copy of
its data:
```
nsh> fdpicxip solib
=== FDPIC shared library, executed in place ===
staged libcounter.so (2052 bytes) and user (2732 bytes)
libcounter.so text at 0x6000cd20
user text at 0x6000dd20
spawning two instances, each bumping by its own seed...
[while running] pins on the library's shared text = 2
[user 1] library total = 3 (expected 3) -- PASS
[user 2] library total = 6 (expected 6) -- PASS
[after exit] pins on the library's shared text = 0
```
`cxx` additionally confirms each object's global constructors ran in
dependency order before `main`, and `jmprel` that a module whose imports are
all in `DT_JMPREL` binds and calls out.
**Hardware, Pimoroni Pico Plus 2 (RP2350, Cortex-M33, ARMv8-M)** against
real QSPI flash, `pimoroni-pico-2-plus:xipfs-fdpic`: full suite 130/130,
`fdpic` 33/33, `reject` 7/7, and all four demo subcommands. The same module
blobs run on both cores; they are built for `cortex-m3`, so one set serves v7-M
and v8-M.
**Simulator, `sim:xipfs`** on the host, which exercises the filesystem half
only since the loader is ARM-only:
```
==== 90 passed, 0 failed ====
```
**Loader assertions verified against deliberate breakage.** Each check in
the `fdpic` section was confirmed to fail when the thing it names is broken —
removing the `scandir` filter resolution, adding a second resolution of the
comparison function, and removing the FDPIC register install around a
`SIGEV_THREAD` callback each take the board down with a HardFault rather than
printing a FAIL line, which is how a broken callback resolution manifests on
Cortex-M.
**`--fixed-r9` reaches the compiler**, checked per the guide, and prints
that flag and no other reserved-register flag:
```
$ make V=1 2>&1 | grep -o -- "--fixed-r[0-9]*" | sort -u
--fixed-r9
```
`tools/checkpatch.sh -f` passes on every changed C and header file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01X77FVXXW4bvvBtfFmz1JPz
--
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]