Hello all,

I thought it would be a good idea to summarize the system configuration
and initialization feature that has been pushed to develop.  This
feature is definitely a work in progress, and this email is meant to
further its development.  In other words, there is a lot of room for
improvement, so please don't hold back with any feedback.

Much of this email will be identical to the previous syscfg summary.  If
you are already familiar with the feature, you may want to skip to the
list of open questions and unresolved issues at the end.

Proposal:
    1. Move package initialization out of apps (main()) and into BSPs
       and auto-generated C files.
    2. Specify package configuration in the pkg.yml hierarchy.

Motivations:
    1. Simplify process of creating an app (no more boilerplate
       code).
    2. Simplify system configuration and make it more cohesive (don't
       specify config within C code).
    3. Ability to audit entire system configuration via the "newt
       config" command.

### 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:
    * Flash map
    * Driver initialization: UART, ADC, SPI, etc.

(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 in 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")
    * type (optional)

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 warns the
user and ignores the attempt.  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

If two packages with the same priority attempt to override the same
setting, newt raises an error and aborts the build.  The remedy for this
problem is for a higher-priority package to override the setting as
well.

The expectation is that libraries would define sensible defaults in
their pkg.yml files, and BSPs, apps, and targets would override those
defaults.  Library packages may also override settings defined by other
library packages.

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.

### Conditional configuration

A pkg.yml file can conditionally specify material depending on the
syscfg state.  Items in a pkg.yml file can specify the name of a single
syscfg setting; the item is only processed if the setting has a
non-false value.  A setting is "false" if its value is "0" or the empty
string.

That is a pretty cryptic description.  Here is an example
(libs/os/pkg.yml):

    pkg.deps:
        - sys/sysinit
        - libs/util

    pkg.deps.OS_CLI:
        - libs/shell

The first item is the unconditional dependency list.  The second item is
a conditional dependency list.  If the system enables the OS_CLI
setting, then the os package depends on the shell package, in addition
to the two unconditional dependencies.  If the OS_CLI setting is not
enabled, the second dependency list is ignored.

This example uses the pkg.deps item, but other items can also be
dependent on syscfg: cflags, apis, req_apis, init_function, init_stage,
syscfg_defs, syscfg_vals.

Newt raises an error and aborts the build if it detects a circular
dependency among syscfg overrides in a set of packages.

### Setting types

By default, a syscfg setting has the string type.  This type is
appropriate for most settings.

Alternatively, a syscfg setting definition may specify one of the
following types:
    * task_priority
    * interrupt_priority

(1) task_priority

A setting with the task_priority type specifies a Mynewt task's numeric
priority (!).  The value of a task_priority setting must be one of the
following forms:

    1. A number.
    2. "any"

All explicit priorities (form 1) are assigned as specified.  All "any"
priorities (form 2) are then iterated alphabetically by setting name;
each setting is assigned a value that is one greater than the current
greatest priority.

Task priorities greater than or equal to 240 are reserved to the system.
Newt raises an error and aborts the build if a task priority setting
resolves to a value >= 240.

Newt raises an error and aborts the build if multiple task priority
settings resolve to the same numeric value.

(2) interrupt_priority

Newt manages interrupt priority settings similarly to task priority
settings.  The differences are as follows:

    * No maximum value (i.e., no restriction for values >= 240).
    * Multiple settings can have the same value.
    * Settings specifies as "any" are all assigned the same numeric
      value: one greater than the largest explicitly specified value.

### newt target config command

The following command is added to newt:

    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.
    * Sequence of setting overrides (i.e., the name of each package
      overrides the setting in the current build).

### 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.

### Questions / issues

1. Flash settings.  A big piece of configuration that is missing from
syscfg is the flash layout.  Ideally, a system could specify its flash
map, and newt would ensure the following:
    * Named regions of storage that file systems / FCB can be
      initialized with.
    * Correct flash addresses in linker scripts
    * Correct flash addresses in download and debug scripts

2. Conditional configuration may not be flexible enough.  An item in a
pkg.yml file can be dependent on the true/false value of a single syscfg
setting.  The following might be preferable.

    a. Multiple settings and boolean logic.  For example, only depend on
       package x if: SETTING_A and (SETTING_B or SETTING_C).  This would
       be useful for a BSP that needs to depend on the uart driver if
       any of its UARTs are enabled.  To achieve this today, we need
       lots of conditional dependency lists, e.g.,:

        pkg.deps.UART_0:
            - drivers/uart/uart_hal
        pkg.deps.UART_1:
            - drivers/uart/uart_hal
        pkg.deps.UART_2:
            - drivers/uart/uart_hal


    b. Depend on the exact value of a setting, rather than its boolean
       value. For example,

        If LOG_TYPE==CONSOLE, then depend on libs/console/full
        If LOG_TYPE==FCB, then depend on sys/fcb

      Currently, the only way to achieve this behavior is by defining
      lots of boolean settings: LOG_TYPE_CONSOLE, LOG_TYPE_FCB, etc.

    c. Depend on the presence of a package or API.  Currently, a pkg.yml
       item can only depend on the value of a syscfg setting.

3. Setting requirements.  Would it be useful if a setting could specify
system requirements in order for it to be set?  If a package attempted
to override a setting whose requirements are not met, newt could raise
an error.  For example, it doesn't make sense to override an ADC's
interrupt priority setting if the ADC itself is disabled in syscfg. 

4. 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.

5. The init_stage mechanism feels really fragile.  My thought was that
there would be defined stages that packages would naturally fit into,
but now I'm not so sure this is practical.  What we really want is a way
to express initialization dependencies (e.g., package X can only be
initialized after pacakges Y and Z), but this could lead to a config
system that is incomprehensible.

6. Modular syscfg headers.  Currently, there is a singly header:
syscfg/syscfg.h.  Lots of files need to include this header to access
the system config, resulting in lots of unnecessary rebuilds when a
single setting is changed.

Instead, we could have a separate header for each pacakge which defines
settings (defines, not overrides).  For example,

    syscfg/syscfg_libs_os.h
    syscfg/syscfg_fs_nffs.h

The syscfg/syscfg.h header would just include all the smaller headers.

7. A "newt target init <target-name>" command: shows the order of
package initialization (probably a poor name for this command).

8. A "newt target deps <target-name>" command: shows the dependency
graph of the build.

Thanks for reading,
Chris

Reply via email to