nxinit NXInit

Overview

In NuttX, there are various options for the init entry point, with NSH being one of the commonly used ones. However, taking NSH as an example, it is not suitable for all usage scenarios. For instance, the code size required by NSH and NSH_LIBRARY is relatively large, and NSH lacks management capabilities for daemons/services (such as restarting services).

NXInit was created by Xiaomi to solve these issues. It is compatible with most of the syntax of Android Init, which you can read about here: https://android.googlesource.com/platform/system/core/+/master/init/README.md

It is lightweight, supports command execution triggered by events, supports service/daemon management and many more.

The init.rc consists of five categories of statements, all line-oriented with parameters separated by spaces. A line whose first character that is not whitespace is # is a comment. A board init.rc in RCSRCS goes through the C preprocessor when the board ROMFS image is built: there it can use #include, #ifdef and C comments, and a line that starts with # is a preprocessor directive.

File path configured by CONFIG_SYSTEM_NXINIT_RC_FILE_PATH.

1. Actions and Commands

on <trigger>
   <command>
   <command>
   <command>
  • An Action (command sequence) contains trigger conditions. When satisfied, it is added to the execution queue.

  • Commands are the specific execution content of actions, such as setprop (set system properties), start (start services), and mount (mount file systems).

2. Services and Options

service <name> <pathname> [ <argument> ]*
   <option>
   <option>
   ...
  • A Service is a program started by Init, with a unique name (can be overridden via the override option).

  • Options are modifiers for services, affecting their running mode and timing. Examples include class (specify service category for batch start/stop), console (redirect the service’s stdio to a console device; see below), override (override previously defined services), restart_period (interval for restarting exited services), and reboot_on_failure (critical services trigger device reboot on startup failure or abnormal exit).

The console Option

service <name> <pathname> [ <argument> ]*
   console [ <device> ]

A service does not open a console device on its own - it simply inherits whatever stdin/stdout/stderr NXInit itself has, which for many boards is never set up at all (there is no /dev/console unless CONFIG_DEV_CONSOLE is enabled). console fixes this by opening <device> (CONFIG_SYSTEM_NXINIT_CONSOLE_DEV, /dev/console by default, if omitted) and duplicating it onto the service’s stdin, stdout and stderr before it is spawned.

After the start, the device is also the controlling terminal of the service (TIOCSCTTY with the PID of the service), as init and getty do on other systems: Ctrl-C and Ctrl-Z send SIGINT and SIGTSTP to it (with CONFIG_TTY_SIGINT and CONFIG_TTY_SIGTSTP), and a shell finds its foreground process group with tcgetpgrp(), so it can use job control. A device without these signals keeps working as before. The NSH sh program gives up the terminal at its prompt and makes each foreground command the foreground process, so Ctrl-C at its prompt does not end it.

This is required for a plain shell service (e.g. service console sh) to actually be usable as an interactive console. It is not needed for nsh (the full NSH application, as opposed to plain sh), which already does the equivalent internally via nsh_consolemain().

For a USB gadget console (CONFIG_CDCACM_CONSOLE/CONFIG_PL2303_CONSOLE), the device does not exist until the gadget is actually registered with the USB stack - unlike a plain UART, this does not happen implicitly during early boot. The gadget must be brought up once, before any service using console is started, e.g. via apps/system/cdcacm’s sercon:

on init
    exec -- sercon

service console sh
    console

Triggers

The triggers of an action serve as its triggering conditions, supporting one or more triggers (which must all be satisfied simultaneously).

There are two types of triggers: event triggers (e.g., boot) and action triggers (also known as property triggers, e.g., property:a=b). Actions can include multiple action triggers but only one event trigger. Triggers can be combined with && to represent “AND” conditions.

The value of a property trigger is matched with fnmatch, so glob patterns are accepted. Use != to trigger when the value does not match, e.g. property:a!=b. An on <event> action fires only on the edge when its condition becomes satisfied, not on every subsequent property update.

Property (action) trigger support requires the init_property_*() backend to be provided; property_simple.c offers a minimal implementation that forwards setprop to the action manager.

1. Event Trigger Execution Order

  1. boot: The first event after NXInit starts.

  2. init: After boot event completation.

  3. netinit: Optional, after netinit_bringup() returns.

  4. finalinit: After all previous events complete.

2. Action Triggers Checking

  • All action triggers are automatically checked once at startup.

  • Property triggers are checked when the property is created or its value is updated (e.g., property:a=b is checked when a’s value changes).

Built-in Properties

NXInit sets the following built-in properties on its own:

  • sys.boot.reason: the cause of the last board reset, set before the boot event fires. It is queried once at startup via boardctl(BOARDIOC_RESET_CAUSE, ...); applications do not need to set it themselves. This requires CONFIG_BOARDCTL_RESET_CAUSE and a board-provided board_reset_cause() implementation.

    • If CONFIG_BOARDCTL_RESET_CAUSE is not enabled, sys.boot.reason is never set, so any trigger referencing it (e.g. property:sys.boot.reason=...) simply never matches; there is no dedicated “unset” value to trigger on.

    • If CONFIG_BOARDCTL_RESET_CAUSE is enabled but the boardctl() call itself fails (the board reports an error), NXInit aborts startup.

    The value has one of two forms:

    1. <cause>,<subreason> for hardware reset causes, where <subreason> is a numeric flag (e.g. the watchdog timer index): cold, watchdog, undervoltage, warm, powerkey, lowpower, unknown. For example, a watchdog reset from timer 4 sets the property to watchdog,4.

    2. A single soft-reset reason string, for software-triggered resets (BOARDIOC_RESETCAUSE_CPU_SOFT): reboot, assert, kernel_panic, bootloader, recovery, factory_reset, factory_reset_inquiry, thermal, shutdown, unknown. shutdown stands for a power-off soft reset, and unknown for a subreason NXInit does not recognize.

    Since the property is matched with fnmatch and alternatives can be separated with |, a trigger can match on the cause, the subreason, or a set of soft-reset reasons:

    on init && property:sys.boot.reason=watchdog,4
        ...
    
    on init && property:sys.boot.reason=bootloader|recovery|thermal
        ...
    
  • init.trigger[.<core>]: the key that trigger <event> writes, with <event> as the value; on <event> matches it. Each init instance uses its own key, so instances that share a property backend do not run each other’s actions:

    • init.trigger.core<N> with CONFIG_BMP, where <N> is the CPU the instance runs on;

    • init.trigger.<cpuname> with CONFIG_RPMSG_LOCAL_CPUNAME;

    • init.trigger otherwise.

    With a shared backend, an instance can wait for another instance’s event through that instance’s key, or trigger an event on it by setting the key. For example, audio waits for ap’s appinit:

    init.audio.rc
    on property:init.trigger.ap=appinit
        ...
    

    and ap triggers audio’s appinit:

    init.ap.rc
    on boot
        setprop init.trigger.audio appinit
    

Commands

The commands supported by an action fall into three types: the built-in commands of NXInit, the built-in commands of NSH (if NSH is enabled), and Builtin Apps.

These are the built-in commands of NXInit:

  • System operations: mount/umount (mount/unmount file systems, see below), set (set an environment variable), setprop (set a system property), unlink (remove a file).

  • Service management: start/stop (start/stop a service), exec_start (start a service and wait until it exits), class_start/class_stop (start/stop all services of a class).

  • Programs: exec -- <program> [<argument>]* (run a program and wait until it exits).

  • Others: trigger (trigger an event), and netinit, boot, start_cpu, board_finalinit and rptun when their configuration options are on (CONFIG_NETUTILS_NETINIT, CONFIG_BOARDCTL_BOOT_IMAGE, CONFIG_BOARDCTL_START_CPU, CONFIG_BOARDCTL_FINALINIT, CONFIG_RPTUN).

Mounting File Systems

mount <type> <device> <dir> [<flag>]* [<options>]
mount -t <type> [-o <options>] [<device>] <dir>
umount <dir>

<flag> is defaults, rw, ro, nosuid, nodev, noexec, sync, remount, dirsync or noatime. In both forms, <options> is a comma-separated list of flags and file system options. For a file system without a device, <device> is any name that is not a device, for example none. The second form is the one of the NSH command. In the pseudo file system, <dir> does not need to exist: NuttX makes the mount point. For example:

on boot
    mount tmpfs none /run
    mount v9fs none /mnt trans=virtio,tag=host
    mount -t hostfs -o fs=. /data

A mount line in neither form fails. With CONFIG_DISABLE_MOUNTPOINT, NXInit has no mount and umount commands.

Compound Commands

A single line inside an action body may chain multiple commands with && and ||, using the familiar shell short-circuit semantics: the next command in an && chain only runs if the previous one exited with status 0 (success), and the next command in an || chain only runs if the previous one exited non-zero (failure). A quoted argument ("...") may contain &&/|| without being treated as an operator.

on boot
    echo "start" && hello && echo "done"
    ls /missing || echo "not found"
    echo "A" && echo "B" || echo "fallback"

Examples

This is an example of enabling the basic functions of the NXInit component, with all log levels additionally enabled to facilitate debugging of the init.rc script.

defconfig:

+CONFIG_INIT_ENTRYPOINT="init_main"
+CONFIG_SYSTEM_NXINIT=y
+CONFIG_SYSTEM_NXINIT_DEBUG=y
+CONFIG_SYSTEM_NXINIT_INFO=y
+CONFIG_SYSTEM_NXINIT_WARN=y

init.rc:

on boot
    start console        /* Start the service named "console" */
    sleep 1              /* Block for 1 second */
    exec_start mkdir_tmp /* Start the "mkdir_tmp" service and wait for exit */

on boot && property:sys.boot.reason=bootloader
    echo "On boot, the reason is BL."
    start fastboot

service console sh
    class core
    restart_period 1000  /* Service restart interval (in milliseconds), calculated from service start time */

service fastboot fastbootd
    class core
    restart_period 7000

service mkdir_tmp sh -c "mkdir /tmp"
    reboot_on_failure 0 /* Reboot device with reason 0 on startup failure or abnormal exit */
    oneshot             /* This service runs only once; releases resources after exit */