Skip to content

boards: Simplify NuttX initialization#18408

Open
linguini1 wants to merge 18 commits intoapache:masterfrom
linguini1:byebye-archinit
Open

boards: Simplify NuttX initialization#18408
linguini1 wants to merge 18 commits intoapache:masterfrom
linguini1:byebye-archinit

Conversation

@linguini1
Copy link
Contributor

@linguini1 linguini1 commented Feb 19, 2026

Summary

BREAKING CHANGE

This change simplifies the NuttX initialization logic by:

  • Replacing NSH_ARCHINIT with CONFIG_BOARD_LATE_INITIALIZE in defconfigs
  • Removing BOARDIOC_INIT/board_app_initialize
  • Ensuring that board_late_initialize performs the same function as
    board_app_initialize did so any defconfigs relying on NSH_ARCHINIT will not
    break.

This is related to #11321.

Twin PR in NuttX apps should be merged at the same time: apache/nuttx-apps#3405

Impact

Almost every single board/configuration in the NuttX source tree, since many
relied on NSH for initialization.

This is a breaking change that removes the user's ability to use BOARDIOC_INIT
as well. Users are provided with quick-fixes in the commit messages for how to
fix any breakages. BOARDIOC_FINALINIT should be used instead for applications
that truly require full control over the initialization process. For every other
application, BOARD_LATE_INITIALIZE should be enabled to have the NuttX kernel
perform initialization before launching into the app.

Testing

I will be testing on the platforms available to me (simulators and whatever
hardware I own). Testing from community members with hardware across the
architectures affected would be greatly appreciated. If you do want to help with
testing, please provide logs in the PR comments for the affected defconfigs you
tested.

Testing of applications can be seen in the twin PR: apache/nuttx-apps#3405

OS Test logs

@linguini1
Copy link
Contributor Author

linguini1 commented Feb 19, 2026

Notes for reviewers on the initial draft:

  1. This is not a fix on all boards; I still have ~200 in boards/arm to finish
  2. This is obviously a huge change so it would be great to have some testing on platforms which I am not able to test on my own hardware (i.e. any STM32, the Espressif internal CI would be good to run, anyone with tricore/renesas/sparc/etc. boards).
  3. Unfortunately I don't think this can be split across multiple PRs, since removing NSH_ARCHINIT relies on board_late_initialize to have identical contents to board_app_initialize. The removal of BOARDIOC_INIT might be able to get split but since this change is going to touch almost every board, it should just be done simultaneously to reduce the chance of making errors. I am open to suggestions as I realize this is a monster PR, but I can't think of a better approach.
  4. I think I will eventually squash all of the commits replacing board_app_initialize -> board_late_initialize into one, since anyone bisecting later would probably treat this entire change as one unit. Please let me know if it's advisable to squash in any other way as well. For now this helps me keep track of everything while the change is under review
  5. I think this should go surprisingly smoothly for most configurations; a majority of the boards that are affected had a very standard setup for app_init/late_init where they both did the exact same thing
  6. Anything beyond removing BOARDIOC_INIT, board_app_initialize and NSH_ARCHINIT are outside of the scope of this PR. I will not be addressing anything else in the boot process that is independent from these changes (i.e. non-standard naming of files for boot logic, etc.)

@fdcavalcanti
Copy link
Contributor

@linguini1 this is a great initiative.
Please share your process or a board example and I can do it on ESP boards.

@linguini1
Copy link
Contributor Author

this is a great initiative. Please share your process or a board example and I can do it on ESP boards.

Hi @fdcavalcanti, if you're talking about making the changes, I think all ESP boards should be included in this patch already (xtensa/risc-v). They were actually quite easy since the board_late_init and board_app_init logic were pretty much identical.

If you're talking about how to test, the process would be to configure the build system for one of the modified ESP32 configurations (i.e. nsh) and just boot into NuttX, check that things look okay and run ostest.

Hope I understood correctly, thanks for your help!

@github-actions github-actions bot added the Arch: arm Issues related to ARM (32-bit) architecture label Feb 19, 2026
@linguini1 linguini1 marked this pull request as ready for review February 19, 2026 22:04
@cederom
Copy link
Contributor

cederom commented Feb 19, 2026

  • Thank you @linguini1 this is another mountain that you move with your one-man-army :-)
  • As discussed on the Simplify NuttX initialization #11321 and mailing list there is agreement on the change already so we can move forward :-)
  • Maybe its worth marking this PR as draft until ready?
  • Would it be possible to create documentation and diagram of the boot process? So we know what was before and after? To be honest I am a bit lost in this probably more users too :-P Please let me know how can I help here?? :-)
  • I am wondering if you already change some internals maybe naming could be updated too to be simpler? Do you think this would help or is not necessary? :-)

@linguini1
Copy link
Contributor Author

  • Thank you @linguini1 this is another mountain that you move with your one-man-army :-)

    • As discussed on the Simplify NuttX initialization #11321 and mailing list there is agreement on the change already so we can move forward :-)

    • Maybe its worth marking this PR as draft until ready?

This PR is now ready! I just finished removing the remaining references to board_app_initialize everywhere, so aside from unintended bugs, everything is in place. What needs to happen now is some pretty extensive testing (right now I have only done a few QEMU/sim boards) to make sure there were no human errors/unintended consequences :)

* Would it be possible to create documentation and diagram of the boot process? So we know what was before and after? To be honest I am a bit lost in this probably more users too :-P Please let me know how can I help here?? :-)

I think that would be good! I must admit though, I am not the most familiar with the boot process. This PR only modifies a very small subset of it.

TLDR:
Many defconfigs enabled NSH_ARCHINIT to allow NSH to take care of board initialization. This was performed through the call boardctl(BOARDIOC_INIT, 0), which under the hood calls the implementation of board_app_initialize. However, it was also possible to have NuttX call board_late_initialize by enabling BOARD_LATE_INITIALIZE. The benefit was not having anything done from user space. Since the two methods achieve the same task, have the exact same logic on many platforms, and was confusing to users, this PR makes board_late_initialize the default and removes the userspace init that was done through NSH. So really, it moves that one boot step from userspace to kernel space.

Note: users can still control init from userspace in extreme cases by making their own implementation of BOARDIOC_FINALINIT.

* I am wondering if you already change some internals maybe naming could be updated too to be simpler? Do you think this would help or is not necessary? :-)

What kind of naming are you thinking of? I don't really want to play around with too many changes since that will delay this PR and could introduce more margin of error. Right now all I've done is strictly replace all app_init stuff with late_init stuff.

@raiden00pl
Copy link
Member

raiden00pl commented Feb 20, 2026

The only thing that puzzles me about this change is how the "init thread" aligns with safety standards. This mechanism creates a thread that is later destroyed, which isn't entirely desirable for safety critical applications. But on the other hand, "init thread" is part of the system initialization process, and during the initialization phase, dynamic memory allocation is tolerated, but I'm not sure about deallocation. The goal here is that in the event of a board initialization failure, the system is in a known safe state.
So, I think it still fits within the acceptable use of dynamic memory for safety critical apps, but not sure. Ultimately, there's always BOARDIOC_FINALINIT option, which allows users to get rid of the "init thread" and perform initialization in "main", just like old BOARDIOC_INIT.

@patacongo
Copy link
Contributor

patacongo commented Feb 20, 2026 via email

@raiden00pl
Copy link
Member

hi @patacongo :)

I think nx_start() became IDLE thread:

nuttx/sched/init/nx_start.c

Lines 749 to 771 in 34f5238

/* Enter to idleloop */
g_nx_initstate = OSINIT_IDLELOOP;
sched_trace_mark("IDLELOOP");
/* Let other threads have access to the memory manager */
sched_trace_end();
sched_unlock();
/* The IDLE Loop **********************************************************/
/* When control is return to this point, the system is idle. */
sinfo("CPU0: Beginning Idle Loop\n");
#ifndef CONFIG_DISABLE_IDLE_LOOP
for (; ; )
{
/* Perform any processor-specific idle state operations */
up_idle();
}
#endif

Board late init (CONFIG_BOARD_LATE_INITIALIZE=y) just exit after work is done:

#ifdef CONFIG_BOARD_LATE_INITIALIZE
static int nx_start_task(int argc, FAR char **argv)
{
/* Do the board/application initialization and exit */
nx_start_application();
return OK;
}
#endif

and finally some resources are freed:

image

@eren-terzioglu
Copy link
Contributor

eren-terzioglu commented Feb 20, 2026

Hi,
We need to test it for Espressif devices. Please do not merge fastly because seems something got broken already. I will update about it later.

@patacongo
Copy link
Contributor

patacongo commented Feb 20, 2026 via email

@linguini1
Copy link
Contributor Author

Hi,
We need to test it for Espressif devices. Please do not merge fastly because seems something got broken already. I will update about it later.

Thank you for testing!

@linguini1
Copy link
Contributor Author

The only thing that puzzles me about this change is how the "init thread" aligns with safety standards. This mechanism creates a thread that is later destroyed, which isn't entirely desirable for safety critical applications. But on the other hand, "init thread" is part of the system initialization process, and during the initialization phase, dynamic memory allocation is tolerated, but I'm not sure about deallocation. The goal here is that in the event of a board initialization failure, the system is in a known safe state.
So, I think it still fits within the acceptable use of dynamic memory for safety critical apps, but not sure. Ultimately, there's always BOARDIOC_FINALINIT option, which allows users to get rid of the "init thread" and perform initialization in "main", just like old BOARDIOC_INIT.

That's a good point. I wonder if it would be worth making board_late_initialize synchronous instead of a thread?

@raiden00pl
Copy link
Member

Nx_bringup() just starts some threads and returns. It does not exit. See "Bring Up the System"; it is just a sequence of function calls and still exists after the bring-up is complete. When it returns, it just falls through and becomes the IDLE loop. Nothing starts the IDLE loop, See "Enter to idleloop". The IDLE was just created from nothing at power-up, what was the initialization thread is simple called the IDLE loop after that point.

That's the point. It creates "AppBringUp" that runs nx_start_task() and this thread can exit. But that's a topic for another time. Currently, NuttX isn't compliant with safety-critical standards anyway; we can address that in the future.

That's a good point. I wonder if it would be worth making board_late_initialize synchronous instead of a thread?

@linguini1 board initialization must be performed in the "thread context", not "init context". More advanced drivers require kernel features that are not available in the "init context"

BREAKING: In an effort to simplify NuttX initialization, NSH_ARCHINIT is
removed. board_app_initialize is also removed. BOARD_LATE_INITIALIZE now
performs all board initialization logic, and is by default enabled. All
references to these symbols are removed. BOARDIOC_INIT remains, but will
result in -ENOTTY when called. It is to be removed in a later commit.

Quick fix: Boards relying on NSH_ARCHINIT should now enable
CONFIG_BOARD_LATE_INITIALIZE instead. If the application needs
fine-grained control over board initialization from userspace, the logic
performed by BOARDIOC_INIT may be copied to the board_finalinitialize
function and used instead via BOARDIOC_FINALINIT. All
board_app_initialize logic provided by NuttX is now moved to
board_late_initialize, and the same should be done for out-of-tree
boards.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Logic provided by board_app_initialize is replaced by
board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Logic provided by board_app_initialize is removed due to the removal of
BOARDIOC_INIT boardctl command. Logic inside board_late_initialize is to
be used and is identical.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Replaced board_app_initialize logic with board_late_initialize.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
@linguini1
Copy link
Contributor Author

To try and get the CI at least to verify the PR (since it's quite an undertaking to do manually), I have put back BOARDIOC_INIT so that it returns -ENOTTY when called via boardctl. This should prevent the missing symbol errors during CI compilation (due to not having the respective changes in nuttx-apps reflected). Once both PRs are merged I will remove BOARDIOC_INIT entirely.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

Comments