Hello all,

This is a follow-up email regarding Mynewt system initialization.  I
took all of your feedback (very helpful, thanks!) and did some more
thinking.  As always, all comments, criticisms, and suggestions are
appreciated.

Thanks,
Chris

### Recap

    Proposal:
        1. Move system initialization out of apps and into BSPs and
           auto-generated C files.
        2. Specify system settings in config files.

    Motivations:
        1. Simplify process of creating an app (no more boilerplate
           code).
        2. Simplify system configuration (don't specify config within C
           code).
        3. Better code organization; cputime, msys, etc. "feel" more
           like system-level modules than application-level, so put
           initialization code where it belongs.

    Examples of "system modules" are:
        * mbufs / msys.
        * Network stacks (e.g., NimBLE host, controller, and HCI
          transport).
        * newtmgr
        * console
        * logging
        * drivers

        The biggest challenge is: ensuring the developer maintains
        precise control over the system configuration.  The current
        Mynewt scheme gives control to the developer by pushing
        configuration up to the application level where he is forced to
        deal with it.

### Changes from previous proposal

* Settings go in pkg.yml files; elimination of syscfg.yml files.
* Initialization is done by generated C files; init functions specified
  in pkg.yml files.
* Removal of newt "features" (pkg.features).

### Summary

System initialization happens in two stages:
    1. BSP init (bsp_init())
    2. sysinit (sysinit())

(1) BSP init sets up everything that is BSP specific:
    * UARTs
    * Flash map
    * Other hardware drivers

(2) Sysinit sets up all the libraries which aren't BSP or hardware
dependent (os, msys, network stacks, etc).  The initialization code is
implemented in the appropriate package.  Newt generates C code which
executes each package's initialization function.

### Syscfg header file generation

The system knows what to initialize and how to initialize it based on
the contents of the following header file:

    <target-path>/include/syscfg/syscfg.h

For example:

    targets/bleprph-nrf52dk/include/syscfg/syscfg.h

This header file is generated by newt during the build process.  Newt
arranges for all packages to have access to this header file.

The syscfg.h header file consists of three sections:
    * Settings
    * Indication of which packages are present
    * Indication of which APIs are available

Here is an abbreviated syscfg.h:

    /*** Settings */

    #ifndef MYNEWT_VAL_CLOCK_FREQ
    #define MYNEWT_VAL_CLOCK_FREQ (1000000)
    #endif

    #ifndef MYNEWT_VAL_MSYS_1_BLOCK_COUNT
    #define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (15)
    #endif

    #ifndef MYNEWT_VAL_MSYS_1_BLOCK_SIZE
    #define MYNEWT_VAL_MSYS_1_BLOCK_SIZE (260)
    #endif

    /*** Packages */

    #ifndef MYNEWT_PKG_LIBS_NEWTMGR
    #define MYNEWT_PKG_LIBS_NEWTMGR (1)
    #endif

    #ifndef MYNEWT_PKG_LIBS_NEWTMGR_TRANSPORT_BLE
    #define MYNEWT_PKG_LIBS_NEWTMGR_TRANSPORT_BLE (1)
    #endif

    #ifndef MYNEWT_PKG_SYS_CONFIG
    #define MYNEWT_PKG_SYS_CONFIG (1)
    #endif

    /*** APIs */

    #ifndef MYNEWT_API_BLE_DRIVER
    #define MYNEWT_API_BLE_DRIVER (1)
    #endif

    #ifndef MYNEWT_API_BLE_TRANSPORT
    #define MYNEWT_API_BLE_TRANSPORT (1)
    #endif

    #ifndef MYNEWT_API_CONSOLE
    #define MYNEWT_API_CONSOLE (1)
    #endif

Newt generates the syscfg.h file from information read from pkg.yml
files.  This proposal adds two items to the pkg.yml structure:

    1. pkg.syscfg_defs
    2. pkg.syscfg_vals

Both of these items are optional.

(1) pkg.syscfg_defs is an associative array of setting definitions.  The
setting name is indicated by the element key.  A setting definition has
the following fields:
    * description ("description")
    * default value ("value")
    * requirements (TBD)

Here is an example from net/nimble/host/pkg.yml:

    pkg.syscfg_defs:
        BLE_SM:
            description: 'Security manager legacy pairing.'
            value: 1
        BLE_SM_SC:
            description: 'Security manager secure connections (4.2).'
            value: 0

A setting can only be defined once.  If a setting with the same name is
defined more than once, in any number of pkg.yml files, newt complains
and aborts the build.

(2) pkg.syscfg_vals is also an associative array keyed by setting name.
However, it only contains setting values.  This array is used to
override default setting values.

Here is a hypothetical example:

    pkg.syscfg_vals:
        MSYS_1_BLOCK_COUNT: 12
        MSYS_1_BLOCK_SIZE: 260
        CLOCK_FREQ: 1000000

If a package attempts to override an undefined setting, newt complains
and the build is aborted.  If several packages override the same
setting, the tie is broken according to package priority.  Packages are
prioritized as follows (lowest priority first, highest last):

    1. Library
    2. BSP
    3. App
    4. Target

The expectation is that libraries would define sensible defaults in
their pkg.yml files, and BSPs, apps, and targets would override those
defaults.

A library is not allowed to override settings; it can only define them.

Within the syscfg.h file:
    * Each setting macro is named as follows:
        MYNEWT_VAL_<escaped-setting-name>

    * Each package-presence macro is named as follows:
        MYNEWT_PKG_<escaped-package-name>

    * Each API-presence macro is named as follows:
        MYNEWT_API_<escaped-API-name>

A name is escaped by converting it to upper case and replacing all
non-alphanumeric characters with underscores.

To prevent unnecessary rebuilds, newt only overwrites the syscfg.h file
if there are any configuration changes since the previous build.

### Task priorities

Settings that specify task priority should not have plain numeric
values.  Instead, the value should be one of the following forms:

    (1) PRIORITY_<number>
    (2) PRIORITY_ANY

Newt converts these strings to numeric values as follows:

    1. All explicit priorities (form 1) are assigned as specified (i.e.,
       the "PRIORITY_" prefix is removed)
    2. All remaining priority settings are iterated alphabetically by
       name; each setting is assigned a value that is one greater than
       the current greatest priority.

Newt raises an error and aborts if it runs out of priority values
(increases beyond 255).

Newt raises an error if multiple packages are assigned the same
priority.

### newt target config command

Newt would have a new command: newt target config <target-name>.  This
command shows the following information about each setting for the
specified target:

    * Setting name
    * Setting description
    * Actual value and package which specifies it.
    * Default value and package which defines it.
    * Corresponding C macro name.

### sysinit pkg.yml items

The following two optional items are added to the pkg.yml structure:

    1. pkg.init_function
    2. pkg.init_stage

(1) pkg.init_function specifies the name of a C function which
initializes the package.

(2) pkg.init_stage is a numeric value indicating when the package should
get initialized, relative to other packages.

Initialization stages are executed in ascending order.  Initialization
of packages within a stage are ordered alphabetically by package name.

### sysinit C code

At build time, newt generates the following target-specific C file:

    <target-path>/src/sysinit.c

This file initializes each package whose pkg.yml file specifies a
pkg.init_function item.  This file does not depend on any other
packages, and it does not include any headers.

Here is an abbreviated example of how this file might look:

    void bootutil_pkg_init(void);
    void console_pkg_init(void);
    void log_init(void);
    void log_reboot_pkg_init(void);
    void nffs_pkg_init(void);
    void os_pkg_init(void);
    void shell_pkg_init(void);
    void stats_module_init(void);

    void
    sysinit(void)
    {
        /*** Stage 0 */
        /* 0.0: libs/os */
        os_pkg_init();
        /* 0.1: sys/stats */
        stats_pkg_init();

        /*** Stage 1 */
        /* 1.0: sys/log */
        log_pkg_init();

        /*** Stage 2 */
        /* 2.0: fs/nffs */
        nffs_pkg_init();
        /* 2.1: sys/reboot */
        log_reboot_pkg_init();

        /*** Stage 5 */
        /* 5.0: libs/bootutil */
        bootutil_pkg_init();
        /* 5.1: libs/console/full */
        console_pkg_init();
        /* 5.2: libs/shell */
        shell_pkg_init();
    }

An init function takes no parameters and returns void.  The parameter
list is empty because the function is expected to read its configuration
from the syscfg.h file.  The void return type is a bit harder to
justify, but here is my thinking.  If init functions all returned error
codes, the generated sysinit code would always handle a failure return
code in the same manner: abort the initialization sequence and perform
some emergency behavior (reboot, enter rescure mode, whatever).  This
repeated checking of return codes would result in extra code (text) with
no real benefit.  It would be better for initialization to call a
panic function on error.  The panic function could either assert(0),
or use longjmp to break out of the init sequence.  Mynewt would need to
allow the application to assign semantics to the panic function.

### Removal of features

First, here is a summary of how the Mynewt features mechanism works.  A
feature is a system-wide boolean setting defined by a package.  A
package's configuration can be dependent on which features the system
enables.  For example, a feature being enabled by cause a package to
specify additional dependencies, cflags, or even other features.

I believe features are currently used to solve the following problems:

1. Allow package A to depend on package B, but only if package B is
already part of the system.  I.e., use it if it's already there, but
don't pull it into the build if it isn't.  Here is an example of this
usage from fs/fs/pkg.yml:

    pkg.deps.SHELL:
        - libs/shell

I.e., the file system package depends on shell only if the SHELL feature
is enabled (presumably by the shell package).  The reason for the
conditional dependency is the existence of file system shell commands
(e.g., ls).

2. Allow package A's C code to know if package B is part of the build.
This has a similar use case to (1) above.  The fs/fs package does this
as follows:

    pkg.cflags.SHELL: -DSHELL_PRESENT

The fs C code registers its shell commands if the SHELL_PRESENT macro is
defined.

3. Allow the BSP download and debug scripts to know if a boot loader is
being uploaded to the board.  The scripts need to know this information
so that they know the correct flash address to write the binary.  Newt
conveys this information to the scripts by passing the list of enabled
features as command line arguments to the scripts.  If a script detects
the "bootloader" argument, it knows it is dealing with a boot loader.

I believe the sysinit mechanism handles all of these cases at least as
well as the features mechanism does.  To simplify usage, I think it is
best to remove features entirely.  Below I will explain how I expect
sysinit to handle the above three cases.

(1) Conditional dependencies

The contitional "deps" syntax remains the same, except a setting name is
appended to "deps." rather than a feature name.  If the named setting
is defined an non-zero, the dependency is processed.

I think this is better than the feature-based method, because usually
the simple presence of a package should not determine whether another
package depends on it.  For example, it is not hard to imagine an
application which uses both the fs and shell packages, but which should
not contain the fs shell commands (e.g., on a space constrained
platform).  Currently it is not possible to arrange for this.  With
syscfg, fs would define a setting indicating whether the shell commands
should be included.  Example (fs/fs/pkg.yml):

    pkg.syscfg_defs:
        FS_CLI:
            description: 'Enable file system shell commands'
            value: 1

    pkg.deps.FS_CLI: libs/shell

(2) Allow package A's C code to know if package B is part of the build.

This is trivially handled via inclusion of the syscfg.h header.

(3) Allow the BSP download and debug scripts to know if a boot loader is
being uploaded to the board.

I think the right answer here is to use a separate BSP for the
bootloader.  Then, the scripts don't have to know what settings are
defined because the boot loader BSP scripts are hardcoded to write to
address 0.  Alternatively, newt could just pass the name of every
non-zero setting to the scripts, and the boot app could define a
"bootloader" setting.

There is one thing that features can do that syscfg cannot:
conditionally override other settings.  In other words, you can't do
something like this:

    pkg.syscfg_vals:
        FS_CLI: 1
        FS_CLI.I_DONT_WANT_CLI: 0

I don't view this as an issue - I think the appropriate package will
know the exact value to assign to a setting.

Removal of features would also mean removal of the pkg.feature_blacklist
and pkg.feature_whitelist configuration items.  While I understand why
these are necessary today, I think they have the potential to cause a
lot of confusion when trying to nail down exactly which settings your
build is configured with.

### Questions / issues

1. Do we need second-stage initialization?  This would occur after the
OS has been started but before the scheduler is running.  This might be
tricky to arrange; it isn't clear which stack this code should run in
since not all packages have a dedicated stack.

2. Setting profiles.  It might be useful for NimBLE to have a
peripheral-only configuration, for example.  In the system I outlined
above, the user would need to override a bunch of BLE settings in his
app or target to achieve this.

3. Should libraries be able to override another libraries settings?  In
this proposal, a package's settings can only be overridden by something
above it in the hierarchy (i.e., bsp, app, or target).  I couldn't think
of a compelling reason to allow this, and it could make builds harder to
understand and debug.

Reply via email to