System Initialization and System Shutdown

Mynewt allows a package to designate startup and shutdown functions. These functions are called automatically by the OS using facilities called sysinit and sysdown.

This guide:

  • Assumes you have read the Concepts section that describes the Mynewt package hierarchy and its use of the pkg.yml and syscfg.yml files.

  • Assumes you have read the Mynewt Theory of Operations and are familiar with how newt determines package dependencies for your target build.

System Initialization

During system startup, Mynewt creates a default event queue and a main task to process events from this queue. You can override the OS_MAIN_TASK_PRIO and OS_MAIN_TASK_STACK_SIZE setting values defined by the kernel/os package to specify different task priority and stack size values.

Your application’s main() function executes in the context of the main task and must perform the following:

  • At the start of main(), call the Mynewt sysinit() function to initialize the packages before performing any other processing.

  • At the end of main(), wait for and dispatch events from the default event queue in an infinite loop.

Note: You must include the sysinit/sysinit.h header file to access the sysinit() function.

Here is an example of a main() function:

int
main(int argc, char **argv)
{
    /* First, call sysinit() to perform the system and package initialization */
    sysinit();

    /* ... other application initialization processing ... */

    /*  Last, process events from the default event queue.  */
    while (1) {
       os_eventq_run(os_eventq_dflt_get());
    }
    /* main never returns */
}

Specifying Package Initialization Functions

The sysinit() function calls the sysinit_app() function to perform system initialization for the packages in the target. You can, optionally, specify one or more package initialization functions that sysinit_app() calls to initialize a package.

A package initialization function must have the following prototype:

void init_func_name(void)

Sysinit functions are not expected to fail. If a sysinit function encounters an unrecoverable failure, it should invoke one of the SYSINIT_PANIC macros. By default, these macros trigger a crash, but the behavior can be overridden by configuring the panic function with sysinit_panic_set().

Package initialization functions are called in stages to ensure that lower priority packages are initialized before higher priority packages. A stage is an integer value, 0 or higher, that specifies when an initialization function is called. Mynewt calls the package initialization functions in increasing stage number order. When multiple init functions have the same stage number, they are called in lexicographic order (by function name).

You use the pkg.init parameter in the pkg.yml file to specify an initialization function and the stage number to call the function. You can specify multiple initialization functions with a different stage number for each function for the parameter values. This feature allows packages with interdependencies to perform initialization in multiple stages.

The pkg.init parameter has the following syntax in the pkg.yml file:

pkg.init:
    pkg_init_func1_name: pkg_init_func1_stage
    pkg_init_func2_name: pkg_init_func2_stage

              ...

    pkg_init_funcN_name: pkg_init_funcN_stage

where pkg_init_func#_name is the C function name of an initialization function, and pkg_init_func#_stage is an integer value, 0 or higher, that indicates the stage when the pkg_init_func#_name function is called.

Generated sysinit_app() Function

The newt tool processes the pkg.init parameters in all the pkg.yml files for a target, generates the sysinit_app() function in the <target-path>/generated/src/<target-name>-sysinit_app.c file, and includes the file in the build. Here is an example sysinit_app() function:

/**
 * This file was generated by Apache Newt (incubating) version: 1.0.0-dev
 */

#if !SPLIT_LOADER

void split_app_init(void);
void os_pkg_init(void);
void imgmgr_module_init(void);

/* ... */

void stats_module_init(void);

void
sysinit_app(void)
{

    /*** Stage 0 */
    /* 0.0: kernel/os */
    os_pkg_init();

    /*** Stage 2 */
    /* 2.0: sys/flash_map */
    flash_map_init();

    /*** Stage 10 */
    /* 10.0: sys/stats/full */
    stats_module_init();

    /*** Stage 20 */
    /* 20.0: sys/console/full */
    console_pkg_init();

    /*** Stage 100 */
    /* 100.0: sys/log/full */
    log_init();
    /* 100.1: sys/mfg */
    mfg_init();

    /* ... */

    /*** Stage 300 */
    /* 300.0: sys/config */
    config_pkg_init();

    /*** Stage 500 */
    /* 500.0: sys/id */
    id_init();
    /* 500.1: sys/shell */
    shell_init();

    /* ... */

    /* 500.4: mgmt/imgmgr */
    imgmgr_module_init();

    /*** Stage 501 */
    /* 501.0: mgmt/newtmgr/transport/nmgr_shell */
    nmgr_shell_pkg_init();
}
#endif

System Shutdown

A Mynewt package can specify a sequence of system shutdown (“sysdown”) function calls. As with sysinit, a stage number is associated with each function. On shutdown, sysdown functions are executed in ascending order of stage number. In the case of a tie, functions are executed in lexicographic order (by function name).

The sysdown procedure is only performed for a controlled shutdown. It is executed when the system processes a newtmgr “reset” command, for example. It is not be executed when the system crashes, browns out, or restarts due to the hardware watchdog.

Sysdown functions are specified in a pkg.yml file using the pkg.down key. They use the following function type:

int func(int reason)

The reason parameter is currently unused and should be ignored.

Each shutdown callback returns one of the following codes:

  • SYSDOWN_COMPLETE

  • SYSDOWN_IN_PROGRESS

If a sysdown function is able to complete its work synchronously, it should return SYSDOWN_COMPLETE.

The “in progress” case is a bit more complicated. Sysdown functions should not block on the current task (e.g., they should not tell the default task to do something and then wait for the result). Blocking like this will result in a deadlock since the current task is waiting for the sysdown function to complete.

Instead, if a sysdown function needs to do extra work in the current task, it should do so asynchronously. That is, it should enqueue an event to the task’s event queue, then return SYSDOWN_IN_PROGRESS. When the sysdown procedure eventually completes, the task should call sysdown_release.

When all sysdown procedures have completed, Mynewt proceeds to reset the device. If it takes longer than MYNEWT_VAL(SYSDOWN_TIMEOUT_MS) milliseconds (default: 10s) for all shutdown procedures to complete, a crash is triggered and the device resets.

As an example, the NimBLE BLE host package configures a sysdown function that terminates all open connections. Its pkg.yml contains the following map:

pkg.down:
    ble_hs_shutdown: 200

and ble_hs_shutdown is defined as follows:

int
ble_hs_shutdown(int reason)
{
    int rc;

    /* Ensure this function only gets called by sysdown. */
    SYSDOWN_ASSERT_ACTIVE();

    /* Initiate a host stop procedure. */
    rc = ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb,
                     NULL);
    switch (rc) {
    case 0:
        /* Stop initiated.  Wait for result to be reported asynchronously. */
        return SYSDOWN_IN_PROGRESS;

    case BLE_HS_EBUSY:
        /* Already stopping.  Wait for result to be reported asynchronously. */
        return SYSDOWN_IN_PROGRESS;

    case BLE_HS_EALREADY:
        /* Already stopped.  Shutdown complete. */
        return SYSDOWN_COMPLETE;

    default:
        BLE_HS_LOG(ERROR, "ble_hs_shutdown: failed to stop host; rc=%d\n", rc);
        return SYSDOWN_COMPLETE;
    }
}

In the cases where this function returns SYSDOWN_IN_PROGRESS, the host eventually calls sysdown_release when the procedure completes.