examples/fdpicxip, testing/fs/xipfs: FDPIC modules executed in place from XIPFS - #3682
Open
casaroli wants to merge 2 commits into
Open
examples/fdpicxip, testing/fs/xipfs: FDPIC modules executed in place from XIPFS#3682casaroli wants to merge 2 commits into
casaroli wants to merge 2 commits into
Conversation
The FDPIC counterpart of examples/nxflatxip, kept separate from it: that example is about NXFLAT, and mixing the two module formats into one program would leave neither demonstrating anything clearly. Four subcommands, each showing one property of the loader. 'qsort' is the simplest case it has -- one self-contained module, two concurrent instances, one shared copy of the text in flash and a private copy of the data each, with the pin count showing the extent held in place while they run and released after. 'solib' adds a shared library, so two objects are mapped and each instance still gets its own copy of both objects' data. 'cxx' is the same in C++, which additionally requires global constructors to have run in dependency order before main. 'jmprel' is a module whose imports are all in DT_JMPREL rather than DT_REL. The modules are embedded as headers rather than built as part of the app: linking one needs arm-uclinuxfdpiceabi binutils, which the tree does not require, so the headers are committed and both this example and testing/fs/xipfs build with a plain toolchain. Their sources are in modules/, with a makefile that rebuilds every header from them on an explicit 'make regen NUTTX_DIR=...' and is never invoked by the application build. It drives nuttx/tools/fdpic and writes the headers this example needs alongside the ones testing/fs/xipfs needs, so one source tree serves both and the two cannot drift apart. CPU is cortex-m3 there deliberately: a v7-M module runs on the v8-M targets too, so one set of blobs serves both the RP2350 and mps2-an500, while a cortex-m33 build produces blobs the Cortex-M7 cannot execute at all. This demonstrates; it does not assert. The assertions are in the fdpic and reject sections of apps/testing/fs/xipfs. Verified on a Pimoroni Pico Plus 2: all four subcommands, and nxflatxip unaffected alongside them. Also verified on mps2-an500 under QEMU, which is a Cortex-M7 -- a different core generation from the RP2350's Cortex-M33. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Adds two sections to the xipfs suite, guarded by CONFIG_FDPIC so a build without the loader is unaffected. They belong here rather than in their own test because what they exercise is modules loaded *out of the filesystem* -- the pin, the in-place mapping and the loader are one path. 'fdpic' asserts on the loader properties that otherwise fail *quietly*: a loader that skips DT_INIT_ARRAY runs a C++ module happily with every global left zero, one that skips DT_JMPREL loads a module that hard-faults only once it calls out, and one that mis-sizes the descriptor pool corrupts the heap. None of those announce themselves. The module exit status is the channel -- each module checks its own invariants and reports a bitmask. It also covers shared libraries across concurrent instances, per-instance data, the leaf-library GOT fallback, the R_ARM_FUNCDESC descriptor pool, and every firmware entry point that has to resolve a module callback, including mq_notify and timer_create with SIGEV_THREAD. 'reject' mutates a known-good module byte by byte and asserts the loader refuses it rather than loading something broken: a missing import, and more DT_NEEDED entries than the walk will follow. The modules are embedded as headers, for the same reason as in examples/fdpicxip, and built for cortex-m3 for the same reason: one set of blobs then runs on both the v7-M and v8-M targets. Their sources and the makefile that regenerates these headers live in apps/examples/fdpicxip/modules, so the copies the two apps carry are built from the same sources in the same way. Verified on a Pimoroni Pico Plus 2: fdpic 33/33, reject 7/7, and the whole suite 130/130 with them included. The same three numbers on mps2-an500 under QEMU, which is a Cortex-M7 rather than the RP2350's Cortex-M33. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
xiaoxiang781216
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The apps half of the FDPIC ELF loader: apache/nuttx#19584, which should merge first. Nothing here can be enabled until
CONFIG_FDPICexists, so on its own this PR is inert rather than broken: the demo's Kconfig depends on that symbol and the new test sections are inside#ifdef CONFIG_FDPIC. Two commits, both new directories, both guarded byCONFIG_FDPIC.examples/fdpicxipdemonstrates FDPIC modules executed in place out of XIPFS. It writes modules into the volume at run time, the way a download would, then runs them. Four subcommands, one loader property each:qsortis one self-contained module as two concurrent instances, sharing a single copy of its text in flash while each instance gets its own data;solibadds a shared library, so two objects are mapped and each instance gets its own copy of both objects' data;cxxis the same in C++, which additionally requires each object's global constructors to have run in dependency order beforemain; andjmprelis a module whose imports are all inDT_JMPRELrather thanDT_REL. It is the FDPIC counterpart ofexamples/nxflatxip, kept separate because one program demonstrating both module formats would demonstrate neither clearly.testing/fs/xipfsgrows two sections,fdpicandreject, so a loader regression fails a test rather than waiting to be noticed on someone's board. They belong in the xipfs suite because what they exercise is modules loaded out of the filesystem — the pin, the in-place mapping and the loader are one path.fdpiccovers the properties that otherwise fail silently: constructors in dependency order, per-instance data, the manufacturedR_ARM_FUNCDESCdescriptors, both relocation tables, the GOT fallback for a leaf library with noDT_PLTGOT, and every firmware entry point that has to resolve a module callback, includingmq_notifyandtimer_createwithSIGEV_THREAD. Each module reports its own invariants through its exit status, because a callback that never runs is the failure being tested for.rejectmutates a known-good module and asserts the loader refuses it rather than loading something broken.The modules are embedded as C headers rather than built as part of either app, because linking one needs
arm-uclinuxfdpiceabibinutils, which building NuttX does not. Their sources are inexamples/fdpicxip/moduleswith a makefile that regenerates every header on an explicitmake regen NUTTX_DIR=..., never as part of the application build. That one makefile writes the headers both apps embed, so the two cannot drift apart, and it drives the tooling innuttx/tools/fdpic.CPUiscortex-m3there deliberately: a v7-M module also runs on the v8-M targets, so one set of blobs serves both the RP2350 andmps2-an500, whereas acortex-m33build produces blobs a Cortex-M7 cannot execute at all.Impact
New feature, off by default. Both
CONFIG_EXAMPLES_FDPICXIPand the two new test sections requireCONFIG_FDPIC, which defaults ton, so a tree without it is unaffected. The new sections intesting/fs/xipfsare#ifdef CONFIG_FDPIC, so an existing xipfs test configuration behaves exactly as before.Both apps build with a plain
arm-none-eabitoolchain, because the module blobs are committed. Nothing invokesmodules/; regeneration is opt-in.No change to any existing app.
Testing
Host: macOS 26.5.1 on arm64. Arm GNU Toolchain 15.2.Rel1 (
arm-none-eabi-gcc15.2.1),arm-uclinuxfdpiceabi-ldfrom GNU Binutils 2.43, QEMU 11.0.3.QEMU,
mps2-an500(Cortex-M7, ARMv7E-M), xipfs on arammtddevice that answersBIOC_XIPBASE. Fullxipfs_testsuite, which is the 90 filesystem checks plus the 40 this PR adds:The two new sections on their own:
The demo, all four subcommands.
solib:cxx, showing each object's constructors ran, the library's before the module's, and the destructors on unload:qsortruns two instances with no library behind them, andjmprelconfirms a module bound throughDT_JMPRELreaches the firmware and returns.Hardware, Pimoroni Pico Plus 2 (RP2350, Cortex-M33, ARMv8-M) against real QSPI flash,
pimoroni-pico-2-plus:xipfs-fdpic: full suite 130/130,fdpic33/33,reject7/7, and all four demo subcommands. The same blobs run on both cores.Simulator,
sim:xipfson the host, confirming the new sections are correctly guarded — the loader is ARM-only, so only the 90 filesystem checks run and nothing regressed:Blob provenance is reproducible.
make -C examples/fdpicxip/modules regen NUTTX_DIR=...rebuilds all 15 headers from the committed sources and reproduces the committed bytes exactly, so the blobs can be audited against their sources.Assertions verified against deliberate breakage. Each check in the
fdpicsection was confirmed to fail when the loader property it names is broken. On Cortex-M that manifests as a HardFault taking the system down mid-test rather than a FAIL line, so a run that ends without its summary line is a failure, not a hang.tools/checkpatch.sh -fpasses on every changed C, C++ and header file, including the generated*_bin.h.🤖 Generated with Claude Code
https://claude.ai/code/session_01X77FVXXW4bvvBtfFmz1JPz